| # data.py -- Calculated/Discovered Data Values |
| # Copyright 1998-2014 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import grp |
| import os |
| import platform |
| import pwd |
| |
| import portage |
| |
| portage.proxy.lazyimport.lazyimport( |
| globals(), |
| "portage.output:colorize", |
| "portage.util:writemsg", |
| "portage.util.path:first_existing", |
| "subprocess", |
| ) |
| from portage.localization import _ |
| |
| ostype = platform.system() |
| userland = None |
| if ostype == "DragonFly" or ostype.endswith("BSD"): |
| userland = "BSD" |
| else: |
| userland = "GNU" |
| |
| lchown = getattr(os, "lchown", None) |
| |
| if not lchown: |
| if ostype == "Darwin": |
| |
| def lchown(*_args, **_kwargs): |
| pass |
| |
| else: |
| |
| def lchown(*_args, **_kwargs): |
| writemsg( |
| colorize("BAD", "!!!") |
| + _( |
| " It seems that os.lchown does not" |
| " exist. Please rebuild python.\n" |
| ), |
| noiselevel=-1, |
| ) |
| |
| lchown() |
| |
| lchown = portage._unicode_func_wrapper(lchown) |
| |
| |
| def _target_eprefix(): |
| """ |
| Calculate the target EPREFIX, which may be different from |
| portage.const.EPREFIX due to cross-prefix support. The result |
| is equivalent to portage.settings["EPREFIX"], but the calculation |
| is done without the expense of instantiating portage.settings. |
| @rtype: str |
| @return: the target EPREFIX |
| """ |
| eprefix = os.environ.get("EPREFIX", portage.const.EPREFIX) |
| if eprefix: |
| eprefix = portage.util.normalize_path(eprefix) |
| return eprefix |
| |
| |
| def _target_root(): |
| """ |
| Calculate the target ROOT. The result is equivalent to |
| portage.settings["ROOT"], but the calculation |
| is done without the expense of instantiating portage.settings. |
| @rtype: str |
| @return: the target ROOT (always ends with a slash) |
| """ |
| root = os.environ.get("ROOT") |
| if not root: |
| # Handle either empty or unset ROOT. |
| root = os.sep |
| root = portage.util.normalize_path(root) |
| return root.rstrip(os.sep) + os.sep |
| |
| |
| def portage_group_warning(): |
| warn_prefix = colorize("BAD", "*** WARNING *** ") |
| mylines = [ |
| "For security reasons, only system administrators should be", |
| "allowed in the portage group. Untrusted users or processes", |
| "can potentially exploit the portage group for attacks such as", |
| "local privilege escalation.", |
| ] |
| for x in mylines: |
| writemsg(warn_prefix, noiselevel=-1) |
| writemsg(x, noiselevel=-1) |
| writemsg("\n", noiselevel=-1) |
| writemsg("\n", noiselevel=-1) |
| |
| |
| # Portage has 3 security levels that depend on the uid and gid of the main |
| # process and are assigned according to the following table: |
| # |
| # Privileges secpass uid gid |
| # normal 0 any any |
| # group 1 any portage_gid |
| # super 2 0 any |
| # |
| # If the "wheel" group does not exist then wheelgid falls back to 0. |
| # If the "portage" group does not exist then portage_uid falls back to wheelgid. |
| |
| # If the current user is not root, but has write access to the |
| # EROOT directory (not due to the 0002 bit), then use "unprivileged" |
| # mode which sets secpass = 2 and uses the UID and GID of the EROOT |
| # directory to generate default PORTAGE_INST_GID, PORTAGE_INST_UID, |
| # PORTAGE_USERNAME, and PORTAGE_GRPNAME settings. |
| |
| |
| def _unprivileged_mode(eroot, eroot_st): |
| return ( |
| os.getuid() != 0 and os.access(eroot, os.W_OK) and not eroot_st.st_mode & 0o0002 |
| ) |
| |
| |
| uid = os.getuid() |
| wheelgid = 0 |
| try: |
| wheelgid = grp.getgrnam("wheel")[2] |
| except KeyError: |
| pass |
| |
| # The portage_uid and portage_gid global constants, and others that |
| # depend on them are initialized lazily, in order to allow configuration |
| # via make.conf. Eventually, these constants may be deprecated in favor |
| # of config attributes, since it's conceivable that multiple |
| # configurations with different constants could be used simultaneously. |
| _initialized_globals = set() |
| |
| |
| def _get_global(k): |
| if k in _initialized_globals: |
| return globals()[k] |
| |
| if k == "secpass": |
| |
| unprivileged = False |
| if hasattr(portage, "settings"): |
| unprivileged = "unprivileged" in portage.settings.features |
| else: |
| # The config class has equivalent code, but we also need to |
| # do it here if _disable_legacy_globals() has been called. |
| eroot_or_parent = first_existing( |
| os.path.join(_target_root(), _target_eprefix().lstrip(os.sep)) |
| ) |
| try: |
| eroot_st = os.stat(eroot_or_parent) |
| except OSError: |
| pass |
| else: |
| unprivileged = _unprivileged_mode(eroot_or_parent, eroot_st) |
| |
| v = 0 |
| if uid == 0: |
| v = 2 |
| elif unprivileged: |
| v = 2 |
| elif _get_global("portage_gid") in os.getgroups(): |
| v = 1 |
| |
| elif k in ("portage_gid", "portage_uid"): |
| |
| # Discover the uid and gid of the portage user/group |
| keyerror = False |
| try: |
| portage_uid = pwd.getpwnam(_get_global("_portage_username")).pw_uid |
| except KeyError: |
| keyerror = True |
| portage_uid = 0 |
| |
| try: |
| portage_gid = grp.getgrnam(_get_global("_portage_grpname")).gr_gid |
| except KeyError: |
| keyerror = True |
| portage_gid = 0 |
| |
| # Suppress this error message if both PORTAGE_GRPNAME and |
| # PORTAGE_USERNAME are set to "root", for things like |
| # Android (see bug #454060). |
| if keyerror and not ( |
| _get_global("_portage_username") == "root" |
| and _get_global("_portage_grpname") == "root" |
| ): |
| writemsg( |
| colorize("BAD", _("portage: 'portage' user or group missing.")) + "\n", |
| noiselevel=-1, |
| ) |
| writemsg( |
| _( |
| " For the defaults, line 1 goes into passwd, " |
| "and 2 into group.\n" |
| ), |
| noiselevel=-1, |
| ) |
| writemsg( |
| colorize( |
| "GOOD", |
| " portage:x:250:250:portage:/var/tmp/portage:/bin/false", |
| ) |
| + "\n", |
| noiselevel=-1, |
| ) |
| writemsg( |
| colorize("GOOD", " portage::250:portage") + "\n", noiselevel=-1 |
| ) |
| portage_group_warning() |
| |
| globals()["portage_gid"] = portage_gid |
| _initialized_globals.add("portage_gid") |
| globals()["portage_uid"] = portage_uid |
| _initialized_globals.add("portage_uid") |
| |
| if k == "portage_gid": |
| return portage_gid |
| if k == "portage_uid": |
| return portage_uid |
| raise AssertionError("unknown name: %s" % k) |
| |
| elif k == "userpriv_groups": |
| v = [_get_global("portage_gid")] |
| if secpass >= 2: |
| # Get a list of group IDs for the portage user. Do not use |
| # grp.getgrall() since it is known to trigger spurious |
| # SIGPIPE problems with nss_ldap. |
| cmd = ["id", "-G", _portage_username] |
| |
| encoding = portage._encodings["content"] |
| cmd = [ |
| portage._unicode_encode(x, encoding=encoding, errors="strict") |
| for x in cmd |
| ] |
| proc = subprocess.Popen( |
| cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT |
| ) |
| myoutput = proc.communicate()[0] |
| status = proc.wait() |
| if os.WIFEXITED(status) and os.WEXITSTATUS(status) == os.EX_OK: |
| for x in portage._unicode_decode( |
| myoutput, encoding=encoding, errors="strict" |
| ).split(): |
| try: |
| v.append(int(x)) |
| except ValueError: |
| pass |
| v = sorted(set(v)) |
| |
| # Avoid instantiating portage.settings when the desired |
| # variable is set in os.environ. |
| elif k in ("_portage_grpname", "_portage_username"): |
| v = None |
| if k == "_portage_grpname": |
| env_key = "PORTAGE_GRPNAME" |
| else: |
| env_key = "PORTAGE_USERNAME" |
| |
| if env_key in os.environ: |
| v = os.environ[env_key] |
| elif hasattr(portage, "settings"): |
| v = portage.settings.get(env_key) |
| else: |
| # The config class has equivalent code, but we also need to |
| # do it here if _disable_legacy_globals() has been called. |
| eroot_or_parent = first_existing( |
| os.path.join(_target_root(), _target_eprefix().lstrip(os.sep)) |
| ) |
| try: |
| eroot_st = os.stat(eroot_or_parent) |
| except OSError: |
| pass |
| else: |
| if _unprivileged_mode(eroot_or_parent, eroot_st): |
| if k == "_portage_grpname": |
| try: |
| grp_struct = grp.getgrgid(eroot_st.st_gid) |
| except KeyError: |
| pass |
| else: |
| v = grp_struct.gr_name |
| else: |
| try: |
| pwd_struct = pwd.getpwuid(eroot_st.st_uid) |
| except KeyError: |
| pass |
| else: |
| v = pwd_struct.pw_name |
| |
| if v is None: |
| v = "portage" |
| else: |
| raise AssertionError("unknown name: %s" % k) |
| |
| globals()[k] = v |
| _initialized_globals.add(k) |
| return v |
| |
| |
| class _GlobalProxy(portage.proxy.objectproxy.ObjectProxy): |
| |
| __slots__ = ("_name",) |
| |
| def __init__(self, name): |
| portage.proxy.objectproxy.ObjectProxy.__init__(self) |
| object.__setattr__(self, "_name", name) |
| |
| def _get_target(self): |
| return _get_global(object.__getattribute__(self, "_name")) |
| |
| |
| for k in ( |
| "portage_gid", |
| "portage_uid", |
| "secpass", |
| "userpriv_groups", |
| "_portage_grpname", |
| "_portage_username", |
| ): |
| globals()[k] = _GlobalProxy(k) |
| del k |
| |
| |
| def _init(settings): |
| """ |
| Use config variables like PORTAGE_GRPNAME and PORTAGE_USERNAME to |
| initialize global variables. This allows settings to come from make.conf |
| instead of requiring them to be set in the calling environment. |
| """ |
| if ( |
| "_portage_grpname" not in _initialized_globals |
| and "_portage_username" not in _initialized_globals |
| ): |
| |
| # Prevents "TypeError: expected string" errors |
| # from grp.getgrnam() with PyPy |
| native_string = platform.python_implementation() == "PyPy" |
| |
| v = settings.get("PORTAGE_GRPNAME", "portage") |
| if native_string: |
| v = portage._native_string(v) |
| globals()["_portage_grpname"] = v |
| _initialized_globals.add("_portage_grpname") |
| |
| v = settings.get("PORTAGE_USERNAME", "portage") |
| if native_string: |
| v = portage._native_string(v) |
| globals()["_portage_username"] = v |
| _initialized_globals.add("_portage_username") |
| |
| if "secpass" not in _initialized_globals: |
| v = 0 |
| if uid == 0: |
| v = 2 |
| elif "unprivileged" in settings.features: |
| v = 2 |
| elif portage_gid in os.getgroups(): |
| v = 1 |
| globals()["secpass"] = v |
| _initialized_globals.add("secpass") |