Source code for pkgcheck.checks.acct

"""Various checks for acct-group and acct-user packages."""

import re
from collections import defaultdict
from configparser import ConfigParser
from functools import partial
from itertools import chain

from pkgcore.ebuild import restricts
from pkgcore.restrictions import packages
from snakeoil.osutils import pjoin

from .. import results, sources
from . import GentooRepoCheck, RepoCheck, SkipCheck


[docs] class MissingAccountIdentifier(results.VersionResult, results.Warning): """UID/GID can not be found in account package.""" def __init__(self, var, **kwargs): super().__init__(**kwargs) self.var = var @property def desc(self): return f"unable to determine the value of {self.var} variable"
[docs] class ConflictingAccountIdentifiers(results.Error): """Same UID/GID is used by multiple packages.""" def __init__(self, kind, identifier, pkgs): super().__init__() self.kind = kind self.identifier = identifier self.pkgs = tuple(pkgs) @property def desc(self): pkgs = ", ".join(self.pkgs) return f"conflicting {self.kind} id {self.identifier} usage: [ {pkgs} ]"
[docs] class OutsideRangeAccountIdentifier(results.VersionResult, results.Error): """UID/GID outside allowed allocation range. To view the range accepted for this repository, look at the file ``metadata/qa-policy.conf`` in the section ``user-group-ids``. """ def __init__(self, kind, identifier, **kwargs): super().__init__(**kwargs) self.kind = kind self.identifier = identifier @property def desc(self): return f"{self.kind} id {self.identifier} outside permitted " f"static allocation range"
[docs] class AcctCheck(GentooRepoCheck, RepoCheck): """Various checks for acct-* packages. Verify that acct-* packages do not use conflicting, invalid or out-of-range UIDs/GIDs. This check uses a special file ``metadata/qa-policy.conf`` located within the repository. It should contain a ``user-group-ids`` section containing two keys: ``uid-range`` and ``gid-range``, which consist of a comma separated list, either ``<n>`` for a single value or ``<m>-<n>`` for a range of values (including both ends). In case this file doesn't exist or is wrongly defined, this check is skipped. """ _restricted_source = ( sources.RestrictionRepoSource, ( packages.OrRestriction( *(restricts.CategoryDep("acct-user"), restricts.CategoryDep("acct-group")) ), ), ) _source = (sources.RepositoryRepoSource, (), (("source", _restricted_source),)) known_results = frozenset( [ MissingAccountIdentifier, ConflictingAccountIdentifiers, OutsideRangeAccountIdentifier, ] ) def __init__(self, *args): super().__init__(*args) self.id_re = re.compile( r'ACCT_(?P<var>USER|GROUP)_ID=(?P<quot>[\'"]?)(?P<id>[0-9]+)(?P=quot)' ) self.seen_uids = defaultdict(partial(defaultdict, list)) self.seen_gids = defaultdict(partial(defaultdict, list)) uid_range, gid_range = self.load_ids_from_configuration(self.options.target_repo) self.category_map = { "acct-user": (self.seen_uids, "USER", tuple(uid_range)), "acct-group": (self.seen_gids, "GROUP", tuple(gid_range)), }
[docs] def parse_config_id_range(self, config: ConfigParser, config_key: str): id_ranges = config["user-group-ids"].get(config_key, None) if not id_ranges: raise SkipCheck(self, f"metadata/qa-policy.conf: missing value for {config_key}") try: for id_range in map(str.strip, id_ranges.split(",")): start, *end = map(int, id_range.split("-", maxsplit=1)) if len(end) == 0: yield range(start, start + 1) else: yield range(start, end[0] + 1) except ValueError: raise SkipCheck(self, f"metadata/qa-policy.conf: invalid value for {config_key}")
[docs] def load_ids_from_configuration(self, repo): config = ConfigParser() if not config.read(pjoin(repo.location, "metadata", "qa-policy.conf")): raise SkipCheck(self, "failed loading 'metadata/qa-policy.conf'") if "user-group-ids" not in config: raise SkipCheck(self, "metadata/qa-policy.conf: missing section user-group-ids") return self.parse_config_id_range(config, "uid-range"), self.parse_config_id_range( config, "gid-range" )
[docs] def feed(self, pkg): try: seen_id_map, expected_var, allowed_ids = self.category_map[pkg.category] except KeyError: return for line in pkg.ebuild.text_fileobj(): m = self.id_re.match(line) if m is not None and m.group("var") == expected_var: found_id = int(m.group("id")) break else: yield MissingAccountIdentifier(f"ACCT_{expected_var}_ID", pkg=pkg) return if not any(found_id in id_range for id_range in allowed_ids): yield OutsideRangeAccountIdentifier(expected_var.lower(), found_id, pkg=pkg) return seen_id_map[found_id][pkg.key].append(pkg)
[docs] def finish(self): # report overlapping ID usage for seen, expected_var, _ids in self.category_map.values(): for found_id, pkgs in sorted(seen.items()): if len(pkgs) > 1: pkgs = (x.cpvstr for x in sorted(chain.from_iterable(pkgs.values()))) yield ConflictingAccountIdentifiers(expected_var.lower(), found_id, pkgs)