Mini Shell
"""
Functions for querying and modifying a user account and the groups to which it
belongs.
"""
import ctypes
import getpass
import logging
import os
import sys
import salt.utils.path
import salt.utils.platform
import salt.utils.stringutils
from salt.exceptions import CommandExecutionError
from salt.utils.decorators.jinja import jinja_filter
# Conditional imports
try:
import pwd
HAS_PWD = True
except ImportError:
HAS_PWD = False
try:
import grp
HAS_GRP = True
except ImportError:
HAS_GRP = False
try:
import salt.utils.win_functions
HAS_WIN_FUNCTIONS = True
except ImportError:
HAS_WIN_FUNCTIONS = False
if sys.platform == "win32":
import ctypes.wintypes
log = logging.getLogger(__name__)
def get_user():
"""
Get the current user
"""
if HAS_PWD:
ret = pwd.getpwuid(os.geteuid()).pw_name
elif HAS_WIN_FUNCTIONS and salt.utils.win_functions.HAS_WIN32:
ret = salt.utils.win_functions.get_current_user()
else:
raise CommandExecutionError(
"Required external library (pwd or win32api) not installed"
)
return salt.utils.stringutils.to_unicode(ret)
@jinja_filter("get_uid")
def get_uid(user=None):
"""
Get the uid for a given user name. If no user given, the current euid will
be returned. If the user does not exist, None will be returned. On systems
which do not support pwd or os.geteuid, None will be returned.
"""
if not HAS_PWD:
return None
elif user is None:
try:
return os.geteuid()
except AttributeError:
return None
else:
try:
return pwd.getpwnam(user).pw_uid
except KeyError:
return None
def _win_user_token_is_admin(user_token):
"""
Using the win32 api, determine if the user with token 'user_token' has
administrator rights.
See MSDN entry here:
http://msdn.microsoft.com/en-us/library/aa376389(VS.85).aspx
"""
class SID_IDENTIFIER_AUTHORITY(ctypes.Structure):
_fields_ = [
("byte0", ctypes.c_byte),
("byte1", ctypes.c_byte),
("byte2", ctypes.c_byte),
("byte3", ctypes.c_byte),
("byte4", ctypes.c_byte),
("byte5", ctypes.c_byte),
]
nt_authority = SID_IDENTIFIER_AUTHORITY()
nt_authority.byte5 = 5
SECURITY_BUILTIN_DOMAIN_RID = 0x20
DOMAIN_ALIAS_RID_ADMINS = 0x220
administrators_group = ctypes.c_void_p()
if (
ctypes.windll.advapi32.AllocateAndInitializeSid(
ctypes.byref(nt_authority),
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0,
0,
0,
0,
0,
0,
ctypes.byref(administrators_group),
)
== 0
):
raise Exception("AllocateAndInitializeSid failed")
try:
is_admin = ctypes.wintypes.BOOL()
if (
ctypes.windll.advapi32.CheckTokenMembership(
user_token, administrators_group, ctypes.byref(is_admin)
)
== 0
):
raise Exception("CheckTokenMembership failed")
return is_admin.value != 0
finally:
ctypes.windll.advapi32.FreeSid(administrators_group)
def _win_current_user_is_admin():
"""
ctypes.windll.shell32.IsUserAnAdmin() is intentionally avoided due to this
function being deprecated.
"""
return _win_user_token_is_admin(0)
def get_specific_user():
"""
Get a user name for publishing. If you find the user is "root" attempt to be
more specific
"""
user = get_user()
if salt.utils.platform.is_windows():
if _win_current_user_is_admin():
return f"sudo_{user}"
else:
env_vars = ("SUDO_USER",)
if user == "root":
for evar in env_vars:
if evar in os.environ:
return f"sudo_{os.environ[evar]}"
return user
def chugid(runas, group=None):
"""
Change the current process to belong to the specified user (and the groups
to which it belongs)
"""
uinfo = pwd.getpwnam(runas)
supgroups = []
supgroups_seen = set()
if group:
try:
target_pw_gid = grp.getgrnam(group).gr_gid
except KeyError as err:
raise CommandExecutionError(
f"Failed to fetch the GID for {group}. Error: {err}"
)
else:
target_pw_gid = uinfo.pw_gid
# The line below used to exclude the current user's primary gid.
# However, when root belongs to more than one group
# this causes root's primary group of '0' to be dropped from
# his grouplist. On FreeBSD, at least, this makes some
# command executions fail with 'access denied'.
#
# The Python documentation says that os.setgroups sets only
# the supplemental groups for a running process. On FreeBSD
# this does not appear to be strictly true.
group_list = get_group_dict(runas, include_default=True)
if sys.platform == "darwin":
group_list = {k: v for k, v in group_list.items() if not k.startswith("_")}
for group_name in group_list:
gid = group_list[group_name]
if gid not in supgroups_seen and not supgroups_seen.add(gid):
supgroups.append(gid)
if os.getgid() != target_pw_gid:
try:
os.setgid(target_pw_gid)
except OSError as err:
raise CommandExecutionError(
"Failed to change from gid {} to {}. Error: {}".format(
os.getgid(), target_pw_gid, err
)
)
# Set supplemental groups
if sorted(os.getgroups()) != sorted(supgroups):
try:
os.setgroups(supgroups)
except OSError as err:
raise CommandExecutionError(
"Failed to set supplemental groups to {}. Error: {}".format(
supgroups, err
)
)
if os.getuid() != uinfo.pw_uid:
try:
os.setuid(uinfo.pw_uid)
except OSError as err:
raise CommandExecutionError(
"Failed to change from uid {} to {}. Error: {}".format(
os.getuid(), uinfo.pw_uid, err
)
)
def chugid_and_umask(runas, umask, group=None):
"""
Helper method for for subprocess.Popen to initialise uid/gid and umask
for the new process.
"""
set_runas = False
set_grp = False
current_user = getpass.getuser()
current_grp = grp.getgrgid(pwd.getpwnam(getpass.getuser()).pw_gid).gr_name
if runas and runas != current_user:
set_runas = True
runas_user = runas
else:
runas_user = current_user
if group:
runas_grp = group
if group != current_grp:
set_grp = True
else:
if runas and runas != current_user:
runas_grp = grp.getgrgid(pwd.getpwnam(runas_user).pw_gid).gr_name
set_grp = True
else:
runas_grp = current_grp
if set_runas or set_grp:
chugid(runas_user, runas_grp)
if umask is not None:
os.umask(umask) # pylint: disable=blacklisted-function
def get_default_group(user):
"""
Returns the specified user's default group. If the user doesn't exist, a
KeyError will be raised.
"""
return (
grp.getgrgid(pwd.getpwnam(user).pw_gid).gr_name if HAS_GRP and HAS_PWD else None
)
def get_group_list(user, include_default=True):
"""
Returns a list of all of the system group names of which the user
is a member.
"""
if HAS_GRP is False or HAS_PWD is False:
return []
group_names = None
ugroups = set()
# Try os.getgrouplist, available in python >= 3.3
log.trace("Trying os.getgrouplist for '%s'", user)
try:
user_group_list = sorted(os.getgrouplist(user, pwd.getpwnam(user).pw_gid))
local_grall = _getgrall()
local_gids = sorted(lgrp.gr_gid for lgrp in local_grall)
max_idx = -1
local_max = local_gids[max_idx]
while local_max >= 65000:
max_idx -= 1
local_max = local_gids[max_idx]
user_group_list_local = [lgrp for lgrp in user_group_list if lgrp <= local_max]
user_group_list_remote = [rgrp for rgrp in user_group_list if rgrp > local_max]
local_group_names = [
_group.gr_name
for _group in local_grall
if _group.gr_gid in user_group_list_local
]
remote_group_names = [
grp.getgrgid(group_id).gr_name for group_id in user_group_list_remote
]
group_names = local_group_names + remote_group_names
except Exception: # pylint: disable=broad-except
pass
if group_names is None:
# Fall back to generic code
# Include the user's default group to match behavior of
# os.getgrouplist()
log.trace("Trying generic group list for '%s'", user)
group_names = [g.gr_name for g in grp.getgrall() if user in g.gr_mem]
try:
default_group = get_default_group(user)
if default_group not in group_names:
group_names.append(default_group)
except KeyError:
# If for some reason the user does not have a default group
pass
if group_names is not None:
ugroups.update(group_names)
if include_default is False:
# Historically, saltstack code for getting group lists did not
# include the default group. Some things may only want
# supplemental groups, so include_default=False omits the users
# default group.
try:
default_group = grp.getgrgid(pwd.getpwnam(user).pw_gid).gr_name
ugroups.remove(default_group)
except KeyError:
# If for some reason the user does not have a default group
pass
log.trace("Group list for user '%s': %s", user, sorted(ugroups))
return sorted(ugroups)
def get_group_dict(user=None, include_default=True):
"""
Returns a dict of all of the system groups as keys, and group ids
as values, of which the user is a member.
E.g.: {'staff': 501, 'sudo': 27}
"""
if HAS_GRP is False or HAS_PWD is False:
return {}
group_dict = {}
group_names = get_group_list(user, include_default=include_default)
for group in group_names:
try:
group_dict.update({group: grp.getgrnam(group).gr_gid})
except KeyError:
# In case if imporer duplicate group was returned by get_group_list
pass
return group_dict
def get_gid_list(user, include_default=True):
"""
Returns a list of all of the system group IDs of which the user
is a member.
"""
if HAS_GRP is False or HAS_PWD is False:
return []
gid_list = list(get_group_dict(user, include_default=include_default).values())
return sorted(set(gid_list))
def get_gid(group=None):
"""
Get the gid for a given group name. If no group given, the current egid
will be returned. If the group does not exist, None will be returned. On
systems which do not support grp or os.getegid it will return None.
"""
if not HAS_GRP:
return None
if group is None:
try:
return os.getegid()
except AttributeError:
return None
else:
try:
return grp.getgrnam(group).gr_gid
except KeyError:
return None
def _getgrall(root=None):
"""
Alternative implemetantion for getgrall, that uses only /etc/group
"""
ret = []
root = "/" if not root else root
etc_group = os.path.join(root, "etc/group")
with salt.utils.files.fopen(etc_group) as fp_:
for line in fp_:
line = salt.utils.stringutils.to_unicode(line)
comps = line.strip().split(":")
# Generate a getgrall compatible output
comps[2] = int(comps[2])
if comps[3]:
comps[3] = [mem.strip() for mem in comps[3].split(",")]
else:
comps[3] = []
ret.append(grp.struct_group(comps))
return ret
Zerion Mini Shell 1.0