From: Joerg Jaspert Date: Sun, 16 Sep 2012 15:56:54 +0000 (+0200) Subject: Merge branch 'dak-unpriv' into merge X-Git-Url: https://git.decadent.org.uk/gitweb/?a=commitdiff_plain;h=56c0f386e8da9aaac70dcb60a5eb1ef9a9e38877;hp=-c;p=dak.git Merge branch 'dak-unpriv' into merge * dak-unpriv: update function docu make use of dak-unpriv Signed-off-by: Joerg Jaspert --- 56c0f386e8da9aaac70dcb60a5eb1ef9a9e38877 diff --combined daklib/archive.py index 5c98eeca,13cec358..dcdcc8e1 --- a/daklib/archive.py +++ b/daklib/archive.py @@@ -595,11 -595,6 +595,11 @@@ class ArchiveUpload(object) @type: bool """ + self._checked = False + """checks passes. set by C{check} + @type: bool + """ + self._new_queue = self.session.query(PolicyQueue).filter_by(queue_name='new').one() self._new = self._new_queue.suite @@@ -623,11 -618,12 +623,12 @@@ cnf = Config() session = self.transaction.session - self.directory = tempfile.mkdtemp(dir=cnf.get('Dir::TempPath')) + (None, self.directory) = utils.temp_dirname(parent=cnf.get('Dir::TempPath'), + mode=0o2750, cnf.unprivgroup) with FilesystemTransaction() as fs: src = os.path.join(self.original_directory, self.original_changes.filename) dst = os.path.join(self.directory, self.original_changes.filename) - fs.copy(src, dst) + fs.copy(src, dst, mode=0o640) self.changes = upload.Changes(self.directory, self.original_changes.filename, self.keyrings) @@@ -636,7 -632,7 +637,7 @@@ dst = os.path.join(self.directory, f.filename) if not os.path.exists(src): continue - fs.copy(src, dst) + fs.copy(src, dst, mode=0o640) source = self.changes.source if source is not None: @@@ -855,35 -851,28 +856,35 @@@ assert self.changes.valid_signature try: + # Validate signatures and hashes before we do any real work: for chk in ( checks.SignatureCheck, checks.ChangesCheck, - checks.TransitionCheck, - checks.UploadBlockCheck, checks.HashesCheck, checks.SourceCheck, checks.BinaryCheck, checks.BinaryTimestampCheck, - checks.ACLCheck, checks.SingleDistributionCheck, - checks.NoSourceOnlyCheck, - checks.LintianCheck, ): chk().check(self) final_suites = self._final_suites() if len(final_suites) == 0: - self.reject_reasons.append('Ended with no suite to install to.') + self.reject_reasons.append('No target suite found. Please check your target distribution and that you uploaded to the right archive.') return False + self.final_suites = final_suites + for chk in ( + checks.TransitionCheck, + checks.ACLCheck, + checks.NoSourceOnlyCheck, + checks.LintianCheck, + ): + chk().check(self) + + for chk in ( + checks.ACLCheck, checks.SourceFormatCheck, checks.SuiteArchitectureCheck, checks.VersionCheck, @@@ -894,7 -883,7 +895,7 @@@ if len(self.reject_reasons) != 0: return False - self.final_suites = final_suites + self._checked = True return True except checks.Reject as e: self.reject_reasons.append(unicode(e)) @@@ -1011,7 -1000,6 +1012,7 @@@ assert len(self.reject_reasons) == 0 assert self.changes.valid_signature assert self.final_suites is not None + assert self._checked byhand = self.changes.byhand_files if len(byhand) == 0: @@@ -1123,7 -1111,6 +1124,7 @@@ assert len(self.reject_reasons) == 0 assert self.changes.valid_signature assert self.final_suites is not None + assert self._checked assert not self.new db_changes = self._install_changes() @@@ -1173,22 -1160,16 +1174,22 @@@ binaries = self.changes.binaries byhand = self.changes.byhand_files - new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='new').one() - if len(byhand) > 0: - new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='byhand').one() - new_suite = new_queue.suite - # we need a suite to guess components suites = list(self.final_suites) assert len(suites) == 1, "NEW uploads must be to a single suite" suite = suites[0] + # decide which NEW queue to use + if suite.new_queue is None: + new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='new').one() + else: + new_queue = suite.new_queue + if len(byhand) > 0: + # There is only one global BYHAND queue + new_queue = self.transaction.session.query(PolicyQueue).filter_by(queue_name='byhand').one() + new_suite = new_queue.suite + + def binary_component_func(binary): return self._binary_component(suite, binary, only_overrides=False) diff --combined daklib/checks.py index 6a95049f,de180941..25cbb8f0 --- a/daklib/checks.py +++ b/daklib/checks.py @@@ -352,80 -352,28 +352,80 @@@ class SingleDistributionCheck(Check) class ACLCheck(Check): """Check the uploader is allowed to upload the packages in .changes""" - def _check_dm(self, upload): + + def _does_hijack(self, session, upload, suite): + # Try to catch hijacks. + # This doesn't work correctly. Uploads to experimental can still + # "hijack" binaries from unstable. Also one can hijack packages + # via buildds (but people who try this should not be DMs). + for binary_name in upload.changes.binary_names: + binaries = session.query(DBBinary).join(DBBinary.source) \ + .filter(DBBinary.suites.contains(suite)) \ + .filter(DBBinary.package == binary_name) + for binary in binaries: + if binary.source.source != upload.changes.changes['Source']: + return True, binary, binary.source.source + return False, None, None + + def _check_acl(self, session, upload, acl): + source_name = upload.changes.source_name + + if acl.match_fingerprint and upload.fingerprint not in acl.fingerprints: + return None, None + if acl.match_keyring is not None and upload.fingerprint.keyring != acl.match_keyring: + return None, None + + if not acl.allow_new: + if upload.new: + return False, "NEW uploads are not allowed" + for f in upload.changes.files.itervalues(): + if f.section == 'byhand' or f.section.startswith("raw-"): + return False, "BYHAND uploads are not allowed" + if not acl.allow_source and upload.changes.source is not None: + return False, "sourceful uploads are not allowed" + binaries = upload.changes.binaries + if len(binaries) != 0: + if not acl.allow_binary: + return False, "binary uploads are not allowed" + if upload.changes.source is None and not acl.allow_binary_only: + return False, "binary-only uploads are not allowed" + if not acl.allow_binary_all: + uploaded_arches = set(upload.changes.architectures) + uploaded_arches.discard('source') + allowed_arches = set(a.arch_string for a in acl.architectures) + forbidden_arches = uploaded_arches - allowed_arches + if len(forbidden_arches) != 0: + return False, "uploads for architecture(s) {0} are not allowed".format(", ".join(forbidden_arches)) + if not acl.allow_hijack: + for suite in upload.final_suites: + does_hijack, hijacked_binary, hijacked_from = self._does_hijack(session, upload, suite) + if does_hijack: + return False, "hijacks are not allowed (binary={0}, other-source={1})".format(hijacked_binary, hijacked_from) + + acl_per_source = session.query(ACLPerSource).filter_by(acl=acl, fingerprint=upload.fingerprint, source=source_name).first() + if acl.allow_per_source: + # XXX: Drop DMUA part here and switch to new implementation. + # XXX: Send warning mail once users can set the new DMUA flag + dmua_status, dmua_reason = self._check_dmua(upload) + if not dmua_status: + return False, dmua_reason + #if acl_per_source is None: + # return False, "not allowed to upload source package '{0}'".format(source_name) + if acl.deny_per_source and acl_per_source is not None: + return False, acl_per_source.reason or "forbidden to upload source package '{0}'".format(source_name) + + return True, None + + def _check_dmua(self, upload): # This code is not very nice, but hopefully works until we can replace # DM-Upload-Allowed, cf. https://lists.debian.org/debian-project/2012/06/msg00029.html session = upload.session - if 'source' not in upload.changes.architectures: - raise Reject('DM uploads must include source') - for f in upload.changes.files.itervalues(): - if f.section == 'byhand' or f.section[:4] == "raw-": - raise Reject("Uploading byhand packages is not allowed for DMs.") - - # Reject NEW packages - distributions = upload.changes.distributions - assert len(distributions) == 1 - suite = session.query(Suite).filter_by(suite_name=distributions[0]).one() - overridesuite = suite - if suite.overridesuite is not None: - overridesuite = session.query(Suite).filter_by(suite_name=suite.overridesuite).one() - if upload._check_new(overridesuite): - raise Reject('Uploading NEW packages is not allowed for DMs.') - # Check DM-Upload-Allowed + suites = upload.final_suites + assert len(suites) == 1 + suite = list(suites)[0] + last_suites = ['unstable', 'experimental'] if suite.suite_name.endswith('-backports'): last_suites = [suite.suite_name] @@@ -433,61 -381,84 +433,61 @@@ .join(DBSource.suites).filter(Suite.suite_name.in_(last_suites)) \ .order_by(DBSource.version.desc()).limit(1).first() if last is None: - raise Reject('No existing source found in {0}'.format(' or '.join(last_suites))) + return False, 'No existing source found in {0}'.format(' or '.join(last_suites)) if not last.dm_upload_allowed: - raise Reject('DM-Upload-Allowed is not set in {0}={1}'.format(last.source, last.version)) + return False, 'DM-Upload-Allowed is not set in {0}={1}'.format(last.source, last.version) # check current Changed-by is in last Maintainer or Uploaders uploader_names = [ u.name for u in last.uploaders ] changed_by_field = upload.changes.changes.get('Changed-By', upload.changes.changes['Maintainer']) if changed_by_field not in uploader_names: - raise Reject('{0} is not an uploader for {1}={2}'.format(changed_by_field, last.source, last.version)) + return False, '{0} is not an uploader for {1}={2}'.format(changed_by_field, last.source, last.version) # check Changed-by is the DM changed_by = fix_maintainer(changed_by_field) uid = upload.fingerprint.uid if uid is None: - raise Reject('Unknown uid for fingerprint {0}'.format(upload.fingerprint.fingerprint)) + return False, 'Unknown uid for fingerprint {0}'.format(upload.fingerprint.fingerprint) if uid.uid != changed_by[3] and uid.name != changed_by[2]: - raise Reject('DMs are not allowed to sponsor uploads (expected {0} <{1}> as maintainer, but got {2})'.format(uid.name, uid.uid, changed_by_field)) + return False, 'DMs are not allowed to sponsor uploads (expected {0} <{1}> as maintainer, but got {2})'.format(uid.name, uid.uid, changed_by_field) - # Try to catch hijacks. - # This doesn't work correctly. Uploads to experimental can still - # "hijack" binaries from unstable. Also one can hijack packages - # via buildds (but people who try this should not be DMs). - for binary_name in upload.changes.binary_names: - binaries = session.query(DBBinary).join(DBBinary.source) \ - .join(DBBinary.suites).filter(Suite.suite_name.in_(upload.changes.distributions)) \ - .filter(DBBinary.package == binary_name) - for binary in binaries: - if binary.source.source != upload.changes.changes['Source']: - raise Reject('DMs must not hijack binaries (binary={0}, other-source={1})'.format(binary_name, binary.source.source)) - - return True + return True, None def check(self, upload): + session = upload.session fingerprint = upload.fingerprint - source_acl = fingerprint.source_acl - if source_acl is None: - if 'source' in upload.changes.architectures: - raise Reject('Fingerprint {0} must not upload source'.format(fingerprint.fingerprint)) - elif source_acl.access_level == 'dm': - self._check_dm(upload) - elif source_acl.access_level != 'full': - raise Reject('Unknown source_acl access level {0} for fingerprint {1}'.format(source_acl.access_level, fingerprint.fingerprint)) - - bin_architectures = set(upload.changes.architectures) - bin_architectures.discard('source') - binary_acl = fingerprint.binary_acl - if binary_acl is None: - if len(bin_architectures) > 0: - raise Reject('Fingerprint {0} must not upload binary packages'.format(fingerprint.fingerprint)) - elif binary_acl.access_level == 'map': - query = upload.session.query(BinaryACLMap).filter_by(fingerprint=fingerprint) - allowed_architectures = [ m.architecture.arch_string for m in query ] - - for arch in upload.changes.architectures: - if arch not in allowed_architectures: - raise Reject('Fingerprint {0} must not upload binaries for architecture {1}'.format(fingerprint.fingerprint, arch)) - elif binary_acl.access_level != 'full': - raise Reject('Unknown binary_acl access level {0} for fingerprint {1}'.format(binary_acl.access_level, fingerprint.fingerprint)) + keyring = fingerprint.keyring - return True + if keyring is None: + raise Reject('No keyring for fingerprint {0}'.format(fingerprint.fingerprint)) + if not keyring.active: + raise Reject('Keyring {0} is not active'.format(keyring.name)) -class UploadBlockCheck(Check): - """check for upload blocks""" - def check(self, upload): - session = upload.session - control = upload.changes.changes + acl = fingerprint.acl or keyring.acl + if acl is None: + raise Reject('No ACL for fingerprint {0}'.format(fingerprint.fingerprint)) + result, reason = self._check_acl(session, upload, acl) + if not result: + raise Reject(reason) - source = re_field_source.match(control['Source']).group('package') - version = control['Version'] - blocks = session.query(UploadBlock).filter_by(source=source) \ - .filter((UploadBlock.version == version) | (UploadBlock.version == None)) + for acl in session.query(ACL).filter_by(is_global=True): + result, reason = self._check_acl(session, upload, acl) + if result == False: + raise Reject(reason) - for block in blocks: - if block.fingerprint == upload.fingerprint: - raise Reject('Manual upload block in place for package {0} and fingerprint {1}:\n{2}'.format(source, upload.fingerprint.fingerprint, block.reason)) - if block.uid == upload.fingerprint.uid: - raise Reject('Manual upload block in place for package {0} and uid {1}:\n{2}'.format(source, block.uid.uid, block.reason)) + return True + def per_suite_check(self, upload, suite): + acls = suite.acls + if len(acls) != 0: + accept = False + for acl in acls: + result, reason = self._check_acl(upload.session, upload, acl) + if result == False: + raise Reject(reason) + accept = accept or result + if not accept: + raise Reject('Not accepted by any per-suite acl (suite={0})'.format(suite.suite_name)) return True class TransitionCheck(Check): @@@ -595,7 -566,7 +595,7 @@@ class LintianCheck(Check) except yaml.YAMLError as msg: raise Exception('Could not read lintian tags file {0}, YAML error: {1}'.format(tagfile, msg)) - fd, temp_filename = utils.temp_filename() + fd, temp_filename = utils.temp_filename(mode=0o644) temptagfile = os.fdopen(fd, 'w') for tags in lintiantags.itervalues(): for tag in tags: @@@ -604,8 -575,10 +604,10 @@@ changespath = os.path.join(upload.directory, changes.filename) try: - # FIXME: no shell - cmd = "lintian --show-overrides --tags-from-file {0} {1}".format(temp_filename, changespath) + if cnf.unpribgroup: + cmd = "sudo -H -u {0} -- /usr/bin/lintian --show-overrides --tags-from-file {1} {2}".format(cnf.unprivgroup, temp_filename, changespath) + else: + cmd = "/usr/bin/lintian --show-overrides --tags-from-file {0} {1}".format(temp_filename, changespath) result, output = commands.getstatusoutput(cmd) finally: os.unlink(temp_filename)