from daklib.textutils import fix_maintainer, ParseMaintError
import daklib.lintian as lintian
import daklib.utils as utils
-from daklib.upload import InvalidHashException
+import daklib.upload
import apt_inst
import apt_pkg
from apt_pkg import version_compare
+import datetime
import errno
import os
import subprocess
"""exception raised by failing checks"""
pass
-class RejectStupidMaintainerException(Exception):
+class RejectExternalFilesMismatch(Reject):
"""exception raised by failing the external hashes check"""
def __str__(self):
return False
class SignatureAndHashesCheck(Check):
+ def check_replay(self, upload):
+ # Use private session as we want to remember having seen the .changes
+ # in all cases.
+ session = upload.session
+ history = SignatureHistory.from_signed_file(upload.changes)
+ r = history.query(session)
+ if r is not None:
+ raise Reject('Signature for changes file was already seen at {0}.\nPlease refresh the signature of the changes file if you want to upload it again.'.format(r.seen))
+ return True
+
"""Check signature of changes and dsc file (if included in upload)
Make sure the signature is valid and done by a known user.
changes = upload.changes
if not changes.valid_signature:
raise Reject("Signature for .changes not valid.")
+ self.check_replay(upload)
self._check_hashes(upload, changes.filename, changes.files.itervalues())
source = None
try:
for f in files:
f.check(upload.directory)
- except IOError as e:
- if e.errno == errno.ENOENT:
- raise Reject('{0} refers to non-existing file: {1}\n'
- 'Perhaps you need to include it in your upload?'
- .format(filename, os.path.basename(e.filename)))
- raise
- except InvalidHashException as e:
+ except daklib.upload.FileDoesNotExist as e:
+ raise Reject('{0}: {1}\n'
+ 'Perhaps you need to include the file in your upload?'
+ .format(filename, unicode(e)))
+ except daklib.upload.UploadException as e:
raise Reject('{0}: {1}'.format(filename, unicode(e)))
+class SignatureTimestampCheck(Check):
+ """Check timestamp of .changes signature"""
+ def check(self, upload):
+ changes = upload.changes
+
+ now = datetime.datetime.utcnow()
+ timestamp = changes.signature_timestamp
+ age = now - timestamp
+
+ age_max = datetime.timedelta(days=365)
+ age_min = datetime.timedelta(days=-7)
+
+ if age > age_max:
+ raise Reject('{0}: Signature from {1} is too old (maximum age is {2} days)'.format(changes.filename, timestamp, age_max.days))
+ if age < age_min:
+ raise Reject('{0}: Signature from {1} is too far in the future (tolerance is {2} days)'.format(changes.filename, timestamp, abs(age_min.days)))
+
+ return True
+
class ChangesCheck(Check):
"""Check changes file for syntax errors."""
def check(self, upload):
return
if ext_size != f.size:
- raise RejectStupidMaintainerException(f.filename, 'size', f.size, ext_size)
+ raise RejectExternalFilesMismatch(f.filename, 'size', f.size, ext_size)
if ext_md5sum != f.md5sum:
- raise RejectStupidMaintainerException(f.filename, 'md5sum', f.md5sum, ext_md5sum)
+ raise RejectExternalFilesMismatch(f.filename, 'md5sum', f.md5sum, ext_md5sum)
if ext_sha1sum != f.sha1sum:
- raise RejectStupidMaintainerException(f.filename, 'sha1sum', f.sha1sum, ext_sha1sum)
+ raise RejectExternalFilesMismatch(f.filename, 'sha1sum', f.sha1sum, ext_sha1sum)
if ext_sha256sum != f.sha256sum:
- raise RejectStupidMaintainerException(f.filename, 'sha256sum', f.sha256sum, ext_sha256sum)
+ raise RejectExternalFilesMismatch(f.filename, 'sha256sum', f.sha256sum, ext_sha256sum)
def check(self, upload):
cnf = Config()
class BinaryCheck(Check):
"""Check binary packages for syntax errors."""
def check(self, upload):
+ debug_deb_name_postfix = "-dbgsym"
+ # XXX: Handle dynamic debug section name here
+
for binary in upload.changes.binaries:
self.check_binary(upload, binary)
- binary_names = set([ binary.control['Package'] for binary in upload.changes.binaries ])
- for bn in binary_names:
- if bn not in upload.changes.binary_names:
- raise Reject('Package {0} is not mentioned in Binary field in changes'.format(bn))
+ binaries = {binary.control['Package']: binary
+ for binary in upload.changes.binaries}
+
+ for name, binary in binaries.items():
+ if name in upload.changes.binary_names:
+ # Package is listed in Binary field. Everything is good.
+ pass
+ elif daklib.utils.is_in_debug_section(binary.control):
+ # If we have a binary package in the debug section, we
+ # can allow it to not be present in the Binary field
+ # in the .changes file, so long as its name (without
+ # -dbgsym) is present in the Binary list.
+ if not name.endswith(debug_deb_name_postfix):
+ raise Reject('Package {0} is in the debug section, but '
+ 'does not end in {1}.'.format(name, debug_deb_name_postfix))
+
+ # Right, so, it's named properly, let's check that
+ # the corresponding package is in the Binary list
+ origin_package_name = name[:-len(debug_deb_name_postfix)]
+ if origin_package_name not in upload.changes.binary_names:
+ raise Reject(
+ "Debug package {debug}'s corresponding binary package "
+ "{origin} is not present in the Binary field.".format(
+ debug=name, origin=origin_package_name))
+ else:
+ # Someone was a nasty little hacker and put a package
+ # into the .changes that isn't in debian/control. Bad,
+ # bad person.
+ raise Reject('Package {0} is not mentioned in Binary field in changes'.format(name))
return True
fn = binary.hashed_file.filename
control = binary.control
- for field in ('Package', 'Architecture', 'Version', 'Description'):
+ for field in ('Package', 'Architecture', 'Version', 'Description', 'Section'):
if field not in control:
raise Reject('{0}: Missing mandatory field {0}.'.format(fn, field))
except:
raise Reject('{0}: APT could not parse {1} field'.format(fn, field))
+ # "Multi-Arch: no" breaks wanna-build, #768353
+ multi_arch = control.get("Multi-Arch")
+ if multi_arch == 'no':
+ raise Reject('{0}: Multi-Arch: no support in Debian is broken (#768353)'.format(fn))
+
class BinaryTimestampCheck(Check):
"""check timestamps of files in binary packages
if not allow_no_arch_indep_uploads \
and 'all' not in changes.architectures \
+ and 'experimental' not in changes.distributions \
+ and 'unstable' not in changes.distributions \
+ and 'sid' not in changes.distributions \
and changes.source.package_list.has_arch_indep_packages():
raise Reject('Uploads not including architecture-independent packages are not allowed.')
return True
+class ArchAllBinNMUCheck(Check):
+ """Check for arch:all binNMUs"""
+ def check(self, upload):
+ changes = upload.changes
+
+ if 'all' in changes.architectures and changes.changes.get('Binary-Only') == 'yes':
+ raise Reject('arch:all binNMUs are not allowed.')
+
+ return True
+
class LintianCheck(Check):
"""Check package using lintian"""
def check(self, upload):
if query.first() is None:
raise Reject('source format {0} is not allowed in suite {1}'.format(source_format, suite.suite_name))
+class SuiteCheck(Check):
+ def per_suite_check(self, upload, suite):
+ if not suite.accept_source_uploads and upload.changes.source is not None:
+ raise Reject('The suite "{0}" does not accept source uploads.'.format(suite.suite_name))
+ if not suite.accept_binary_uploads and len(upload.changes.binaries) != 0:
+ raise Reject('The suite "{0}" does not accept binary uploads.'.format(suite.suite_name))
+ return True
+
class SuiteArchitectureCheck(Check):
def per_suite_check(self, upload, suite):
session = upload.session