Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
#!/usr/bin/python
|
2012-08-03 03:29:10 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
|
|
|
|
# (c) 2012, Stephen Fromm <sfromm@gmail.com>
|
|
|
|
#
|
|
|
|
# This file is part of Ansible
|
|
|
|
#
|
|
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2012-09-30 08:50:25 +02:00
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: user
|
2015-06-15 21:53:30 +02:00
|
|
|
author: "Stephen Fromm (@sfromm)"
|
2013-11-28 03:23:03 +01:00
|
|
|
version_added: "0.2"
|
2012-09-30 08:50:25 +02:00
|
|
|
short_description: Manage user accounts
|
|
|
|
requirements: [ useradd, userdel, usermod ]
|
|
|
|
description:
|
|
|
|
- Manage user accounts and user attributes.
|
|
|
|
options:
|
|
|
|
name:
|
|
|
|
required: true
|
2012-10-25 14:03:13 +02:00
|
|
|
aliases: [ "user" ]
|
2012-09-30 08:50:25 +02:00
|
|
|
description:
|
|
|
|
- Name of the user to create, remove or modify.
|
|
|
|
comment:
|
|
|
|
required: false
|
|
|
|
description:
|
|
|
|
- Optionally sets the description (aka I(GECOS)) of user account.
|
|
|
|
uid:
|
|
|
|
required: false
|
|
|
|
description:
|
|
|
|
- Optionally sets the I(UID) of the user.
|
2013-03-14 10:55:44 +01:00
|
|
|
non_unique:
|
2013-03-12 15:35:54 +01:00
|
|
|
required: false
|
|
|
|
default: "no"
|
|
|
|
choices: [ "yes", "no" ]
|
|
|
|
description:
|
|
|
|
- Optionally when used with the -u option, this option allows to
|
|
|
|
change the user ID to a non-unique value.
|
2013-03-30 20:44:34 +01:00
|
|
|
version_added: "1.1"
|
2012-09-30 08:50:25 +02:00
|
|
|
group:
|
|
|
|
required: false
|
|
|
|
description:
|
|
|
|
- Optionally sets the user's primary group (takes a group name).
|
|
|
|
groups:
|
|
|
|
required: false
|
|
|
|
description:
|
2013-02-08 00:50:02 +01:00
|
|
|
- Puts the user in this comma-delimited list of groups. When set to
|
|
|
|
the empty string ('groups='), the user is removed from all groups
|
|
|
|
except the primary group.
|
2012-09-30 08:50:25 +02:00
|
|
|
append:
|
|
|
|
required: false
|
2014-04-16 06:17:39 +02:00
|
|
|
default: "no"
|
|
|
|
choices: [ "yes", "no" ]
|
2012-09-30 08:50:25 +02:00
|
|
|
description:
|
2012-11-21 18:49:30 +01:00
|
|
|
- If C(yes), will only add groups, not set them to just the list
|
2012-09-30 08:50:25 +02:00
|
|
|
in I(groups).
|
|
|
|
shell:
|
|
|
|
required: false
|
|
|
|
description:
|
|
|
|
- Optionally set the user's shell.
|
|
|
|
home:
|
|
|
|
required: false
|
|
|
|
description:
|
|
|
|
- Optionally set the user's home directory.
|
|
|
|
password:
|
|
|
|
required: false
|
|
|
|
description:
|
|
|
|
- Optionally set the user's password to this crypted value. See
|
|
|
|
the user example in the github examples directory for what this looks
|
2015-04-09 21:44:00 +02:00
|
|
|
like in a playbook. See U(http://docs.ansible.com/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module)
|
|
|
|
for details on various ways to generate these password values.
|
2014-08-28 22:47:11 +02:00
|
|
|
Note on Darwin system, this value has to be cleartext.
|
|
|
|
Beware of security issues.
|
2012-09-30 08:50:25 +02:00
|
|
|
state:
|
|
|
|
required: false
|
|
|
|
default: "present"
|
2015-01-28 15:22:32 +01:00
|
|
|
choices: [ present, absent ]
|
2012-09-30 08:50:25 +02:00
|
|
|
description:
|
2015-01-28 15:22:32 +01:00
|
|
|
- Whether the account should exist or not, taking action if the state is different from what is stated.
|
2012-09-30 08:50:25 +02:00
|
|
|
createhome:
|
|
|
|
required: false
|
|
|
|
default: "yes"
|
2013-03-12 13:18:12 +01:00
|
|
|
choices: [ "yes", "no" ]
|
2012-09-30 08:50:25 +02:00
|
|
|
description:
|
2012-11-21 18:49:30 +01:00
|
|
|
- Unless set to C(no), a home directory will be made for the user
|
2013-10-17 02:08:41 +02:00
|
|
|
when the account is created or if the home directory does not
|
2015-01-28 15:22:32 +01:00
|
|
|
exist.
|
2014-01-29 06:35:21 +01:00
|
|
|
move_home:
|
|
|
|
required: false
|
|
|
|
default: "no"
|
|
|
|
choices: [ "yes", "no" ]
|
|
|
|
description:
|
|
|
|
- If set to C(yes) when used with C(home=), attempt to move the
|
|
|
|
user's home directory to the specified directory if it isn't there
|
|
|
|
already.
|
2012-09-30 08:50:25 +02:00
|
|
|
system:
|
|
|
|
required: false
|
|
|
|
default: "no"
|
2013-03-12 13:18:12 +01:00
|
|
|
choices: [ "yes", "no" ]
|
2012-09-30 08:50:25 +02:00
|
|
|
description:
|
2012-11-21 18:49:30 +01:00
|
|
|
- When creating an account, setting this to C(yes) makes the user a
|
2012-09-30 08:50:25 +02:00
|
|
|
system account. This setting cannot be changed on existing users.
|
|
|
|
force:
|
|
|
|
required: false
|
|
|
|
default: "no"
|
2013-03-12 13:18:12 +01:00
|
|
|
choices: [ "yes", "no" ]
|
2012-09-30 08:50:25 +02:00
|
|
|
description:
|
2012-11-21 18:49:30 +01:00
|
|
|
- When used with C(state=absent), behavior is as with
|
|
|
|
C(userdel --force).
|
2013-06-01 02:53:37 +02:00
|
|
|
login_class:
|
|
|
|
required: false
|
|
|
|
description:
|
|
|
|
- Optionally sets the user's login class for FreeBSD, OpenBSD and NetBSD systems.
|
2012-09-30 08:50:25 +02:00
|
|
|
remove:
|
|
|
|
required: false
|
|
|
|
default: "no"
|
2013-03-12 13:18:12 +01:00
|
|
|
choices: [ "yes", "no" ]
|
2012-09-30 08:50:25 +02:00
|
|
|
description:
|
2012-11-21 18:49:30 +01:00
|
|
|
- When used with C(state=absent), behavior is as with
|
|
|
|
C(userdel --remove).
|
2012-11-03 23:38:05 +01:00
|
|
|
generate_ssh_key:
|
2012-10-20 07:00:31 +02:00
|
|
|
required: false
|
2012-11-03 23:38:05 +01:00
|
|
|
default: "no"
|
2013-03-12 13:18:12 +01:00
|
|
|
choices: [ "yes", "no" ]
|
2012-10-24 07:35:56 +02:00
|
|
|
version_added: "0.9"
|
2012-10-20 07:00:31 +02:00
|
|
|
description:
|
|
|
|
- Whether to generate a SSH key for the user in question.
|
|
|
|
This will B(not) overwrite an existing SSH key.
|
|
|
|
ssh_key_bits:
|
|
|
|
required: false
|
|
|
|
default: 2048
|
2012-10-24 07:35:56 +02:00
|
|
|
version_added: "0.9"
|
2012-10-20 07:00:31 +02:00
|
|
|
description:
|
|
|
|
- Optionally specify number of bits in SSH key to create.
|
|
|
|
ssh_key_type:
|
|
|
|
required: false
|
|
|
|
default: rsa
|
2012-10-24 07:35:56 +02:00
|
|
|
version_added: "0.9"
|
2012-10-20 07:00:31 +02:00
|
|
|
description:
|
2012-10-25 20:55:09 +02:00
|
|
|
- Optionally specify the type of SSH key to generate.
|
2012-10-20 07:00:31 +02:00
|
|
|
Available SSH key types will depend on implementation
|
2012-10-24 07:35:56 +02:00
|
|
|
present on target host.
|
2012-10-20 07:00:31 +02:00
|
|
|
ssh_key_file:
|
|
|
|
required: false
|
2015-01-07 17:45:55 +01:00
|
|
|
default: .ssh/id_rsa
|
2012-10-24 07:35:56 +02:00
|
|
|
version_added: "0.9"
|
2012-10-20 07:00:31 +02:00
|
|
|
description:
|
2015-01-07 17:45:55 +01:00
|
|
|
- Optionally specify the SSH key filename. If this is a relative
|
|
|
|
filename then it will be relative to the user's home directory.
|
2012-10-20 07:00:31 +02:00
|
|
|
ssh_key_comment:
|
|
|
|
required: false
|
2014-09-30 00:07:41 +02:00
|
|
|
default: ansible-generated on $HOSTNAME
|
2012-10-24 07:35:56 +02:00
|
|
|
version_added: "0.9"
|
2012-10-20 07:00:31 +02:00
|
|
|
description:
|
|
|
|
- Optionally define the comment for the SSH key.
|
|
|
|
ssh_key_passphrase:
|
|
|
|
required: false
|
2012-10-24 07:35:56 +02:00
|
|
|
version_added: "0.9"
|
2012-10-20 07:00:31 +02:00
|
|
|
description:
|
|
|
|
- Set a passphrase for the SSH key. If no
|
|
|
|
passphrase is provided, the SSH key will default to
|
2012-10-24 07:35:56 +02:00
|
|
|
having no passphrase.
|
2013-06-17 04:47:29 +02:00
|
|
|
update_password:
|
|
|
|
required: false
|
|
|
|
default: always
|
2013-07-01 02:32:05 +02:00
|
|
|
choices: ['always', 'on_create']
|
2013-06-17 04:47:29 +02:00
|
|
|
version_added: "1.3"
|
|
|
|
description:
|
2013-07-01 02:32:05 +02:00
|
|
|
- C(always) will update passwords if they differ. C(on_create) will only set the password for newly created users.
|
2015-01-28 15:22:32 +01:00
|
|
|
expires:
|
|
|
|
version_added: "1.9"
|
|
|
|
required: false
|
|
|
|
default: "None"
|
|
|
|
description:
|
|
|
|
- An expiry time for the user in epoch, it will be ignored on platforms that do not support this.
|
|
|
|
Currently supported on Linux and FreeBSD.
|
2013-06-14 11:53:43 +02:00
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
# Add the user 'johnd' with a specific uid and a primary group of 'admin'
|
2014-06-02 14:32:52 +02:00
|
|
|
- user: name=johnd comment="John Doe" uid=1040 group=admin
|
2013-06-14 11:53:43 +02:00
|
|
|
|
2014-04-11 17:14:40 +02:00
|
|
|
# Add the user 'james' with a bash shell, appending the group 'admins' and 'developers' to the user's groups
|
|
|
|
- user: name=james shell=/bin/bash groups=admins,developers append=yes
|
|
|
|
|
2013-06-14 11:53:43 +02:00
|
|
|
# Remove the user 'johnd'
|
|
|
|
- user: name=johnd state=absent remove=yes
|
|
|
|
|
2015-01-07 17:45:55 +01:00
|
|
|
# Create a 2048-bit SSH key for user jsmith in ~jsmith/.ssh/id_rsa
|
|
|
|
- user: name=jsmith generate_ssh_key=yes ssh_key_bits=2048 ssh_key_file=.ssh/id_rsa
|
2015-01-28 15:22:32 +01:00
|
|
|
|
2015-02-11 03:18:16 +01:00
|
|
|
# added a consultant whose account you want to expire
|
2015-01-28 15:22:32 +01:00
|
|
|
- user: name=james18 shell=/bin/zsh groups=developers expires=1422403387
|
2012-09-30 08:50:25 +02:00
|
|
|
'''
|
|
|
|
|
2012-08-08 08:57:17 +02:00
|
|
|
import os
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
import pwd
|
2012-03-27 22:43:36 +02:00
|
|
|
import grp
|
2012-10-29 23:00:58 +01:00
|
|
|
import syslog
|
|
|
|
import platform
|
2014-09-30 00:07:41 +02:00
|
|
|
import socket
|
2015-01-28 15:22:32 +01:00
|
|
|
import time
|
2012-11-03 23:38:05 +01:00
|
|
|
|
2012-05-08 19:40:44 +02:00
|
|
|
try:
|
|
|
|
import spwd
|
|
|
|
HAVE_SPWD=True
|
|
|
|
except:
|
|
|
|
HAVE_SPWD=False
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
class User(object):
|
2012-11-01 20:16:54 +01:00
|
|
|
"""
|
2012-11-03 23:38:05 +01:00
|
|
|
This is a generic User manipulation class that is subclassed
|
2013-05-31 01:18:18 +02:00
|
|
|
based on platform.
|
|
|
|
|
2012-11-01 20:16:54 +01:00
|
|
|
A subclass may wish to override the following action methods:-
|
|
|
|
- create_user()
|
|
|
|
- remove_user()
|
|
|
|
- modify_user()
|
|
|
|
- ssh_key_gen()
|
|
|
|
- ssh_key_fingerprint()
|
|
|
|
- user_exists()
|
|
|
|
|
|
|
|
All subclasses MUST define platform and distribution (which may be None).
|
|
|
|
"""
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
platform = 'Generic'
|
|
|
|
distribution = None
|
|
|
|
SHADOWFILE = '/etc/shadow'
|
2015-05-07 22:55:28 +02:00
|
|
|
DATE_FORMAT = '%Y-%m-%d'
|
2012-10-29 23:00:58 +01:00
|
|
|
|
2012-11-03 23:38:05 +01:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
return load_platform_subclass(User, args, kwargs)
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
def __init__(self, module):
|
|
|
|
self.module = module
|
|
|
|
self.state = module.params['state']
|
|
|
|
self.name = module.params['name']
|
|
|
|
self.uid = module.params['uid']
|
2013-03-14 10:55:44 +01:00
|
|
|
self.non_unique = module.params['non_unique']
|
2012-10-29 23:00:58 +01:00
|
|
|
self.group = module.params['group']
|
|
|
|
self.groups = module.params['groups']
|
|
|
|
self.comment = module.params['comment']
|
|
|
|
self.shell = module.params['shell']
|
|
|
|
self.password = module.params['password']
|
2013-02-23 19:59:52 +01:00
|
|
|
self.force = module.params['force']
|
|
|
|
self.remove = module.params['remove']
|
|
|
|
self.createhome = module.params['createhome']
|
2014-01-29 06:35:21 +01:00
|
|
|
self.move_home = module.params['move_home']
|
2013-02-23 19:59:52 +01:00
|
|
|
self.system = module.params['system']
|
2013-06-01 02:53:37 +02:00
|
|
|
self.login_class = module.params['login_class']
|
2013-02-23 19:59:52 +01:00
|
|
|
self.append = module.params['append']
|
|
|
|
self.sshkeygen = module.params['generate_ssh_key']
|
2012-10-29 23:00:58 +01:00
|
|
|
self.ssh_bits = module.params['ssh_key_bits']
|
|
|
|
self.ssh_type = module.params['ssh_key_type']
|
|
|
|
self.ssh_comment = module.params['ssh_key_comment']
|
|
|
|
self.ssh_passphrase = module.params['ssh_key_passphrase']
|
2013-06-17 04:47:29 +02:00
|
|
|
self.update_password = module.params['update_password']
|
2015-07-19 05:28:41 +02:00
|
|
|
self.home = None
|
2015-01-28 15:22:32 +01:00
|
|
|
self.expires = None
|
|
|
|
|
2015-07-15 12:00:23 +02:00
|
|
|
if module.params['home'] is not None:
|
|
|
|
self.home = os.path.expanduser(module.params['home'])
|
|
|
|
|
2015-01-28 15:22:32 +01:00
|
|
|
if module.params['expires']:
|
|
|
|
try:
|
|
|
|
self.expires = time.gmtime(module.params['expires'])
|
|
|
|
except Exception,e:
|
|
|
|
module.fail_json("Invalid expires time %s: %s" %(self.expires, str(e)))
|
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if module.params['ssh_key_file'] is not None:
|
|
|
|
self.ssh_file = module.params['ssh_key_file']
|
|
|
|
else:
|
|
|
|
self.ssh_file = os.path.join('.ssh', 'id_%s' % self.ssh_type)
|
2012-10-29 23:04:06 +01:00
|
|
|
|
|
|
|
# select whether we dump additional debug info through syslog
|
|
|
|
self.syslogging = False
|
2012-10-29 23:00:58 +01:00
|
|
|
|
2015-01-28 15:22:32 +01:00
|
|
|
|
2015-01-12 19:08:22 +01:00
|
|
|
def execute_command(self, cmd, use_unsafe_shell=False, data=None):
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.syslogging:
|
|
|
|
syslog.openlog('ansible-%s' % os.path.basename(__file__))
|
|
|
|
syslog.syslog(syslog.LOG_NOTICE, 'Command %s' % '|'.join(cmd))
|
|
|
|
|
2015-01-12 19:08:22 +01:00
|
|
|
return self.module.run_command(cmd, use_unsafe_shell=use_unsafe_shell, data=data)
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
def remove_user_userdel(self):
|
|
|
|
cmd = [self.module.get_bin_path('userdel', True)]
|
|
|
|
if self.force:
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
cmd.append('-f')
|
2013-12-05 23:07:24 +01:00
|
|
|
if self.remove:
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
cmd.append('-r')
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append(self.name)
|
|
|
|
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def create_user_useradd(self, command_name='useradd'):
|
|
|
|
cmd = [self.module.get_bin_path(command_name, True)]
|
|
|
|
|
|
|
|
if self.uid is not None:
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
cmd.append('-u')
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-03-12 15:35:54 +01:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.group is not None:
|
2012-11-06 18:05:25 +01:00
|
|
|
if not self.group_exists(self.group):
|
2012-10-29 23:00:58 +01:00
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
2012-03-28 23:12:35 +02:00
|
|
|
cmd.append('-g')
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append(self.group)
|
2014-02-28 18:40:19 +01:00
|
|
|
elif self.group_exists(self.name):
|
|
|
|
# use the -N option (no user group) if a group already
|
|
|
|
# exists with the same name as the user to prevent
|
|
|
|
# errors from useradd trying to create a group when
|
|
|
|
# USERGROUPS_ENAB is set in /etc/login.defs.
|
2014-11-17 16:31:54 +01:00
|
|
|
if os.path.exists('/etc/redhat-release'):
|
|
|
|
dist = platform.dist()
|
|
|
|
major_release = int(dist[1].split('.')[0])
|
|
|
|
if major_release <= 5:
|
|
|
|
cmd.append('-n')
|
|
|
|
else:
|
|
|
|
cmd.append('-N')
|
|
|
|
else:
|
|
|
|
cmd.append('-N')
|
2012-10-29 23:00:58 +01:00
|
|
|
|
2013-08-02 03:19:11 +02:00
|
|
|
if self.groups is not None and len(self.groups):
|
2013-05-31 01:18:18 +02:00
|
|
|
groups = self.get_groups_set()
|
2012-03-28 23:12:35 +02:00
|
|
|
cmd.append('-G')
|
2013-05-31 01:18:18 +02:00
|
|
|
cmd.append(','.join(groups))
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
if self.comment is not None:
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
cmd.append('-c')
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None:
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
cmd.append('-d')
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None:
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
cmd.append('-s')
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append(self.shell)
|
|
|
|
|
2015-01-28 15:22:32 +01:00
|
|
|
if self.expires:
|
2014-09-30 00:42:28 +02:00
|
|
|
cmd.append('--expiredate')
|
2015-01-28 15:22:32 +01:00
|
|
|
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
2014-09-30 00:42:28 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.password is not None:
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
cmd.append('-p')
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append(self.password)
|
|
|
|
|
|
|
|
if self.createhome:
|
|
|
|
cmd.append('-m')
|
|
|
|
else:
|
|
|
|
cmd.append('-M')
|
|
|
|
|
|
|
|
if self.system:
|
2012-05-01 21:38:55 +02:00
|
|
|
cmd.append('-r')
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
|
2013-10-11 15:10:35 +02:00
|
|
|
def _check_usermod_append(self):
|
|
|
|
# check if this version of usermod can append groups
|
2014-04-28 12:29:57 +02:00
|
|
|
usermod_path = self.module.get_bin_path('usermod', True)
|
2013-10-11 15:10:35 +02:00
|
|
|
|
2014-04-28 12:29:57 +02:00
|
|
|
# for some reason, usermod --help cannot be used by non root
|
|
|
|
# on RH/Fedora, due to lack of execute bit for others
|
|
|
|
if not os.access(usermod_path, os.X_OK):
|
|
|
|
return False
|
|
|
|
|
|
|
|
cmd = [usermod_path]
|
2013-10-11 15:10:35 +02:00
|
|
|
cmd.append('--help')
|
|
|
|
rc, data1, data2 = self.execute_command(cmd)
|
|
|
|
helpout = data1 + data2
|
|
|
|
|
|
|
|
# check if --append exists
|
|
|
|
lines = helpout.split('\n')
|
|
|
|
for line in lines:
|
|
|
|
if line.strip().startswith('-a, --append'):
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
def modify_user_usermod(self):
|
|
|
|
cmd = [self.module.get_bin_path('usermod', True)]
|
|
|
|
info = self.user_info()
|
2013-10-11 15:10:35 +02:00
|
|
|
has_append = self._check_usermod_append()
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
if self.uid is not None and info[2] != int(self.uid):
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-03-12 15:35:54 +01:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
2012-11-12 22:13:51 +01:00
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
2012-10-29 23:00:58 +01:00
|
|
|
ginfo = self.group_info(self.group)
|
2012-03-28 23:12:35 +02:00
|
|
|
if info[3] != ginfo[2]:
|
|
|
|
cmd.append('-g')
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
|
|
|
current_groups = self.user_group_membership()
|
2012-05-08 01:43:51 +02:00
|
|
|
groups_need_mod = False
|
2013-02-08 00:50:02 +01:00
|
|
|
groups = []
|
2012-05-08 01:43:51 +02:00
|
|
|
|
2013-02-08 00:50:02 +01:00
|
|
|
if self.groups == '':
|
|
|
|
if current_groups and not self.append:
|
2012-08-11 18:35:58 +02:00
|
|
|
groups_need_mod = True
|
2013-02-08 00:50:02 +01:00
|
|
|
else:
|
2013-11-25 20:56:46 +01:00
|
|
|
groups = self.get_groups_set(remove_existing=False)
|
2013-05-31 01:18:18 +02:00
|
|
|
group_diff = set(current_groups).symmetric_difference(groups)
|
2013-02-08 00:50:02 +01:00
|
|
|
|
|
|
|
if group_diff:
|
|
|
|
if self.append:
|
|
|
|
for g in groups:
|
|
|
|
if g in group_diff:
|
2013-10-11 15:10:35 +02:00
|
|
|
if has_append:
|
|
|
|
cmd.append('-a')
|
2013-02-08 00:50:02 +01:00
|
|
|
groups_need_mod = True
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
groups_need_mod = True
|
2012-05-08 01:43:51 +02:00
|
|
|
|
|
|
|
if groups_need_mod:
|
2013-10-11 15:10:35 +02:00
|
|
|
if self.append and not has_append:
|
|
|
|
cmd.append('-A')
|
|
|
|
cmd.append(','.join(group_diff))
|
|
|
|
else:
|
|
|
|
cmd.append('-G')
|
|
|
|
cmd.append(','.join(groups))
|
|
|
|
|
2012-05-08 01:43:51 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.comment is not None and info[4] != self.comment:
|
2013-02-17 23:26:54 +01:00
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
if self.home is not None and info[5] != self.home:
|
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
2014-02-25 06:38:51 +01:00
|
|
|
if self.move_home:
|
|
|
|
cmd.append('-m')
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
if self.shell is not None and info[6] != self.shell:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
2015-01-28 15:22:32 +01:00
|
|
|
if self.expires:
|
2014-09-30 00:42:28 +02:00
|
|
|
cmd.append('--expiredate')
|
2015-01-28 15:22:32 +01:00
|
|
|
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
2014-09-30 00:42:28 +02:00
|
|
|
|
2013-06-17 04:47:29 +02:00
|
|
|
if self.update_password == 'always' and self.password is not None and info[1] != self.password:
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append('-p')
|
|
|
|
cmd.append(self.password)
|
|
|
|
|
|
|
|
# skip if no changes to be made
|
|
|
|
if len(cmd) == 1:
|
|
|
|
return (None, '', '')
|
2013-02-18 13:23:20 +01:00
|
|
|
elif self.module.check_mode:
|
2013-03-27 03:04:04 +01:00
|
|
|
return (0, '', '')
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def group_exists(self,group):
|
|
|
|
try:
|
2014-12-12 21:08:03 +01:00
|
|
|
# Try group as a gid first
|
|
|
|
grp.getgrgid(int(group))
|
|
|
|
return True
|
|
|
|
except (ValueError, KeyError):
|
|
|
|
try:
|
|
|
|
grp.getgrnam(group)
|
|
|
|
return True
|
|
|
|
except KeyError:
|
|
|
|
return False
|
2012-10-29 23:00:58 +01:00
|
|
|
|
2014-12-12 21:08:03 +01:00
|
|
|
def group_info(self, group):
|
2012-10-29 23:00:58 +01:00
|
|
|
if not self.group_exists(group):
|
|
|
|
return False
|
2014-12-12 21:08:03 +01:00
|
|
|
try:
|
|
|
|
# Try group as a gid first
|
|
|
|
return list(grp.getgrgid(int(group)))
|
|
|
|
except (ValueError, KeyError):
|
2012-10-29 23:00:58 +01:00
|
|
|
return list(grp.getgrnam(group))
|
|
|
|
|
2013-11-25 20:56:46 +01:00
|
|
|
def get_groups_set(self, remove_existing=True):
|
2013-05-31 01:18:18 +02:00
|
|
|
if self.groups is None:
|
|
|
|
return None
|
|
|
|
info = self.user_info()
|
2014-02-10 22:08:53 +01:00
|
|
|
groups = set(filter(None, self.groups.split(',')))
|
2013-05-31 01:18:18 +02:00
|
|
|
for g in set(groups):
|
|
|
|
if not self.group_exists(g):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % (g))
|
2013-11-25 20:56:46 +01:00
|
|
|
if info and remove_existing and self.group_info(g)[2] == info[3]:
|
2013-05-31 01:18:18 +02:00
|
|
|
groups.remove(g)
|
|
|
|
return groups
|
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
def user_group_membership(self):
|
|
|
|
groups = []
|
|
|
|
info = self.get_pwd_info()
|
|
|
|
for group in grp.getgrall():
|
2013-05-31 01:18:18 +02:00
|
|
|
if self.name in group.gr_mem and not info[3] == group.gr_gid:
|
2012-10-29 23:00:58 +01:00
|
|
|
groups.append(group[0])
|
|
|
|
return groups
|
|
|
|
|
|
|
|
def user_exists(self):
|
2012-08-08 08:57:17 +02:00
|
|
|
try:
|
2012-10-29 23:00:58 +01:00
|
|
|
if pwd.getpwnam(self.name):
|
|
|
|
return True
|
2012-08-08 08:57:17 +02:00
|
|
|
except KeyError:
|
2012-10-29 23:00:58 +01:00
|
|
|
return False
|
|
|
|
|
|
|
|
def get_pwd_info(self):
|
|
|
|
if not self.user_exists():
|
|
|
|
return False
|
|
|
|
return list(pwd.getpwnam(self.name))
|
|
|
|
|
|
|
|
def user_info(self):
|
|
|
|
if not self.user_exists():
|
|
|
|
return False
|
|
|
|
info = self.get_pwd_info()
|
|
|
|
if len(info[1]) == 1 or len(info[1]) == 0:
|
|
|
|
info[1] = self.user_password()
|
|
|
|
return info
|
|
|
|
|
|
|
|
def user_password(self):
|
|
|
|
passwd = ''
|
|
|
|
if HAVE_SPWD:
|
|
|
|
try:
|
|
|
|
passwd = spwd.getspnam(self.name)[1]
|
|
|
|
except KeyError:
|
|
|
|
return passwd
|
2012-11-03 23:38:05 +01:00
|
|
|
if not self.user_exists():
|
2012-08-08 08:57:17 +02:00
|
|
|
return passwd
|
2015-04-06 20:05:11 +02:00
|
|
|
elif self.SHADOWFILE:
|
2012-10-29 23:00:58 +01:00
|
|
|
# Read shadow file for user's encrypted password string
|
2012-11-04 10:54:50 +01:00
|
|
|
if os.path.exists(self.SHADOWFILE) and os.access(self.SHADOWFILE, os.R_OK):
|
|
|
|
for line in open(self.SHADOWFILE).readlines():
|
2012-10-29 23:00:58 +01:00
|
|
|
if line.startswith('%s:' % self.name):
|
|
|
|
passwd = line.split(':')[1]
|
|
|
|
return passwd
|
|
|
|
|
|
|
|
def get_ssh_key_path(self):
|
2012-11-03 23:38:05 +01:00
|
|
|
info = self.user_info()
|
2012-10-29 23:00:58 +01:00
|
|
|
if os.path.isabs(self.ssh_file):
|
|
|
|
ssh_key_file = self.ssh_file
|
|
|
|
else:
|
|
|
|
ssh_key_file = os.path.join(info[5], self.ssh_file)
|
|
|
|
return ssh_key_file
|
|
|
|
|
|
|
|
def ssh_key_gen(self):
|
2012-11-03 23:38:05 +01:00
|
|
|
info = self.user_info()
|
2012-10-29 23:00:58 +01:00
|
|
|
if not os.path.exists(info[5]):
|
|
|
|
return (1, '', 'User %s home directory does not exist' % self.name)
|
2012-11-03 23:38:05 +01:00
|
|
|
ssh_key_file = self.get_ssh_key_path()
|
2015-01-28 15:22:32 +01:00
|
|
|
ssh_dir = os.path.dirname(ssh_key_file)
|
2012-10-29 23:00:58 +01:00
|
|
|
if not os.path.exists(ssh_dir):
|
|
|
|
try:
|
|
|
|
os.mkdir(ssh_dir, 0700)
|
2012-11-04 10:44:38 +01:00
|
|
|
os.chown(ssh_dir, info[2], info[3])
|
2012-10-29 23:00:58 +01:00
|
|
|
except OSError, e:
|
|
|
|
return (1, '', 'Failed to create %s: %s' % (ssh_dir, str(e)))
|
|
|
|
if os.path.exists(ssh_key_file):
|
|
|
|
return (None, 'Key already exists', '')
|
|
|
|
cmd = [self.module.get_bin_path('ssh-keygen', True)]
|
|
|
|
cmd.append('-t')
|
|
|
|
cmd.append(self.ssh_type)
|
|
|
|
cmd.append('-b')
|
|
|
|
cmd.append(self.ssh_bits)
|
|
|
|
cmd.append('-C')
|
|
|
|
cmd.append(self.ssh_comment)
|
|
|
|
cmd.append('-f')
|
|
|
|
cmd.append(ssh_key_file)
|
|
|
|
cmd.append('-N')
|
|
|
|
if self.ssh_passphrase is not None:
|
|
|
|
cmd.append(self.ssh_passphrase)
|
|
|
|
else:
|
|
|
|
cmd.append('')
|
2012-10-20 07:00:31 +02:00
|
|
|
|
2012-11-04 10:47:30 +01:00
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
2012-11-03 23:38:05 +01:00
|
|
|
if rc == 0:
|
|
|
|
# If the keys were successfully created, we should be able
|
|
|
|
# to tweak ownership.
|
|
|
|
os.chown(ssh_key_file, info[2], info[3])
|
|
|
|
os.chown('%s.pub' % ssh_key_file, info[2], info[3])
|
|
|
|
return (rc, out, err)
|
2012-10-20 07:00:31 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
def ssh_key_fingerprint(self):
|
2012-11-03 23:38:05 +01:00
|
|
|
ssh_key_file = self.get_ssh_key_path()
|
2012-10-29 23:00:58 +01:00
|
|
|
if not os.path.exists(ssh_key_file):
|
|
|
|
return (1, 'SSH Key file %s does not exist' % ssh_key_file, '')
|
2012-11-03 23:38:05 +01:00
|
|
|
cmd = [ self.module.get_bin_path('ssh-keygen', True) ]
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append('-l')
|
|
|
|
cmd.append('-f')
|
|
|
|
cmd.append(ssh_key_file)
|
2012-10-20 07:00:31 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
2013-04-13 16:10:58 +02:00
|
|
|
def get_ssh_public_key(self):
|
|
|
|
ssh_public_key_file = '%s.pub' % self.get_ssh_key_path()
|
|
|
|
try:
|
|
|
|
f = open(ssh_public_key_file)
|
|
|
|
ssh_public_key = f.read().strip()
|
|
|
|
f.close()
|
|
|
|
except IOError:
|
|
|
|
return None
|
|
|
|
return ssh_public_key
|
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
def create_user(self):
|
|
|
|
# by default we use the create_user_useradd method
|
|
|
|
return self.create_user_useradd()
|
|
|
|
|
|
|
|
def remove_user(self):
|
|
|
|
# by default we use the remove_user_userdel method
|
|
|
|
return self.remove_user_userdel()
|
|
|
|
|
|
|
|
def modify_user(self):
|
|
|
|
# by default we use the modify_user_usermod method
|
|
|
|
return self.modify_user_usermod()
|
|
|
|
|
2013-10-17 02:08:41 +02:00
|
|
|
def create_homedir(self, path):
|
|
|
|
if not os.path.exists(path):
|
|
|
|
# use /etc/skel if possible
|
|
|
|
if os.path.exists('/etc/skel'):
|
|
|
|
try:
|
|
|
|
shutil.copytree('/etc/skel', path, symlinks=True)
|
|
|
|
except OSError, e:
|
|
|
|
self.module.exit_json(failed=True, msg="%s" % e)
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
os.makedirs(path)
|
|
|
|
except OSError, e:
|
|
|
|
self.module.exit_json(failed=True, msg="%s" % e)
|
|
|
|
|
|
|
|
def chown_homedir(self, uid, gid, path):
|
|
|
|
try:
|
|
|
|
os.chown(path, uid, gid)
|
|
|
|
for root, dirs, files in os.walk(path):
|
|
|
|
for d in dirs:
|
|
|
|
os.chown(path, uid, gid)
|
|
|
|
for f in files:
|
|
|
|
os.chown(os.path.join(root, f), uid, gid)
|
|
|
|
except OSError, e:
|
|
|
|
self.module.exit_json(failed=True, msg="%s" % e)
|
2015-01-28 15:22:32 +01:00
|
|
|
|
2013-10-17 02:08:41 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
# ===========================================
|
|
|
|
|
2012-11-03 23:38:05 +01:00
|
|
|
class FreeBsdUser(User):
|
2012-11-01 20:16:54 +01:00
|
|
|
"""
|
|
|
|
This is a FreeBSD User manipulation class - it uses the pw command
|
|
|
|
to manipulate the user database, followed by the chpass command
|
|
|
|
to change the password.
|
2013-06-01 02:53:37 +02:00
|
|
|
|
2012-11-01 20:16:54 +01:00
|
|
|
This overrides the following methods from the generic class:-
|
|
|
|
- create_user()
|
|
|
|
- remove_user()
|
|
|
|
- modify_user()
|
|
|
|
"""
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
platform = 'FreeBSD'
|
|
|
|
distribution = None
|
|
|
|
SHADOWFILE = '/etc/master.passwd'
|
|
|
|
|
|
|
|
def remove_user(self):
|
2013-02-18 01:48:02 +01:00
|
|
|
cmd = [
|
|
|
|
self.module.get_bin_path('pw', True),
|
|
|
|
'userdel',
|
|
|
|
'-n',
|
|
|
|
self.name
|
|
|
|
]
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.remove:
|
|
|
|
cmd.append('-r')
|
|
|
|
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def create_user(self):
|
2013-02-18 01:48:02 +01:00
|
|
|
cmd = [
|
|
|
|
self.module.get_bin_path('pw', True),
|
|
|
|
'useradd',
|
|
|
|
'-n',
|
2013-06-01 02:53:37 +02:00
|
|
|
self.name,
|
2013-02-18 01:48:02 +01:00
|
|
|
]
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
if self.uid is not None:
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-03-12 15:35:54 +01:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.comment is not None:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None:
|
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.group is not None:
|
2013-05-08 11:03:18 +02:00
|
|
|
if not self.group_exists(self.group):
|
2012-10-29 23:00:58 +01:00
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
2013-05-31 01:18:18 +02:00
|
|
|
groups = self.get_groups_set()
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append('-G')
|
2013-05-31 01:18:18 +02:00
|
|
|
cmd.append(','.join(groups))
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
if self.createhome:
|
|
|
|
cmd.append('-m')
|
|
|
|
|
|
|
|
if self.shell is not None:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
2013-06-01 02:53:37 +02:00
|
|
|
if self.login_class is not None:
|
|
|
|
cmd.append('-L')
|
|
|
|
cmd.append(self.login_class)
|
|
|
|
|
2015-01-28 15:22:32 +01:00
|
|
|
if self.expires:
|
|
|
|
days =( time.mktime(self.expires) - time.time() ) / 86400
|
2014-09-30 00:42:28 +02:00
|
|
|
cmd.append('-e')
|
2015-01-28 15:22:32 +01:00
|
|
|
cmd.append(str(int(days)))
|
2014-09-30 00:42:28 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
# system cannot be handled currently - should we error if its requested?
|
|
|
|
# create the user
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc is not None and rc != 0:
|
2012-11-10 09:29:45 +01:00
|
|
|
self.module.fail_json(name=self.name, msg=err, rc=rc)
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
# we have to set the password in a second command
|
|
|
|
if self.password is not None:
|
2013-02-18 01:48:02 +01:00
|
|
|
cmd = [
|
|
|
|
self.module.get_bin_path('chpass', True),
|
|
|
|
'-p',
|
|
|
|
self.password,
|
2015-01-28 15:22:32 +01:00
|
|
|
self.name
|
2013-02-18 01:48:02 +01:00
|
|
|
]
|
2012-10-29 23:00:58 +01:00
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
return (rc, out, err)
|
|
|
|
|
|
|
|
def modify_user(self):
|
2013-02-18 01:48:02 +01:00
|
|
|
cmd = [
|
|
|
|
self.module.get_bin_path('pw', True),
|
|
|
|
'usermod',
|
|
|
|
'-n',
|
2015-01-28 15:22:32 +01:00
|
|
|
self.name
|
2013-02-18 01:48:02 +01:00
|
|
|
]
|
2012-11-04 13:09:19 +01:00
|
|
|
cmd_len = len(cmd)
|
2012-10-29 23:00:58 +01:00
|
|
|
info = self.user_info()
|
|
|
|
|
|
|
|
if self.uid is not None and info[2] != int(self.uid):
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-03-12 15:35:54 +01:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.comment is not None and info[4] != self.comment:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None and info[5] != self.home:
|
2014-01-29 06:35:21 +01:00
|
|
|
if self.move_home:
|
|
|
|
cmd.append('-m')
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.group is not None:
|
2013-05-08 11:03:18 +02:00
|
|
|
if not self.group_exists(self.group):
|
2012-10-29 23:00:58 +01:00
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
ginfo = self.group_info(self.group)
|
|
|
|
if info[3] != ginfo[2]:
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
2012-10-20 07:00:31 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.shell is not None and info[6] != self.shell:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
2013-05-31 01:18:18 +02:00
|
|
|
|
2013-06-01 02:53:37 +02:00
|
|
|
if self.login_class is not None:
|
2015-02-27 12:39:18 +01:00
|
|
|
# find current login class
|
|
|
|
user_login_class = None
|
|
|
|
if os.path.exists(self.SHADOWFILE) and os.access(self.SHADOWFILE, os.R_OK):
|
|
|
|
for line in open(self.SHADOWFILE).readlines():
|
|
|
|
if line.startswith('%s:' % self.name):
|
|
|
|
user_login_class = line.split(':')[4]
|
|
|
|
|
|
|
|
# act only if login_class change
|
|
|
|
if self.login_class != user_login_class:
|
|
|
|
cmd.append('-L')
|
|
|
|
cmd.append(self.login_class)
|
2013-06-01 02:53:37 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if self.groups is not None:
|
|
|
|
current_groups = self.user_group_membership()
|
2013-05-31 01:18:18 +02:00
|
|
|
groups = self.get_groups_set()
|
2012-10-20 07:00:31 +02:00
|
|
|
|
2013-05-31 01:18:18 +02:00
|
|
|
group_diff = set(current_groups).symmetric_difference(groups)
|
2012-10-29 23:00:58 +01:00
|
|
|
groups_need_mod = False
|
2012-10-20 07:00:31 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if group_diff:
|
|
|
|
if self.append:
|
|
|
|
for g in groups:
|
|
|
|
if g in group_diff:
|
|
|
|
groups_need_mod = True
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
groups_need_mod = True
|
|
|
|
|
|
|
|
if groups_need_mod:
|
2012-11-04 13:09:19 +01:00
|
|
|
cmd.append('-G')
|
2012-10-29 23:00:58 +01:00
|
|
|
new_groups = groups
|
|
|
|
if self.append:
|
2013-10-17 17:02:23 +02:00
|
|
|
new_groups = groups | set(current_groups)
|
2012-10-29 23:00:58 +01:00
|
|
|
cmd.append(','.join(new_groups))
|
|
|
|
|
2015-01-28 15:22:32 +01:00
|
|
|
if self.expires:
|
|
|
|
days = ( time.mktime(self.expires) - time.time() ) / 86400
|
2014-09-30 00:42:28 +02:00
|
|
|
cmd.append('-e')
|
2015-01-28 15:22:32 +01:00
|
|
|
cmd.append(str(int(days)))
|
2014-09-30 00:42:28 +02:00
|
|
|
|
2012-11-04 13:09:19 +01:00
|
|
|
# modify the user if cmd will do anything
|
|
|
|
if cmd_len != len(cmd):
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc is not None and rc != 0:
|
2012-11-10 09:29:45 +01:00
|
|
|
self.module.fail_json(name=self.name, msg=err, rc=rc)
|
2012-11-04 13:09:19 +01:00
|
|
|
else:
|
|
|
|
(rc, out, err) = (None, '', '')
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
# we have to set the password in a second command
|
2013-06-17 04:47:29 +02:00
|
|
|
if self.update_password == 'always' and self.password is not None and info[1] != self.password:
|
2013-02-18 01:48:02 +01:00
|
|
|
cmd = [
|
|
|
|
self.module.get_bin_path('chpass', True),
|
|
|
|
'-p',
|
|
|
|
self.password,
|
|
|
|
self.name
|
|
|
|
]
|
2012-10-29 23:00:58 +01:00
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
return (rc, out, err)
|
2012-11-09 04:48:00 +01:00
|
|
|
|
|
|
|
# ===========================================
|
|
|
|
|
2013-06-01 02:53:37 +02:00
|
|
|
class OpenBSDUser(User):
|
|
|
|
"""
|
|
|
|
This is a OpenBSD User manipulation class.
|
|
|
|
Main differences are that OpenBSD:-
|
|
|
|
- has no concept of "system" account.
|
|
|
|
- has no force delete user
|
|
|
|
|
|
|
|
This overrides the following methods from the generic class:-
|
|
|
|
- create_user()
|
|
|
|
- remove_user()
|
|
|
|
- modify_user()
|
|
|
|
"""
|
|
|
|
|
|
|
|
platform = 'OpenBSD'
|
|
|
|
distribution = None
|
|
|
|
SHADOWFILE = '/etc/master.passwd'
|
|
|
|
|
|
|
|
def create_user(self):
|
|
|
|
cmd = [self.module.get_bin_path('useradd', True)]
|
|
|
|
|
|
|
|
if self.uid is not None:
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-06-01 02:53:37 +02:00
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
|
|
|
groups = self.get_groups_set()
|
|
|
|
cmd.append('-G')
|
|
|
|
cmd.append(','.join(groups))
|
|
|
|
|
|
|
|
if self.comment is not None:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None:
|
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
|
|
|
if self.login_class is not None:
|
|
|
|
cmd.append('-L')
|
|
|
|
cmd.append(self.login_class)
|
|
|
|
|
2015-02-26 15:10:28 +01:00
|
|
|
if self.password is not None and self.password != '*':
|
2013-06-01 02:53:37 +02:00
|
|
|
cmd.append('-p')
|
|
|
|
cmd.append(self.password)
|
|
|
|
|
|
|
|
if self.createhome:
|
|
|
|
cmd.append('-m')
|
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def remove_user_userdel(self):
|
|
|
|
cmd = [self.module.get_bin_path('userdel', True)]
|
|
|
|
if self.remove:
|
|
|
|
cmd.append('-r')
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def modify_user(self):
|
|
|
|
cmd = [self.module.get_bin_path('usermod', True)]
|
|
|
|
info = self.user_info()
|
|
|
|
|
|
|
|
if self.uid is not None and info[2] != int(self.uid):
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-06-01 02:53:37 +02:00
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
ginfo = self.group_info(self.group)
|
|
|
|
if info[3] != ginfo[2]:
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
|
|
|
current_groups = self.user_group_membership()
|
|
|
|
groups_need_mod = False
|
|
|
|
groups_option = '-G'
|
|
|
|
groups = []
|
|
|
|
|
|
|
|
if self.groups == '':
|
|
|
|
if current_groups and not self.append:
|
|
|
|
groups_need_mod = True
|
|
|
|
else:
|
|
|
|
groups = self.get_groups_set()
|
|
|
|
group_diff = set(current_groups).symmetric_difference(groups)
|
|
|
|
|
|
|
|
if group_diff:
|
|
|
|
if self.append:
|
|
|
|
for g in groups:
|
|
|
|
if g in group_diff:
|
|
|
|
groups_option = '-S'
|
|
|
|
groups_need_mod = True
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
groups_need_mod = True
|
|
|
|
|
|
|
|
if groups_need_mod:
|
|
|
|
cmd.append(groups_option)
|
|
|
|
cmd.append(','.join(groups))
|
|
|
|
|
|
|
|
if self.comment is not None and info[4] != self.comment:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None and info[5] != self.home:
|
2014-01-29 06:35:21 +01:00
|
|
|
if self.move_home:
|
|
|
|
cmd.append('-m')
|
2013-06-01 02:53:37 +02:00
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None and info[6] != self.shell:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
|
|
|
if self.login_class is not None:
|
2014-01-03 15:41:12 +01:00
|
|
|
# find current login class
|
|
|
|
user_login_class = None
|
|
|
|
userinfo_cmd = [self.module.get_bin_path('userinfo', True), self.name]
|
|
|
|
(rc, out, err) = self.execute_command(userinfo_cmd)
|
|
|
|
|
|
|
|
for line in out.splitlines():
|
|
|
|
tokens = line.split()
|
|
|
|
|
|
|
|
if tokens[0] == 'class' and len(tokens) == 2:
|
|
|
|
user_login_class = tokens[1]
|
|
|
|
|
|
|
|
# act only if login_class change
|
|
|
|
if self.login_class != user_login_class:
|
|
|
|
cmd.append('-L')
|
|
|
|
cmd.append(self.login_class)
|
2013-06-01 02:53:37 +02:00
|
|
|
|
2015-02-26 15:10:28 +01:00
|
|
|
if self.update_password == 'always' and self.password is not None \
|
|
|
|
and self.password != '*' and info[1] != self.password:
|
2013-06-01 02:53:37 +02:00
|
|
|
cmd.append('-p')
|
|
|
|
cmd.append(self.password)
|
|
|
|
|
|
|
|
# skip if no changes to be made
|
|
|
|
if len(cmd) == 1:
|
|
|
|
return (None, '', '')
|
|
|
|
elif self.module.check_mode:
|
|
|
|
return (0, '', '')
|
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
|
|
|
|
# ===========================================
|
|
|
|
|
|
|
|
class NetBSDUser(User):
|
|
|
|
"""
|
|
|
|
This is a NetBSD User manipulation class.
|
|
|
|
Main differences are that NetBSD:-
|
|
|
|
- has no concept of "system" account.
|
|
|
|
- has no force delete user
|
|
|
|
|
|
|
|
|
|
|
|
This overrides the following methods from the generic class:-
|
|
|
|
- create_user()
|
|
|
|
- remove_user()
|
|
|
|
- modify_user()
|
|
|
|
"""
|
|
|
|
|
|
|
|
platform = 'NetBSD'
|
|
|
|
distribution = None
|
|
|
|
SHADOWFILE = '/etc/master.passwd'
|
|
|
|
|
|
|
|
def create_user(self):
|
|
|
|
cmd = [self.module.get_bin_path('useradd', True)]
|
|
|
|
|
|
|
|
if self.uid is not None:
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-06-01 02:53:37 +02:00
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
|
|
|
groups = self.get_groups_set()
|
|
|
|
if len(groups) > 16:
|
|
|
|
self.module.fail_json(msg="Too many groups (%d) NetBSD allows for 16 max." % len(groups))
|
|
|
|
cmd.append('-G')
|
|
|
|
cmd.append(','.join(groups))
|
|
|
|
|
|
|
|
if self.comment is not None:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None:
|
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
|
|
|
if self.login_class is not None:
|
|
|
|
cmd.append('-L')
|
|
|
|
cmd.append(self.login_class)
|
|
|
|
|
|
|
|
if self.password is not None:
|
|
|
|
cmd.append('-p')
|
|
|
|
cmd.append(self.password)
|
|
|
|
|
|
|
|
if self.createhome:
|
|
|
|
cmd.append('-m')
|
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def remove_user_userdel(self):
|
|
|
|
cmd = [self.module.get_bin_path('userdel', True)]
|
|
|
|
if self.remove:
|
|
|
|
cmd.append('-r')
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def modify_user(self):
|
|
|
|
cmd = [self.module.get_bin_path('usermod', True)]
|
|
|
|
info = self.user_info()
|
|
|
|
|
|
|
|
if self.uid is not None and info[2] != int(self.uid):
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-06-01 02:53:37 +02:00
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
ginfo = self.group_info(self.group)
|
|
|
|
if info[3] != ginfo[2]:
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
|
|
|
current_groups = self.user_group_membership()
|
|
|
|
groups_need_mod = False
|
|
|
|
groups = []
|
|
|
|
|
|
|
|
if self.groups == '':
|
|
|
|
if current_groups and not self.append:
|
|
|
|
groups_need_mod = True
|
|
|
|
else:
|
|
|
|
groups = self.get_groups_set()
|
|
|
|
group_diff = set(current_groups).symmetric_difference(groups)
|
|
|
|
|
|
|
|
if group_diff:
|
|
|
|
if self.append:
|
|
|
|
for g in groups:
|
|
|
|
if g in group_diff:
|
|
|
|
groups = set(current_groups).union(groups)
|
|
|
|
groups_need_mod = True
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
groups_need_mod = True
|
|
|
|
|
|
|
|
if groups_need_mod:
|
|
|
|
if len(groups) > 16:
|
|
|
|
self.module.fail_json(msg="Too many groups (%d) NetBSD allows for 16 max." % len(groups))
|
|
|
|
cmd.append('-G')
|
|
|
|
cmd.append(','.join(groups))
|
|
|
|
|
|
|
|
if self.comment is not None and info[4] != self.comment:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None and info[5] != self.home:
|
2014-01-29 06:35:21 +01:00
|
|
|
if self.move_home:
|
|
|
|
cmd.append('-m')
|
2013-06-01 02:53:37 +02:00
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None and info[6] != self.shell:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
|
|
|
if self.login_class is not None:
|
|
|
|
cmd.append('-L')
|
|
|
|
cmd.append(self.login_class)
|
|
|
|
|
2013-06-17 04:47:29 +02:00
|
|
|
if self.update_password == 'always' and self.password is not None and info[1] != self.password:
|
2013-06-01 02:53:37 +02:00
|
|
|
cmd.append('-p')
|
|
|
|
cmd.append(self.password)
|
|
|
|
|
|
|
|
# skip if no changes to be made
|
|
|
|
if len(cmd) == 1:
|
|
|
|
return (None, '', '')
|
|
|
|
elif self.module.check_mode:
|
|
|
|
return (0, '', '')
|
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
|
|
|
|
# ===========================================
|
|
|
|
|
2012-11-09 04:48:00 +01:00
|
|
|
class SunOS(User):
|
|
|
|
"""
|
|
|
|
This is a SunOS User manipulation class - The main difference between
|
|
|
|
this class and the generic user class is that Solaris-type distros
|
|
|
|
don't support the concept of a "system" account and we need to
|
|
|
|
edit the /etc/shadow file manually to set a password. (Ugh)
|
2013-05-31 01:18:18 +02:00
|
|
|
|
2012-11-09 04:48:00 +01:00
|
|
|
This overrides the following methods from the generic class:-
|
|
|
|
- create_user()
|
|
|
|
- remove_user()
|
|
|
|
- modify_user()
|
|
|
|
"""
|
|
|
|
|
|
|
|
platform = 'SunOS'
|
|
|
|
distribution = None
|
|
|
|
SHADOWFILE = '/etc/shadow'
|
|
|
|
|
|
|
|
def remove_user(self):
|
|
|
|
cmd = [self.module.get_bin_path('userdel', True)]
|
|
|
|
if self.remove:
|
|
|
|
cmd.append('-r')
|
|
|
|
cmd.append(self.name)
|
|
|
|
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def create_user(self):
|
|
|
|
cmd = [self.module.get_bin_path('useradd', True)]
|
|
|
|
|
|
|
|
if self.uid is not None:
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-03-12 15:35:54 +01:00
|
|
|
|
2012-11-09 04:48:00 +01:00
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
2013-05-31 01:18:18 +02:00
|
|
|
groups = self.get_groups_set()
|
2012-11-09 04:48:00 +01:00
|
|
|
cmd.append('-G')
|
2013-05-31 01:18:18 +02:00
|
|
|
cmd.append(','.join(groups))
|
2012-11-09 04:48:00 +01:00
|
|
|
|
|
|
|
if self.comment is not None:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None:
|
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
|
|
|
if self.createhome:
|
|
|
|
cmd.append('-m')
|
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
|
2013-12-05 06:44:55 +01:00
|
|
|
if self.module.check_mode:
|
|
|
|
return (0, '', '')
|
|
|
|
else:
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc is not None and rc != 0:
|
|
|
|
self.module.fail_json(name=self.name, msg=err, rc=rc)
|
2012-11-09 04:48:00 +01:00
|
|
|
|
2013-12-05 06:44:55 +01:00
|
|
|
# we have to set the password by editing the /etc/shadow file
|
|
|
|
if self.password is not None:
|
|
|
|
try:
|
|
|
|
lines = []
|
|
|
|
for line in open(self.SHADOWFILE, 'rb').readlines():
|
|
|
|
fields = line.strip().split(':')
|
|
|
|
if not fields[0] == self.name:
|
|
|
|
lines.append(line)
|
|
|
|
continue
|
|
|
|
fields[1] = self.password
|
2014-03-22 20:12:56 +01:00
|
|
|
fields[2] = str(int(time.time() / 86400))
|
2013-12-05 06:44:55 +01:00
|
|
|
line = ':'.join(fields)
|
|
|
|
lines.append('%s\n' % line)
|
|
|
|
open(self.SHADOWFILE, 'w+').writelines(lines)
|
|
|
|
except Exception, err:
|
|
|
|
self.module.fail_json(msg="failed to update users password: %s" % str(err))
|
|
|
|
|
|
|
|
return (rc, out, err)
|
2012-11-09 04:48:00 +01:00
|
|
|
|
|
|
|
def modify_user_usermod(self):
|
|
|
|
cmd = [self.module.get_bin_path('usermod', True)]
|
|
|
|
cmd_len = len(cmd)
|
|
|
|
info = self.user_info()
|
|
|
|
|
|
|
|
if self.uid is not None and info[2] != int(self.uid):
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
2013-07-08 11:46:38 +02:00
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
2013-03-12 15:35:54 +01:00
|
|
|
|
2012-11-09 04:48:00 +01:00
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
ginfo = self.group_info(self.group)
|
|
|
|
if info[3] != ginfo[2]:
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
|
|
|
current_groups = self.user_group_membership()
|
2013-05-31 01:18:18 +02:00
|
|
|
groups = self.get_groups_set()
|
|
|
|
group_diff = set(current_groups).symmetric_difference(groups)
|
2012-11-09 04:48:00 +01:00
|
|
|
groups_need_mod = False
|
|
|
|
|
|
|
|
if group_diff:
|
|
|
|
if self.append:
|
|
|
|
for g in groups:
|
|
|
|
if g in group_diff:
|
|
|
|
groups_need_mod = True
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
groups_need_mod = True
|
|
|
|
|
|
|
|
if groups_need_mod:
|
|
|
|
cmd.append('-G')
|
|
|
|
new_groups = groups
|
|
|
|
if self.append:
|
2014-12-17 13:44:58 +01:00
|
|
|
new_groups.update(current_groups)
|
2012-11-09 04:48:00 +01:00
|
|
|
cmd.append(','.join(new_groups))
|
|
|
|
|
|
|
|
if self.comment is not None and info[4] != self.comment:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None and info[5] != self.home:
|
2014-01-29 06:35:21 +01:00
|
|
|
if self.move_home:
|
|
|
|
cmd.append('-m')
|
2012-11-09 04:48:00 +01:00
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None and info[6] != self.shell:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
2013-12-05 06:44:55 +01:00
|
|
|
if self.module.check_mode:
|
|
|
|
return (0, '', '')
|
2012-11-09 04:48:00 +01:00
|
|
|
else:
|
2013-12-05 06:44:55 +01:00
|
|
|
# modify the user if cmd will do anything
|
|
|
|
if cmd_len != len(cmd):
|
|
|
|
cmd.append(self.name)
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc is not None and rc != 0:
|
|
|
|
self.module.fail_json(name=self.name, msg=err, rc=rc)
|
|
|
|
else:
|
|
|
|
(rc, out, err) = (None, '', '')
|
2012-11-09 04:48:00 +01:00
|
|
|
|
2013-12-05 06:44:55 +01:00
|
|
|
# we have to set the password by editing the /etc/shadow file
|
|
|
|
if self.update_password == 'always' and self.password is not None and info[1] != self.password:
|
|
|
|
try:
|
|
|
|
lines = []
|
|
|
|
for line in open(self.SHADOWFILE, 'rb').readlines():
|
|
|
|
fields = line.strip().split(':')
|
|
|
|
if not fields[0] == self.name:
|
|
|
|
lines.append(line)
|
|
|
|
continue
|
|
|
|
fields[1] = self.password
|
2014-03-22 20:12:56 +01:00
|
|
|
fields[2] = str(int(time.time() / 86400))
|
2013-12-05 06:44:55 +01:00
|
|
|
line = ':'.join(fields)
|
|
|
|
lines.append('%s\n' % line)
|
|
|
|
open(self.SHADOWFILE, 'w+').writelines(lines)
|
|
|
|
rc = 0
|
|
|
|
except Exception, err:
|
|
|
|
self.module.fail_json(msg="failed to update users password: %s" % str(err))
|
|
|
|
|
|
|
|
return (rc, out, err)
|
2012-11-09 04:48:00 +01:00
|
|
|
|
2014-08-28 22:47:11 +02:00
|
|
|
# ===========================================
|
|
|
|
class DarwinUser(User):
|
|
|
|
"""
|
|
|
|
This is a Darwin Mac OS X User manipulation class.
|
|
|
|
Main differences are that Darwin:-
|
|
|
|
- Handles accounts in a database managed by dscl(1)
|
|
|
|
- Has no useradd/groupadd
|
|
|
|
- Does not create home directories
|
|
|
|
- User password must be cleartext
|
|
|
|
- UID must be given
|
|
|
|
- System users must ben under 500
|
|
|
|
|
|
|
|
This overrides the following methods from the generic class:-
|
|
|
|
- user_exists()
|
|
|
|
- create_user()
|
|
|
|
- remove_user()
|
|
|
|
- modify_user()
|
|
|
|
"""
|
|
|
|
platform = 'Darwin'
|
|
|
|
distribution = None
|
|
|
|
SHADOWFILE = None
|
|
|
|
|
|
|
|
dscl_directory = '.'
|
|
|
|
|
|
|
|
fields = [
|
|
|
|
('comment', 'RealName'),
|
|
|
|
('home', 'NFSHomeDirectory'),
|
|
|
|
('shell', 'UserShell'),
|
|
|
|
('uid', 'UniqueID'),
|
|
|
|
('group', 'PrimaryGroupID'),
|
|
|
|
]
|
|
|
|
|
|
|
|
def _get_dscl(self):
|
|
|
|
return [ self.module.get_bin_path('dscl', True), self.dscl_directory ]
|
|
|
|
|
|
|
|
def _list_user_groups(self):
|
|
|
|
cmd = self._get_dscl()
|
|
|
|
cmd += [ '-search', '/Groups', 'GroupMembership', self.name ]
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
groups = []
|
|
|
|
for line in out.splitlines():
|
|
|
|
if line.startswith(' ') or line.startswith(')'):
|
|
|
|
continue
|
|
|
|
groups.append(line.split()[0])
|
|
|
|
return groups
|
|
|
|
|
|
|
|
def _get_user_property(self, property):
|
|
|
|
'''Return user PROPERTY as given my dscl(1) read or None if not found.'''
|
|
|
|
cmd = self._get_dscl()
|
|
|
|
cmd += [ '-read', '/Users/%s' % self.name, property ]
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc != 0:
|
|
|
|
return None
|
|
|
|
# from dscl(1)
|
|
|
|
# if property contains embedded spaces, the list will instead be
|
|
|
|
# displayed one entry per line, starting on the line after the key.
|
|
|
|
lines = out.splitlines()
|
|
|
|
#sys.stderr.write('*** |%s| %s -> %s\n' % (property, out, lines))
|
|
|
|
if len(lines) == 1:
|
|
|
|
return lines[0].split(': ')[1]
|
|
|
|
else:
|
|
|
|
if len(lines) > 2:
|
|
|
|
return '\n'.join([ lines[1].strip() ] + lines[2:])
|
|
|
|
else:
|
|
|
|
if len(lines) == 2:
|
|
|
|
return lines[1].strip()
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2015-04-06 20:05:11 +02:00
|
|
|
def _get_next_uid(self):
|
|
|
|
'''Return the next available uid'''
|
|
|
|
cmd = self._get_dscl()
|
|
|
|
cmd += ['-list', '/Users', 'UniqueID']
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc != 0:
|
|
|
|
self.module.fail_json(
|
|
|
|
msg="Unable to get the next available uid",
|
|
|
|
rc=rc,
|
|
|
|
out=out,
|
|
|
|
err=err
|
|
|
|
)
|
|
|
|
max_uid = 0
|
|
|
|
for line in out.splitlines():
|
|
|
|
if max_uid < int(line.split()[1]):
|
|
|
|
max_uid = int(line.split()[1])
|
|
|
|
return max_uid + 1
|
|
|
|
|
2014-08-28 22:47:11 +02:00
|
|
|
def _change_user_password(self):
|
|
|
|
'''Change password for SELF.NAME against SELF.PASSWORD.
|
|
|
|
|
|
|
|
Please note that password must be cleatext.
|
|
|
|
'''
|
|
|
|
# some documentation on how is stored passwords on OSX:
|
|
|
|
# http://blog.lostpassword.com/2012/07/cracking-mac-os-x-lion-accounts-passwords/
|
|
|
|
# http://null-byte.wonderhowto.com/how-to/hack-mac-os-x-lion-passwords-0130036/
|
|
|
|
# http://pastebin.com/RYqxi7Ca
|
|
|
|
# on OSX 10.8+ hash is SALTED-SHA512-PBKDF2
|
|
|
|
# https://pythonhosted.org/passlib/lib/passlib.hash.pbkdf2_digest.html
|
|
|
|
# https://gist.github.com/nueh/8252572
|
|
|
|
cmd = self._get_dscl()
|
|
|
|
if self.password:
|
|
|
|
cmd += [ '-passwd', '/Users/%s' % self.name, self.password]
|
|
|
|
else:
|
|
|
|
cmd += [ '-create', '/Users/%s' % self.name, 'Password', '*']
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc != 0:
|
|
|
|
self.module.fail_json(msg='Error when changing password',
|
|
|
|
err=err, out=out, rc=rc)
|
|
|
|
return (rc, out, err)
|
|
|
|
|
|
|
|
def _make_group_numerical(self):
|
|
|
|
'''Convert SELF.GROUP to is stringed numerical value suitable for dscl.'''
|
2015-04-06 20:05:11 +02:00
|
|
|
if self.group is None:
|
|
|
|
self.group = 'nogroup'
|
|
|
|
try:
|
|
|
|
self.group = grp.getgrnam(self.group).gr_gid
|
|
|
|
except KeyError:
|
|
|
|
self.module.fail_json(msg='Group "%s" not found. Try to create it first using "group" module.' % self.group)
|
|
|
|
# We need to pass a string to dscl
|
|
|
|
self.group = str(self.group)
|
2014-08-28 22:47:11 +02:00
|
|
|
|
|
|
|
def __modify_group(self, group, action):
|
|
|
|
'''Add or remove SELF.NAME to or from GROUP depending on ACTION.
|
|
|
|
ACTION can be 'add' or 'remove' otherwhise 'remove' is assumed. '''
|
|
|
|
if action == 'add':
|
|
|
|
option = '-a'
|
|
|
|
else:
|
|
|
|
option = '-d'
|
|
|
|
cmd = [ 'dseditgroup', '-o', 'edit', option, self.name,
|
|
|
|
'-t', 'user', group ]
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc != 0:
|
|
|
|
self.module.fail_json(msg='Cannot %s user "%s" to group "%s".'
|
|
|
|
% (action, self.name, group),
|
|
|
|
err=err, out=out, rc=rc)
|
|
|
|
return (rc, out, err)
|
|
|
|
|
|
|
|
def _modify_group(self):
|
|
|
|
'''Add or remove SELF.NAME to or from GROUP depending on ACTION.
|
|
|
|
ACTION can be 'add' or 'remove' otherwhise 'remove' is assumed. '''
|
|
|
|
|
|
|
|
rc = 0
|
|
|
|
out = ''
|
|
|
|
err = ''
|
|
|
|
changed = False
|
|
|
|
|
|
|
|
current = set(self._list_user_groups())
|
|
|
|
if self.groups is not None:
|
|
|
|
target = set(self.groups.split(','))
|
|
|
|
else:
|
|
|
|
target = set([])
|
|
|
|
|
|
|
|
for remove in current - target:
|
|
|
|
(_rc, _err, _out) = self.__modify_group(remove, 'delete')
|
|
|
|
rc += rc
|
|
|
|
out += _out
|
|
|
|
err += _err
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
for add in target - current:
|
|
|
|
(_rc, _err, _out) = self.__modify_group(add, 'add')
|
|
|
|
rc += _rc
|
|
|
|
out += _out
|
|
|
|
err += _err
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
return (rc, err, out, changed)
|
|
|
|
|
|
|
|
def _update_system_user(self):
|
|
|
|
'''Hide or show user on login window according SELF.SYSTEM.
|
|
|
|
|
|
|
|
Returns 0 if a change has been made, None otherwhise.'''
|
|
|
|
|
|
|
|
plist_file = '/Library/Preferences/com.apple.loginwindow.plist'
|
|
|
|
|
|
|
|
# http://support.apple.com/kb/HT5017?viewlocale=en_US
|
|
|
|
cmd = [ 'defaults', 'read', plist_file, 'HiddenUsersList' ]
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
# returned value is
|
|
|
|
# (
|
|
|
|
# "_userA",
|
|
|
|
# "_UserB",
|
|
|
|
# userc
|
|
|
|
# )
|
|
|
|
hidden_users = []
|
|
|
|
for x in out.splitlines()[1:-1]:
|
|
|
|
try:
|
|
|
|
x = x.split('"')[1]
|
|
|
|
except IndexError:
|
|
|
|
x = x.strip()
|
|
|
|
hidden_users.append(x)
|
|
|
|
|
|
|
|
if self.system:
|
|
|
|
if not self.name in hidden_users:
|
|
|
|
cmd = [ 'defaults', 'write', plist_file,
|
|
|
|
'HiddenUsersList', '-array-add', self.name ]
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc != 0:
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='Cannot user "%s" to hidden user list.'
|
|
|
|
% self.name, err=err, out=out, rc=rc)
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
if self.name in hidden_users:
|
|
|
|
del(hidden_users[hidden_users.index(self.name)])
|
|
|
|
|
|
|
|
cmd = [ 'defaults', 'write', plist_file,
|
|
|
|
'HiddenUsersList', '-array' ] + hidden_users
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
if rc != 0:
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='Cannot remove user "%s" from hidden user list.'
|
|
|
|
% self.name, err=err, out=out, rc=rc)
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def user_exists(self):
|
|
|
|
'''Check is SELF.NAME is a known user on the system.'''
|
|
|
|
cmd = self._get_dscl()
|
|
|
|
cmd += [ '-list', '/Users/%s' % self.name]
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
return rc == 0
|
|
|
|
|
|
|
|
def remove_user(self):
|
|
|
|
'''Delete SELF.NAME. If SELF.FORCE is true, remove its home directory.'''
|
|
|
|
info = self.user_info()
|
|
|
|
|
|
|
|
cmd = self._get_dscl()
|
|
|
|
cmd += [ '-delete', '/Users/%s' % self.name]
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
|
|
|
|
if rc != 0:
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='Cannot delete user "%s".'
|
|
|
|
% self.name, err=err, out=out, rc=rc)
|
|
|
|
|
|
|
|
if self.force:
|
|
|
|
if os.path.exists(info[5]):
|
|
|
|
shutil.rmtree(info[5])
|
|
|
|
out += "Removed %s" % info[5]
|
|
|
|
|
|
|
|
return (rc, out, err)
|
|
|
|
|
|
|
|
def create_user(self, command_name='dscl'):
|
|
|
|
cmd = self._get_dscl()
|
|
|
|
cmd += [ '-create', '/Users/%s' % self.name]
|
|
|
|
(rc, err, out) = self.execute_command(cmd)
|
|
|
|
if rc != 0:
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='Cannot create user "%s".'
|
|
|
|
% self.name, err=err, out=out, rc=rc)
|
|
|
|
|
|
|
|
|
|
|
|
self._make_group_numerical()
|
2015-04-06 20:05:11 +02:00
|
|
|
if self.uid is None:
|
|
|
|
self.uid = str(self._get_next_uid())
|
2014-08-28 22:47:11 +02:00
|
|
|
|
|
|
|
# Homedir is not created by default
|
|
|
|
if self.createhome:
|
|
|
|
if self.home is None:
|
|
|
|
self.home = '/Users/%s' % self.name
|
|
|
|
if not os.path.exists(self.home):
|
|
|
|
os.makedirs(self.home)
|
|
|
|
self.chown_homedir(int(self.uid), int(self.group), self.home)
|
|
|
|
|
|
|
|
for field in self.fields:
|
|
|
|
if self.__dict__.has_key(field[0]) and self.__dict__[field[0]]:
|
|
|
|
|
|
|
|
cmd = self._get_dscl()
|
|
|
|
cmd += [ '-create', '/Users/%s' % self.name,
|
|
|
|
field[1], self.__dict__[field[0]]]
|
|
|
|
(rc, _err, _out) = self.execute_command(cmd)
|
|
|
|
if rc != 0:
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='Cannot add property "%s" to user "%s".'
|
|
|
|
% (field[0], self.name), err=err, out=out, rc=rc)
|
|
|
|
|
|
|
|
out += _out
|
|
|
|
err += _err
|
|
|
|
if rc != 0:
|
|
|
|
return (rc, _err, _out)
|
|
|
|
|
|
|
|
|
|
|
|
(rc, _err, _out) = self._change_user_password()
|
|
|
|
out += _out
|
|
|
|
err += _err
|
|
|
|
|
|
|
|
self._update_system_user()
|
|
|
|
# here we don't care about change status since it is a creation,
|
|
|
|
# thus changed is always true.
|
|
|
|
(rc, _out, _err, changed) = self._modify_group()
|
|
|
|
out += _out
|
|
|
|
err += _err
|
|
|
|
return (rc, err, out)
|
|
|
|
|
|
|
|
def modify_user(self):
|
|
|
|
changed = None
|
|
|
|
out = ''
|
|
|
|
err = ''
|
|
|
|
|
|
|
|
self._make_group_numerical()
|
|
|
|
|
|
|
|
for field in self.fields:
|
|
|
|
if self.__dict__.has_key(field[0]) and self.__dict__[field[0]]:
|
|
|
|
current = self._get_user_property(field[1])
|
|
|
|
if current is None or current != self.__dict__[field[0]]:
|
|
|
|
cmd = self._get_dscl()
|
|
|
|
cmd += [ '-create', '/Users/%s' % self.name,
|
|
|
|
field[1], self.__dict__[field[0]]]
|
|
|
|
(rc, _err, _out) = self.execute_command(cmd)
|
|
|
|
if rc != 0:
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='Cannot update property "%s" for user "%s".'
|
|
|
|
% (field[0], self.name), err=err, out=out, rc=rc)
|
|
|
|
changed = rc
|
|
|
|
out += _out
|
|
|
|
err += _err
|
2015-04-06 20:05:11 +02:00
|
|
|
if self.update_password == 'always' and self.password is not None:
|
2014-08-28 22:47:11 +02:00
|
|
|
(rc, _err, _out) = self._change_user_password()
|
|
|
|
out += _out
|
|
|
|
err += _err
|
|
|
|
changed = rc
|
|
|
|
|
|
|
|
(rc, _out, _err, _changed) = self._modify_group()
|
|
|
|
out += _out
|
|
|
|
err += _err
|
|
|
|
|
|
|
|
if _changed is True:
|
|
|
|
changed = rc
|
|
|
|
|
|
|
|
rc = self._update_system_user()
|
|
|
|
if rc == 0:
|
|
|
|
changed = rc
|
|
|
|
|
|
|
|
return (changed, out, err)
|
|
|
|
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
# ===========================================
|
|
|
|
|
2013-02-14 12:45:08 +01:00
|
|
|
class AIX(User):
|
|
|
|
"""
|
|
|
|
This is a AIX User manipulation class.
|
|
|
|
|
|
|
|
This overrides the following methods from the generic class:-
|
|
|
|
- create_user()
|
|
|
|
- remove_user()
|
|
|
|
- modify_user()
|
|
|
|
"""
|
|
|
|
|
|
|
|
platform = 'AIX'
|
|
|
|
distribution = None
|
|
|
|
SHADOWFILE = '/etc/security/passwd'
|
|
|
|
|
|
|
|
def remove_user(self):
|
|
|
|
cmd = [self.module.get_bin_path('userdel', True)]
|
|
|
|
if self.remove:
|
|
|
|
cmd.append('-r')
|
|
|
|
cmd.append(self.name)
|
|
|
|
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def create_user_useradd(self, command_name='useradd'):
|
|
|
|
cmd = [self.module.get_bin_path(command_name, True)]
|
|
|
|
|
|
|
|
if self.uid is not None:
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
2013-08-02 03:19:11 +02:00
|
|
|
if self.groups is not None and len(self.groups):
|
2013-05-31 01:18:18 +02:00
|
|
|
groups = self.get_groups_set()
|
2013-02-14 12:45:08 +01:00
|
|
|
cmd.append('-G')
|
2013-05-31 01:18:18 +02:00
|
|
|
cmd.append(','.join(groups))
|
2013-02-14 12:45:08 +01:00
|
|
|
|
|
|
|
if self.comment is not None:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None:
|
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
|
|
|
if self.createhome:
|
|
|
|
cmd.append('-m')
|
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
|
|
|
|
# set password with chpasswd
|
|
|
|
if self.password is not None:
|
|
|
|
cmd = []
|
|
|
|
cmd.append(self.module.get_bin_path('chpasswd', True))
|
|
|
|
cmd.append('-e')
|
|
|
|
cmd.append('-c')
|
2015-01-12 19:08:22 +01:00
|
|
|
self.execute_command(' '.join(cmd), data="%s:%s" % (self.name, self.password))
|
2013-02-14 12:45:08 +01:00
|
|
|
|
|
|
|
return (rc, out, err)
|
|
|
|
|
|
|
|
def modify_user_usermod(self):
|
|
|
|
cmd = [self.module.get_bin_path('usermod', True)]
|
|
|
|
info = self.user_info()
|
|
|
|
|
|
|
|
if self.uid is not None and info[2] != int(self.uid):
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
ginfo = self.group_info(self.group)
|
|
|
|
if info[3] != ginfo[2]:
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
|
|
|
current_groups = self.user_group_membership()
|
|
|
|
groups_need_mod = False
|
2013-08-27 16:17:33 +02:00
|
|
|
groups = []
|
2013-02-14 12:45:08 +01:00
|
|
|
|
2013-08-27 16:17:33 +02:00
|
|
|
if self.groups == '':
|
|
|
|
if current_groups and not self.append:
|
2013-02-14 12:45:08 +01:00
|
|
|
groups_need_mod = True
|
2013-08-27 16:17:33 +02:00
|
|
|
else:
|
|
|
|
groups = self.get_groups_set()
|
|
|
|
group_diff = set(current_groups).symmetric_difference(groups)
|
|
|
|
|
|
|
|
if group_diff:
|
|
|
|
if self.append:
|
|
|
|
for g in groups:
|
|
|
|
if g in group_diff:
|
|
|
|
groups_need_mod = True
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
groups_need_mod = True
|
2013-02-14 12:45:08 +01:00
|
|
|
|
|
|
|
if groups_need_mod:
|
|
|
|
cmd.append('-G')
|
|
|
|
cmd.append(','.join(groups))
|
|
|
|
|
|
|
|
if self.comment is not None and info[4] != self.comment:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None and info[5] != self.home:
|
2014-01-29 06:35:21 +01:00
|
|
|
if self.move_home:
|
|
|
|
cmd.append('-m')
|
2013-02-14 12:45:08 +01:00
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None and info[6] != self.shell:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
|
|
|
# skip if no changes to be made
|
|
|
|
if len(cmd) == 1:
|
|
|
|
(rc, out, err) = (None, '', '')
|
2013-02-18 13:23:20 +01:00
|
|
|
elif self.module.check_mode:
|
2013-02-17 23:26:54 +01:00
|
|
|
return (True, '', '')
|
2013-02-14 12:45:08 +01:00
|
|
|
else:
|
|
|
|
cmd.append(self.name)
|
|
|
|
(rc, out, err) = self.execute_command(cmd)
|
|
|
|
|
|
|
|
# set password with chpasswd
|
2013-06-17 04:47:29 +02:00
|
|
|
if self.update_password == 'always' and self.password is not None and info[1] != self.password:
|
2013-02-14 12:45:08 +01:00
|
|
|
cmd = []
|
|
|
|
cmd.append(self.module.get_bin_path('chpasswd', True))
|
|
|
|
cmd.append('-e')
|
|
|
|
cmd.append('-c')
|
2015-01-12 19:08:22 +01:00
|
|
|
(rc2, out2, err2) = self.execute_command(' '.join(cmd), data="%s:%s" % (self.name, self.password))
|
2013-02-14 12:45:08 +01:00
|
|
|
else:
|
|
|
|
(rc2, out2, err2) = (None, '', '')
|
|
|
|
|
|
|
|
if rc != None:
|
|
|
|
return (rc, out+out2, err+err2)
|
|
|
|
else:
|
|
|
|
return (rc2, out+out2, err+err2)
|
|
|
|
|
|
|
|
# ===========================================
|
|
|
|
|
2015-01-09 18:35:31 +01:00
|
|
|
class HPUX(User):
|
|
|
|
"""
|
|
|
|
This is a HP-UX User manipulation class.
|
|
|
|
|
|
|
|
This overrides the following methods from the generic class:-
|
|
|
|
- create_user()
|
|
|
|
- remove_user()
|
|
|
|
- modify_user()
|
|
|
|
"""
|
|
|
|
|
|
|
|
platform = 'HP-UX'
|
|
|
|
distribution = None
|
|
|
|
SHADOWFILE = '/etc/shadow'
|
|
|
|
|
|
|
|
def create_user(self):
|
|
|
|
cmd = ['/usr/sam/lbin/useradd.sam']
|
|
|
|
|
|
|
|
if self.uid is not None:
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None and len(self.groups):
|
|
|
|
groups = self.get_groups_set()
|
|
|
|
cmd.append('-G')
|
|
|
|
cmd.append(','.join(groups))
|
|
|
|
|
|
|
|
if self.comment is not None:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None:
|
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
|
|
|
|
if self.shell is not None:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
|
|
|
if self.password is not None:
|
|
|
|
cmd.append('-p')
|
|
|
|
cmd.append(self.password)
|
|
|
|
|
|
|
|
if self.createhome:
|
|
|
|
cmd.append('-m')
|
|
|
|
else:
|
|
|
|
cmd.append('-M')
|
|
|
|
|
|
|
|
if self.system:
|
|
|
|
cmd.append('-r')
|
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def remove_user(self):
|
|
|
|
cmd = ['/usr/sam/lbin/userdel.sam']
|
|
|
|
if self.force:
|
|
|
|
cmd.append('-F')
|
|
|
|
if self.remove:
|
|
|
|
cmd.append('-r')
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
def modify_user(self):
|
|
|
|
cmd = ['/usr/sam/lbin/usermod.sam']
|
|
|
|
info = self.user_info()
|
|
|
|
has_append = self._check_usermod_append()
|
|
|
|
|
|
|
|
if self.uid is not None and info[2] != int(self.uid):
|
|
|
|
cmd.append('-u')
|
|
|
|
cmd.append(self.uid)
|
|
|
|
|
|
|
|
if self.non_unique:
|
|
|
|
cmd.append('-o')
|
|
|
|
|
|
|
|
if self.group is not None:
|
|
|
|
if not self.group_exists(self.group):
|
|
|
|
self.module.fail_json(msg="Group %s does not exist" % self.group)
|
|
|
|
ginfo = self.group_info(self.group)
|
|
|
|
if info[3] != ginfo[2]:
|
|
|
|
cmd.append('-g')
|
|
|
|
cmd.append(self.group)
|
|
|
|
|
|
|
|
if self.groups is not None:
|
|
|
|
current_groups = self.user_group_membership()
|
|
|
|
groups_need_mod = False
|
|
|
|
groups = []
|
|
|
|
|
|
|
|
if self.groups == '':
|
|
|
|
if current_groups and not self.append:
|
|
|
|
groups_need_mod = True
|
|
|
|
else:
|
|
|
|
groups = self.get_groups_set(remove_existing=False)
|
|
|
|
group_diff = set(current_groups).symmetric_difference(groups)
|
|
|
|
|
|
|
|
if group_diff:
|
|
|
|
if self.append:
|
|
|
|
for g in groups:
|
|
|
|
if g in group_diff:
|
|
|
|
if has_append:
|
|
|
|
cmd.append('-a')
|
|
|
|
groups_need_mod = True
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
groups_need_mod = True
|
|
|
|
|
|
|
|
if groups_need_mod:
|
|
|
|
if self.append and not has_append:
|
|
|
|
cmd.append('-A')
|
|
|
|
cmd.append(','.join(group_diff))
|
|
|
|
else:
|
|
|
|
cmd.append('-G')
|
|
|
|
cmd.append(','.join(groups))
|
|
|
|
|
|
|
|
|
|
|
|
if self.comment is not None and info[4] != self.comment:
|
|
|
|
cmd.append('-c')
|
|
|
|
cmd.append(self.comment)
|
|
|
|
|
|
|
|
if self.home is not None and info[5] != self.home:
|
|
|
|
cmd.append('-d')
|
|
|
|
cmd.append(self.home)
|
|
|
|
if self.move_home:
|
|
|
|
cmd.append('-m')
|
|
|
|
|
|
|
|
if self.shell is not None and info[6] != self.shell:
|
|
|
|
cmd.append('-s')
|
|
|
|
cmd.append(self.shell)
|
|
|
|
|
|
|
|
if self.update_password == 'always' and self.password is not None and info[1] != self.password:
|
|
|
|
cmd.append('-p')
|
|
|
|
cmd.append(self.password)
|
|
|
|
|
|
|
|
# skip if no changes to be made
|
|
|
|
if len(cmd) == 1:
|
|
|
|
return (None, '', '')
|
|
|
|
elif self.module.check_mode:
|
|
|
|
return (0, '', '')
|
|
|
|
|
|
|
|
cmd.append(self.name)
|
|
|
|
return self.execute_command(cmd)
|
|
|
|
|
|
|
|
# ===========================================
|
|
|
|
|
2012-07-23 07:56:48 +02:00
|
|
|
def main():
|
2012-10-20 07:00:31 +02:00
|
|
|
ssh_defaults = {
|
|
|
|
'bits': '2048',
|
|
|
|
'type': 'rsa',
|
|
|
|
'passphrase': None,
|
2014-09-30 00:07:41 +02:00
|
|
|
'comment': 'ansible-generated on %s' % socket.gethostname()
|
2012-10-20 07:00:31 +02:00
|
|
|
}
|
2012-07-23 07:56:48 +02:00
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec = dict(
|
2015-01-28 15:22:32 +01:00
|
|
|
state=dict(default='present', choices=['present', 'absent'], type='str'),
|
2013-03-18 05:40:57 +01:00
|
|
|
name=dict(required=True, aliases=['user'], type='str'),
|
|
|
|
uid=dict(default=None, type='str'),
|
2013-03-14 10:55:44 +01:00
|
|
|
non_unique=dict(default='no', type='bool'),
|
2013-03-18 05:40:57 +01:00
|
|
|
group=dict(default=None, type='str'),
|
|
|
|
groups=dict(default=None, type='str'),
|
|
|
|
comment=dict(default=None, type='str'),
|
|
|
|
home=dict(default=None, type='str'),
|
|
|
|
shell=dict(default=None, type='str'),
|
|
|
|
password=dict(default=None, type='str'),
|
2013-06-01 02:53:37 +02:00
|
|
|
login_class=dict(default=None, type='str'),
|
2012-07-23 07:56:48 +02:00
|
|
|
# following options are specific to userdel
|
2013-02-23 22:56:45 +01:00
|
|
|
force=dict(default='no', type='bool'),
|
|
|
|
remove=dict(default='no', type='bool'),
|
2012-07-23 07:56:48 +02:00
|
|
|
# following options are specific to useradd
|
2013-02-23 22:56:45 +01:00
|
|
|
createhome=dict(default='yes', type='bool'),
|
|
|
|
system=dict(default='no', type='bool'),
|
2012-07-23 07:56:48 +02:00
|
|
|
# following options are specific to usermod
|
2014-01-29 06:35:21 +01:00
|
|
|
move_home=dict(default='no', type='bool'),
|
2013-02-23 22:56:45 +01:00
|
|
|
append=dict(default='no', type='bool'),
|
2012-10-20 07:00:31 +02:00
|
|
|
# following are specific to ssh key generation
|
2013-10-11 14:45:13 +02:00
|
|
|
generate_ssh_key=dict(type='bool'),
|
2013-03-18 05:40:57 +01:00
|
|
|
ssh_key_bits=dict(default=ssh_defaults['bits'], type='str'),
|
|
|
|
ssh_key_type=dict(default=ssh_defaults['type'], type='str'),
|
|
|
|
ssh_key_file=dict(default=None, type='str'),
|
|
|
|
ssh_key_comment=dict(default=ssh_defaults['comment'], type='str'),
|
2013-06-17 04:47:29 +02:00
|
|
|
ssh_key_passphrase=dict(default=None, type='str'),
|
2015-01-28 15:22:32 +01:00
|
|
|
update_password=dict(default='always',choices=['always','on_create'],type='str'),
|
|
|
|
expires=dict(default=None, type='float'),
|
2013-02-17 23:26:54 +01:00
|
|
|
),
|
|
|
|
supports_check_mode=True
|
2012-07-23 07:56:48 +02:00
|
|
|
)
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
user = User(module)
|
2012-10-20 07:00:31 +02:00
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
if user.syslogging:
|
|
|
|
syslog.openlog('ansible-%s' % os.path.basename(__file__))
|
|
|
|
syslog.syslog(syslog.LOG_NOTICE, 'User instantiated - platform %s' % user.platform)
|
|
|
|
if user.distribution:
|
|
|
|
syslog.syslog(syslog.LOG_NOTICE, 'User instantiated - distribution %s' % user.distribution)
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
|
2012-07-23 07:56:48 +02:00
|
|
|
rc = None
|
|
|
|
out = ''
|
|
|
|
err = ''
|
|
|
|
result = {}
|
2012-10-29 23:00:58 +01:00
|
|
|
result['name'] = user.name
|
|
|
|
result['state'] = user.state
|
|
|
|
if user.state == 'absent':
|
|
|
|
if user.user_exists():
|
2013-02-17 23:26:54 +01:00
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(changed=True)
|
2012-10-29 23:00:58 +01:00
|
|
|
(rc, out, err) = user.remove_user()
|
2012-07-23 07:56:48 +02:00
|
|
|
if rc != 0:
|
2013-02-12 17:52:43 +01:00
|
|
|
module.fail_json(name=user.name, msg=err, rc=rc)
|
2012-10-29 23:00:58 +01:00
|
|
|
result['force'] = user.force
|
|
|
|
result['remove'] = user.remove
|
2015-01-28 15:22:32 +01:00
|
|
|
elif user.state == 'present':
|
2012-10-29 23:00:58 +01:00
|
|
|
if not user.user_exists():
|
2013-02-17 23:26:54 +01:00
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(changed=True)
|
2012-10-29 23:00:58 +01:00
|
|
|
(rc, out, err) = user.create_user()
|
|
|
|
result['system'] = user.system
|
|
|
|
result['createhome'] = user.createhome
|
2012-07-23 07:56:48 +02:00
|
|
|
else:
|
2013-02-17 23:26:54 +01:00
|
|
|
# modify user (note: this function is check mode aware)
|
2012-10-29 23:00:58 +01:00
|
|
|
(rc, out, err) = user.modify_user()
|
|
|
|
result['append'] = user.append
|
2014-01-29 06:35:21 +01:00
|
|
|
result['move_home'] = user.move_home
|
2012-07-23 07:56:48 +02:00
|
|
|
if rc is not None and rc != 0:
|
2012-10-29 23:00:58 +01:00
|
|
|
module.fail_json(name=user.name, msg=err, rc=rc)
|
|
|
|
if user.password is not None:
|
2012-07-23 07:56:48 +02:00
|
|
|
result['password'] = 'NOT_LOGGING_PASSWORD'
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
|
2012-07-23 07:56:48 +02:00
|
|
|
if rc is None:
|
|
|
|
result['changed'] = False
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
else:
|
2012-07-23 07:56:48 +02:00
|
|
|
result['changed'] = True
|
|
|
|
if out:
|
|
|
|
result['stdout'] = out
|
|
|
|
if err:
|
|
|
|
result['stderr'] = err
|
2012-10-29 23:00:58 +01:00
|
|
|
|
|
|
|
if user.user_exists():
|
|
|
|
info = user.user_info()
|
2012-07-23 07:56:48 +02:00
|
|
|
if info == False:
|
2012-10-29 23:00:58 +01:00
|
|
|
result['msg'] = "failed to look up user name: %s" % user.name
|
2012-07-23 07:56:48 +02:00
|
|
|
result['failed'] = True
|
|
|
|
result['uid'] = info[2]
|
|
|
|
result['group'] = info[3]
|
|
|
|
result['comment'] = info[4]
|
|
|
|
result['home'] = info[5]
|
|
|
|
result['shell'] = info[6]
|
|
|
|
result['uid'] = info[2]
|
2012-10-29 23:00:58 +01:00
|
|
|
if user.groups is not None:
|
|
|
|
result['groups'] = user.groups
|
|
|
|
|
2014-12-18 22:47:09 +01:00
|
|
|
# handle missing homedirs
|
|
|
|
info = user.user_info()
|
|
|
|
if user.home is None:
|
|
|
|
user.home = info[5]
|
|
|
|
if not os.path.exists(user.home) and user.createhome:
|
|
|
|
if not module.check_mode:
|
|
|
|
user.create_homedir(user.home)
|
|
|
|
user.chown_homedir(info[2], info[3], user.home)
|
|
|
|
result['changed'] = True
|
|
|
|
|
2012-10-29 23:00:58 +01:00
|
|
|
# deal with ssh key
|
|
|
|
if user.sshkeygen:
|
|
|
|
(rc, out, err) = user.ssh_key_gen()
|
2012-10-20 07:00:31 +02:00
|
|
|
if rc is not None and rc != 0:
|
2012-10-29 23:00:58 +01:00
|
|
|
module.fail_json(name=user.name, msg=err, rc=rc)
|
2012-10-20 07:00:31 +02:00
|
|
|
if rc == 0:
|
|
|
|
result['changed'] = True
|
2012-10-29 23:00:58 +01:00
|
|
|
(rc, out, err) = user.ssh_key_fingerprint()
|
2012-10-20 07:00:31 +02:00
|
|
|
if rc == 0:
|
|
|
|
result['ssh_fingerprint'] = out.strip()
|
|
|
|
else:
|
|
|
|
result['ssh_fingerprint'] = err.strip()
|
2012-10-29 23:00:58 +01:00
|
|
|
result['ssh_key_file'] = user.get_ssh_key_path()
|
2013-04-13 16:10:58 +02:00
|
|
|
result['ssh_public_key'] = user.get_ssh_public_key()
|
2012-10-20 07:00:31 +02:00
|
|
|
|
2012-07-23 07:56:48 +02:00
|
|
|
module.exit_json(**result)
|
Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed. Other options supported for creating or modifying an existing
account: uid, gid, comment, home, shell, and password. If managing the
password, it must already be encrypted. When creating an account, you
can also provide the argument createhome to control whether the home
directory is created. Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 19:21:41 +01:00
|
|
|
|
2013-12-02 21:11:23 +01:00
|
|
|
# import module snippets
|
|
|
|
from ansible.module_utils.basic import *
|
2012-07-23 07:56:48 +02:00
|
|
|
main()
|