Mini Shell
"""
Manage users with the pw command
.. important::
If you feel that Salt should be using this module to manage users on a
minion, and it is using a different module (or gives an error similar to
*'user.info' is not available*), see :ref:`here
<module-provider-override>`.
"""
# Notes:
# ------
#
# Format of the master.passwd file:
#
# - name User's login name.
# - password User's encrypted password.
# - uid User's id.
# - gid User's login group id.
# - class User's login class.
# - change Password change time.
# - expire Account expiration time.
# - gecos General information about the user.
# - home_dir User's home directory.
# - shell User's login shell.
#
# The usershow command allows viewing of an account in a format that is
# identical to the format used in /etc/master.passwd (with the password field
# replaced with a ‘*’.)
#
# Example:
# % pw usershow -n someuser
# someuser:*:1001:1001::0:0:SomeUser Name:/home/someuser:/bin/sh
import copy
import logging
import salt.utils.args
import salt.utils.data
import salt.utils.user
from salt.exceptions import CommandExecutionError
try:
import pwd
HAS_PWD = True
except ImportError:
HAS_PWD = False
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = "user"
def __virtual__():
"""
Set the user module if the kernel is FreeBSD or DragonFly
"""
if HAS_PWD and __grains__["kernel"] in ("FreeBSD", "DragonFly"):
return __virtualname__
return (
False,
"The pw_user execution module cannot be loaded: the pwd python module is not"
" available or the system is not FreeBSD.",
)
def _get_gecos(name):
"""
Retrieve GECOS field info and return it in dictionary form
"""
try:
gecos_field = pwd.getpwnam(name).pw_gecos.split(",", 3)
except KeyError:
raise CommandExecutionError(f"User '{name}' does not exist")
if not gecos_field:
return {}
else:
# Assign empty strings for any unspecified trailing GECOS fields
while len(gecos_field) < 4:
gecos_field.append("")
return {
"fullname": salt.utils.data.decode(gecos_field[0]),
"roomnumber": salt.utils.data.decode(gecos_field[1]),
"workphone": salt.utils.data.decode(gecos_field[2]),
"homephone": salt.utils.data.decode(gecos_field[3]),
}
def _build_gecos(gecos_dict):
"""
Accepts a dictionary entry containing GECOS field names and their values,
and returns a full GECOS comment string, to be used with pw usermod.
"""
return "{},{},{},{}".format(
gecos_dict.get("fullname", ""),
gecos_dict.get("roomnumber", ""),
gecos_dict.get("workphone", ""),
gecos_dict.get("homephone", ""),
)
def _update_gecos(name, key, value):
"""
Common code to change a user's GECOS information
"""
if not isinstance(value, str):
value = str(value)
pre_info = _get_gecos(name)
if not pre_info:
return False
if value == pre_info[key]:
return True
gecos_data = copy.deepcopy(pre_info)
gecos_data[key] = value
cmd = ["pw", "usermod", name, "-c", _build_gecos(gecos_data)]
__salt__["cmd.run"](cmd, python_shell=False)
post_info = info(name)
return _get_gecos(name).get(key) == value
def add(
name,
uid=None,
gid=None,
groups=None,
home=None,
shell=None,
unique=True,
fullname="",
roomnumber="",
workphone="",
homephone="",
createhome=True,
loginclass=None,
**kwargs,
):
"""
Add a user to the minion
CLI Example:
.. code-block:: bash
salt '*' user.add name <uid> <gid> <groups> <home> <shell>
"""
kwargs = salt.utils.args.clean_kwargs(**kwargs)
if salt.utils.data.is_true(kwargs.pop("system", False)):
log.warning("pw_user module does not support the 'system' argument")
if kwargs:
log.warning("Invalid kwargs passed to user.add")
if isinstance(groups, str):
groups = groups.split(",")
cmd = ["pw", "useradd"]
if uid:
cmd.extend(["-u", uid])
if gid:
cmd.extend(["-g", gid])
if groups:
cmd.extend(["-G", ",".join(groups)])
if home is not None:
cmd.extend(["-d", home])
if createhome is True:
cmd.append("-m")
if loginclass:
cmd.extend(["-L", loginclass])
if shell:
cmd.extend(["-s", shell])
if not salt.utils.data.is_true(unique):
cmd.append("-o")
gecos_field = _build_gecos(
{
"fullname": fullname,
"roomnumber": roomnumber,
"workphone": workphone,
"homephone": homephone,
}
)
cmd.extend(["-c", gecos_field])
cmd.extend(["-n", name])
return __salt__["cmd.retcode"](cmd, python_shell=False) == 0
def delete(name, remove=False, force=False):
"""
Remove a user from the minion
CLI Example:
.. code-block:: bash
salt '*' user.delete name remove=True force=True
"""
if salt.utils.data.is_true(force):
log.error(
"pw userdel does not support force-deleting user while user is logged in"
)
cmd = ["pw", "userdel"]
if remove:
cmd.append("-r")
cmd.extend(["-n", name])
return __salt__["cmd.retcode"](cmd, python_shell=False) == 0
def getent(refresh=False):
"""
Return the list of all info for all users
CLI Example:
.. code-block:: bash
salt '*' user.getent
"""
if "user.getent" in __context__ and not refresh:
return __context__["user.getent"]
ret = []
for data in pwd.getpwall():
ret.append(info(data.pw_name))
__context__["user.getent"] = ret
return ret
def chuid(name, uid):
"""
Change the uid for a named user
CLI Example:
.. code-block:: bash
salt '*' user.chuid foo 4376
"""
pre_info = info(name)
if not pre_info:
raise CommandExecutionError(f"User '{name}' does not exist")
if uid == pre_info["uid"]:
return True
cmd = ["pw", "usermod", "-u", uid, "-n", name]
__salt__["cmd.run"](cmd, python_shell=False)
return info(name).get("uid") == uid
def chgid(name, gid):
"""
Change the default group of the user
CLI Example:
.. code-block:: bash
salt '*' user.chgid foo 4376
"""
pre_info = info(name)
if not pre_info:
raise CommandExecutionError(f"User '{name}' does not exist")
if gid == pre_info["gid"]:
return True
cmd = ["pw", "usermod", "-g", gid, "-n", name]
__salt__["cmd.run"](cmd, python_shell=False)
return info(name).get("gid") == gid
def chshell(name, shell):
"""
Change the default shell of the user
CLI Example:
.. code-block:: bash
salt '*' user.chshell foo /bin/zsh
"""
pre_info = info(name)
if not pre_info:
raise CommandExecutionError(f"User '{name}' does not exist")
if shell == pre_info["shell"]:
return True
cmd = ["pw", "usermod", "-s", shell, "-n", name]
__salt__["cmd.run"](cmd, python_shell=False)
return info(name).get("shell") == shell
def chhome(name, home, persist=False):
"""
Set a new home directory for an existing user
name
Username to modify
home
New home directory to set
persist : False
Set to ``True`` to prevent configuration files in the new home
directory from being overwritten by the files from the skeleton
directory.
CLI Example:
.. code-block:: bash
salt '*' user.chhome foo /home/users/foo True
"""
pre_info = info(name)
if not pre_info:
raise CommandExecutionError(f"User '{name}' does not exist")
if home == pre_info["home"]:
return True
cmd = ["pw", "usermod", name, "-d", home]
if persist:
cmd.append("-m")
__salt__["cmd.run"](cmd, python_shell=False)
return info(name).get("home") == home
def chgroups(name, groups, append=False):
"""
Change the groups to which a user belongs
name
Username to modify
groups
List of groups to set for the user. Can be passed as a comma-separated
list or a Python list.
append : False
Set to ``True`` to append these groups to the user's existing list of
groups. Otherwise, the specified groups will replace any existing
groups for the user.
CLI Example:
.. code-block:: bash
salt '*' user.chgroups foo wheel,root True
"""
if isinstance(groups, str):
groups = groups.split(",")
ugrps = set(list_groups(name))
if ugrps == set(groups):
return True
if append:
groups += ugrps
cmd = ["pw", "usermod", "-G", ",".join(groups), "-n", name]
return __salt__["cmd.retcode"](cmd, python_shell=False) == 0
def chfullname(name, fullname):
"""
Change the user's Full Name
CLI Example:
.. code-block:: bash
salt '*' user.chfullname foo "Foo Bar"
"""
return _update_gecos(name, "fullname", fullname)
def chroomnumber(name, roomnumber):
"""
Change the user's Room Number
CLI Example:
.. code-block:: bash
salt '*' user.chroomnumber foo 123
"""
return _update_gecos(name, "roomnumber", roomnumber)
def chworkphone(name, workphone):
"""
Change the user's Work Phone
CLI Example:
.. code-block:: bash
salt '*' user.chworkphone foo "7735550123"
"""
return _update_gecos(name, "workphone", workphone)
def chhomephone(name, homephone):
"""
Change the user's Home Phone
CLI Example:
.. code-block:: bash
salt '*' user.chhomephone foo "7735551234"
"""
return _update_gecos(name, "homephone", homephone)
def chloginclass(name, loginclass, root=None):
"""
Change the default login class of the user
.. versionadded:: 2016.3.5
CLI Example:
.. code-block:: bash
salt '*' user.chloginclass foo staff
"""
if loginclass == get_loginclass(name):
return True
cmd = ["pw", "usermod", "-L", f"{loginclass}", "-n", f"{name}"]
__salt__["cmd.run"](cmd, python_shell=False)
return get_loginclass(name) == loginclass
def info(name):
"""
Return user information
CLI Example:
.. code-block:: bash
salt '*' user.info root
"""
ret = {}
try:
data = pwd.getpwnam(name)
ret["gid"] = data.pw_gid
ret["groups"] = list_groups(name)
ret["home"] = data.pw_dir
ret["name"] = data.pw_name
ret["passwd"] = data.pw_passwd
ret["shell"] = data.pw_shell
ret["uid"] = data.pw_uid
# Put GECOS info into a list
gecos_field = data.pw_gecos.split(",", 3)
# Assign empty strings for any unspecified GECOS fields
while len(gecos_field) < 4:
gecos_field.append("")
ret["fullname"] = gecos_field[0]
ret["roomnumber"] = gecos_field[1]
ret["workphone"] = gecos_field[2]
ret["homephone"] = gecos_field[3]
except KeyError:
return {}
return ret
def get_loginclass(name):
"""
Get the login class of the user
.. versionadded:: 2016.3.0
CLI Example:
.. code-block:: bash
salt '*' user.get_loginclass foo
"""
userinfo = __salt__["cmd.run_stdout"](["pw", "usershow", "-n", name])
userinfo = userinfo.split(":")
return userinfo[4] if len(userinfo) == 10 else ""
def list_groups(name):
"""
Return a list of groups the named user belongs to
CLI Example:
.. code-block:: bash
salt '*' user.list_groups foo
"""
return salt.utils.user.get_group_list(name)
def list_users():
"""
Return a list of all users
CLI Example:
.. code-block:: bash
salt '*' user.list_users
"""
return sorted(user.pw_name for user in pwd.getpwall())
def rename(name, new_name):
"""
Change the username for a named user
CLI Example:
.. code-block:: bash
salt '*' user.rename name new_name
"""
current_info = info(name)
if not current_info:
raise CommandExecutionError(f"User '{name}' does not exist")
new_info = info(new_name)
if new_info:
raise CommandExecutionError(f"User '{new_name}' already exists")
cmd = ["pw", "usermod", "-l", new_name, "-n", name]
__salt__["cmd.run"](cmd)
post_info = info(new_name)
if post_info["name"] != current_info["name"]:
return post_info["name"] == new_name
return False
Zerion Mini Shell 1.0