PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/extras-3.10/rads/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/extras-3.10/rads/_users.py |
"""Functions for fetching basic info on user accounts""" import pwd import grp import re import os from typing import Literal, Union, overload import yaml from ._yaml import DumbYamlLoader from . import SYS_USERS, STAFF_GROUPS, OUR_RESELLERS class CpuserError(Exception): """Raised when there's something wrong collecting cPanel user info""" __module__ = 'rads' def get_login() -> str: """Obtain which user ran this script Returns: username """ try: blame = os.getlogin() except OSError: blame = pwd.getpwuid(os.geteuid()).pw_name return blame get_login.__module__ = 'rads' def is_cpuser(user: str) -> bool: """Checks if a user is a valid cPanel user. Warning: This only checks if the user exists and will also be true for restricted cPanel users. Use ``cpuser_safe`` instead if you need to check for those Args: user: cPanel username to check Returns: Whether the cPanel user exists """ try: homedir = pwd.getpwnam(user).pw_dir except KeyError: return False return all( ( os.path.isdir(homedir), os.path.isfile(os.path.join('/var/cpanel/users', user)), os.path.isdir(os.path.join('/var/cpanel/userdata', user)), ) ) is_cpuser.__module__ = 'rads' @overload def all_cpusers(owners: Literal[False] = False) -> list[str]: ... @overload def all_cpusers(owners: Literal[True] = True) -> dict[str, str]: ... def all_cpusers(owners: bool = False) -> Union[dict[str, str], list[str]]: """Returns cPanel users from /etc/trueuserowners Args: owners: whether to return users as a dict with owners as the values Raises: CpuserError: if /etc/trueuserowners is invalid Returns: either a list of all users, or a dict of users (keys) to owners (vals) """ with open('/etc/trueuserowners', encoding='utf-8') as userowners: userdict = yaml.load(userowners, DumbYamlLoader) if not isinstance(userdict, dict): raise CpuserError('/etc/trueuserowners is invalid') if owners: return userdict return list(userdict.keys()) all_cpusers.__module__ = 'rads' def main_cpusers() -> list: """Get a all non-child, non-system, "main" cPanel users Raises: CpuserError: if /etc/trueuserowners is invalid""" return [ user for user, owner in all_cpusers(owners=True).items() if owner in OUR_RESELLERS or owner == user ] main_cpusers.__module__ = 'rads' def get_owner(user: str) -> str: """Get a user's owner (even if the account has reseller ownership of itself) Warning: the owner may be root, which is not a cPanel user Hint: If looking this up for multiple users, use ``get_cpusers(owners=True)`` instead Args: user: cPanel username to find the owner for Raises: CpuserError: if /etc/trueuserowners is invalid or the requested user is not defined in there """ try: return all_cpusers(owners=True)[user] except KeyError as exc: raise CpuserError(f'{user} is not in /etc/trueuserowners') from exc get_owner.__module__ = 'rads' def is_child(user: str) -> bool: """Check if a cPanel user is not self-owned and not owned by a system user Args: user: cPanel username to check Raises: CpuserError: if /etc/trueuserowners is invalid or the requested user is not defined in there """ owner = get_owner(user) return owner not in OUR_RESELLERS and owner != user is_child.__module__ = 'rads' def get_children(owner: str) -> list[str]: """Get a list of child accounts for a reseller Args: owner: cPanel username to lookup Returns: all child accounts of a reseller, excluding itself Raises: CpuserError: if /etc/trueuserowners is invalid """ return [ usr for usr, own in all_cpusers(owners=True).items() if own == owner and usr != own ] get_children.__module__ = 'rads' def cpuser_safe(user: str) -> bool: """Checks whether the user is safe for support to operate on - The user exists and is a valid cPanel user - The user is not a reserved account - The user is not in a staff group Args: user: cPanel username to check """ # SYS_USERS includes SECURE_USER if user in SYS_USERS or user in OUR_RESELLERS or not is_cpuser(user): return False for group in [x.gr_name for x in grp.getgrall() if user in x.gr_mem]: if group in STAFF_GROUPS: return False return True cpuser_safe.__module__ = 'rads' def cpuser_suspended(user: str) -> bool: """Check if a user is currently suspended Warning: This does not check for pending suspensions Args: user: cPanel username to check """ return os.path.exists(os.path.join('/var/cpanel/suspended', user)) cpuser_suspended.__module__ = 'rads' def get_homedir(user: str): """Get home directory path for a cPanel user Args: user: cPanel username to check Raises: CpuserError: if the user does not exist or the home directory path found does not match the expected pattern """ try: homedir = pwd.getpwnam(user).pw_dir except KeyError as exc: raise CpuserError(f'{user}: no such user') from exc if re.match(r'/home[0-9]*/\w+', homedir) is None: # Even though we fetched the homedir successfully from /etc/passwd, # treat this as an error due to unexpected output. If the result was # '/' for example, some calling programs might misbehave or even # rm -rf / depending on what it's being used for raise CpuserError(f'{user!r} does not match expected pattern') return homedir get_homedir.__module__ = 'rads' def get_primary_domain(user: str) -> str: """Get primary domain from cpanel userdata Args: user: cPanel username to check Raises: CpuserError: if cpanel userdata cannot be read or main_domain is missing """ userdata_path = os.path.join('/var/cpanel/userdata', user, 'main') try: with open(userdata_path, encoding='utf-8') as userdata_filehandle: return yaml.safe_load(userdata_filehandle)['main_domain'] except (yaml.YAMLError, KeyError, OSError) as exc: raise CpuserError(exc) from exc get_primary_domain.__module__ = 'rads' def whoowns(domain: str) -> str: """ Get the cPanel username that owns a domain Args: domain: Domain name to look up Returns: The name of a cPanel user that owns the domain name, or None on failure """ try: with open('/etc/userdomains', encoding='utf-8') as file: match = next(x for x in file if x.startswith(f'{domain}: ')) return match.rstrip().split(': ')[1] except (OSError, FileNotFoundError, StopIteration): return None whoowns.__module__ = 'rads' def _read_userdata_yaml(user, domfile, required): """Internal helper function for UserData to strictly parse YAML files""" path = os.path.join('/var/cpanel/userdata', user, domfile) try: with open(path, encoding='utf-8') as handle: data = yaml.safe_load(handle) if not isinstance(data, dict): raise ValueError except OSError as exc: raise CpuserError(f'{path!r} could not be opened') from exc except ValueError as exc: raise CpuserError(f'{path!r} could not be parsed') from exc for key, req_type in required.items(): if key not in data: raise CpuserError(f'{path!r} is missing {key!r}') if not isinstance(data[key], req_type): raise CpuserError(f'{path!r} contains invalid data for {key!r}') data['has_ssl'] = os.path.isfile(f'{path}_SSL') return data class UserData: """Object representing the data parsed from userdata Args: user: cPanel username to read cPanel userdata for Raises: CpuserError: if cPanel userdata is invalid Attributes: user (str): username primary (UserDomain): UserDomain object for the main domain addons (list): UserDomain objects for addon domains parked (list): UserDomain objects for parked domains subs (list): UserDomain objects for subdomains Hint: Use vars() to view this ``UserData`` object as a dict """ user: str primary: 'UserDomain' addons: list['UserDomain'] parked: list['UserDomain'] subs: list['UserDomain'] __module__ = 'rads' def __init__(self, user: str): """Initializes a UserData object given a cPanel username""" self.user = user main_data = _read_userdata_yaml( user=user, domfile='main', required={ 'main_domain': str, 'addon_domains': dict, 'parked_domains': list, 'sub_domains': list, }, ) dom_data = _read_userdata_yaml( user=user, domfile=main_data['main_domain'], required={'documentroot': str}, ) # populate primary domain self.primary = UserDomain( domain=main_data['main_domain'], has_ssl=dom_data['has_ssl'], docroot=dom_data['documentroot'], ) # populate addon domains self.addons = [] for addon, addon_file in main_data['addon_domains'].items(): addon_data = _read_userdata_yaml( user=user, domfile=addon_file, required={'documentroot': str} ) self.addons.append( UserDomain( domain=addon, has_ssl=addon_data['has_ssl'], docroot=addon_data['documentroot'], ) ) # populate parked domains self.parked = [] for parked in main_data['parked_domains']: self.parked.append( UserDomain( domain=parked, has_ssl=False, docroot=self.primary.docroot ) ) # populate subdomains self.subs = [] for sub in main_data['sub_domains']: sub_data = _read_userdata_yaml( user=user, domfile=sub, required={'documentroot': str} ) self.subs.append( UserDomain( domain=sub, has_ssl=sub_data['has_ssl'], docroot=sub_data['documentroot'], ) ) def __repr__(self): return f'UserData({self.user!r})' @property def __dict__(self): return { 'user': self.user, 'primary': vars(self.primary), 'addons': [vars(x) for x in self.addons], 'parked': [vars(x) for x in self.parked], 'subs': [vars(x) for x in self.subs], } @property def all_roots(self) -> list[str]: """All site document roots (list)""" all_dirs = {self.primary.docroot} all_dirs.update([x.docroot for x in self.subs]) all_dirs.update([x.docroot for x in self.addons]) return list(all_dirs) @property def merged_roots(self) -> list[str]: """Merged, top-level document roots for a user (list)""" merged = [] for test_path in sorted(self.all_roots): head, tail = os.path.split(test_path) while head and tail: if head in merged: break head, tail = os.path.split(head) else: if test_path not in merged: merged.append(test_path) return merged class UserDomain: """Object representing a cPanel domain in ``rads.UserData()`` Attributes: domain (str): domain name has_ssl (bool): True/False if the domain has ssl docroot (str): document root on the disk Hint: vars() can be run on this object to convert it into a dict """ __module__ = 'rads' def __init__(self, domain: str, has_ssl: bool, docroot: str): self.domain = domain self.has_ssl = has_ssl self.docroot = docroot def __repr__(self): return ( f"UserDomain(domain={self.domain!r}, has_ssl={self.has_ssl!r}, " f"docroot={self.docroot!r})" ) @property def __dict__(self): myvars = {} for attr in ('domain', 'has_ssl', 'docroot'): myvars[attr] = getattr(self, attr) return myvars