4 # Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006 James Troup <james@nocrew.org>
6 ################################################################################
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 ################################################################################
24 import codecs, commands, email.Header, os, pwd, re, select, socket, shutil, \
25 sys, tempfile, traceback
28 from dak_exceptions import *
30 ################################################################################
32 re_comments = re.compile(r"\#.*")
33 re_no_epoch = re.compile(r"^\d+\:")
34 re_no_revision = re.compile(r"-[^-]+$")
35 re_arch_from_filename = re.compile(r"/binary-[^/]+/")
36 re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
37 re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$")
38 re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$")
40 re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)")
41 re_multi_line_field = re.compile(r"^\s(.*)")
42 re_taint_free = re.compile(r"^[-+~/\.\w]+$")
44 re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>")
45 re_gpg_uid = re.compile('^uid.*<([^>]*)>')
47 re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
48 re_verwithext = re.compile(r"^(\d+)(?:\.(\d+))(?:\s+\((\S+)\))?$")
50 re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
52 default_config = "/etc/dak/dak.conf"
53 default_apt_config = "/etc/dak/apt.conf"
56 key_uid_email_cache = {}
58 # (hashname, function, earliest_changes_version)
59 known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
60 ("sha256", apt_pkg.sha256sum, (1, 8))]
62 ################################################################################
64 def open_file(filename, mode='r'):
66 f = open(filename, mode)
68 raise CantOpenError, filename
71 ################################################################################
73 def our_raw_input(prompt=""):
75 sys.stdout.write(prompt)
81 sys.stderr.write("\nUser interrupt (^D).\n")
84 ################################################################################
86 def extract_component_from_section(section):
89 if section.find('/') != -1:
90 component = section.split('/')[0]
92 # Expand default component
94 if Cnf.has_key("Component::%s" % section):
99 return (section, component)
101 ################################################################################
103 def parse_changes(filename, signing_rules=0):
104 """Parses a changes file and returns a dictionary where each field is a
105 key. The mandatory first argument is the filename of the .changes
108 signing_rules is an optional argument:
110 o If signing_rules == -1, no signature is required.
111 o If signing_rules == 0 (the default), a signature is required.
112 o If signing_rules == 1, it turns on the same strict format checking
115 The rules for (signing_rules == 1)-mode are:
117 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
118 followed by any PGP header data and must end with a blank line.
120 o The data section must end with a blank line and must be followed by
121 "-----BEGIN PGP SIGNATURE-----".
127 changes_in = open_file(filename)
128 lines = changes_in.readlines()
131 raise ParseChangesError, "[Empty changes file]"
133 # Reindex by line number so we can easily verify the format of
139 indexed_lines[index] = line[:-1]
143 num_of_lines = len(indexed_lines.keys())
146 while index < num_of_lines:
148 line = indexed_lines[index]
150 if signing_rules == 1:
152 if index > num_of_lines:
153 raise InvalidDscError, index
154 line = indexed_lines[index]
155 if not line.startswith("-----BEGIN PGP SIGNATURE"):
156 raise InvalidDscError, index
161 if line.startswith("-----BEGIN PGP SIGNATURE"):
163 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
165 if signing_rules == 1:
166 while index < num_of_lines and line != "":
168 line = indexed_lines[index]
170 # If we're not inside the signed data, don't process anything
171 if signing_rules >= 0 and not inside_signature:
173 slf = re_single_line_field.match(line)
175 field = slf.groups()[0].lower()
176 changes[field] = slf.groups()[1]
180 changes[field] += '\n'
182 mlf = re_multi_line_field.match(line)
185 raise ParseChangesError, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
186 if first == 1 and changes[field] != "":
187 changes[field] += '\n'
189 changes[field] += mlf.groups()[0] + '\n'
193 if signing_rules == 1 and inside_signature:
194 raise InvalidDscError, index
197 changes["filecontents"] = "".join(lines)
199 if changes.has_key("source"):
200 # Strip the source version in brackets from the source field,
201 # put it in the "source-version" field instead.
202 srcver = re_srchasver.search(changes["source"])
204 changes["source"] = srcver.group(1)
205 changes["source-version"] = srcver.group(2)
208 raise ParseChangesError, error
212 ################################################################################
214 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
216 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
219 # Make sure we have a Files: field to parse...
220 if not changes.has_key(field):
221 raise NoFilesFieldError
223 # Make sure we recognise the format of the Files: field
224 format = re_verwithext.search(changes.get("format", "0.0"))
226 raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
228 format = format.groups()
229 if format[1] == None:
230 format = int(float(format[0])), 0, format[2]
232 format = int(format[0]), int(format[1]), format[2]
233 if format[2] == None:
238 raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
240 if (format < (1,5) or format > (1,8)):
241 raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
242 if field != "files" and format < (1,8):
243 raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
245 includes_section = (not is_a_dsc) and field == "files"
247 # Parse each entry/line:
248 for i in changes[field].split('\n'):
252 section = priority = ""
255 (md5, size, section, priority, name) = s
257 (md5, size, name) = s
259 raise ParseChangesError, i
266 (section, component) = extract_component_from_section(section)
268 files[name] = Dict(size=size, section=section,
269 priority=priority, component=component)
270 files[name][hashname] = md5
274 ################################################################################
276 def force_to_utf8(s):
277 """Forces a string to UTF-8. If the string isn't already UTF-8,
278 it's assumed to be ISO-8859-1."""
283 latin1_s = unicode(s,'iso8859-1')
284 return latin1_s.encode('utf-8')
286 def rfc2047_encode(s):
287 """Encodes a (header) string per RFC2047 if necessary. If the
288 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
290 codecs.lookup('ascii')[1](s)
295 codecs.lookup('utf-8')[1](s)
296 h = email.Header.Header(s, 'utf-8', 998)
299 h = email.Header.Header(s, 'iso-8859-1', 998)
302 ################################################################################
304 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
305 # with it. I know - I'll fix the suckage and make things
308 def fix_maintainer (maintainer):
309 """Parses a Maintainer or Changed-By field and returns:
310 (1) an RFC822 compatible version,
311 (2) an RFC2047 compatible version,
315 The name is forced to UTF-8 for both (1) and (3). If the name field
316 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
317 switched to 'email (name)' format."""
318 maintainer = maintainer.strip()
320 return ('', '', '', '')
322 if maintainer.find("<") == -1:
325 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
326 email = maintainer[1:-1]
329 m = re_parse_maintainer.match(maintainer)
331 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
335 # Get an RFC2047 compliant version of the name
336 rfc2047_name = rfc2047_encode(name)
338 # Force the name to be UTF-8
339 name = force_to_utf8(name)
341 if name.find(',') != -1 or name.find('.') != -1:
342 rfc822_maint = "%s (%s)" % (email, name)
343 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
345 rfc822_maint = "%s <%s>" % (name, email)
346 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
348 if email.find("@") == -1 and email.find("buildd_") != 0:
349 raise ParseMaintError, "No @ found in email address part."
351 return (rfc822_maint, rfc2047_maint, name, email)
353 ################################################################################
355 # sendmail wrapper, takes _either_ a message string or a file as arguments
356 def send_mail (message, filename=""):
357 # If we've been passed a string dump it into a temporary file
359 filename = tempfile.mktemp()
360 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
361 os.write (fd, message)
365 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
367 raise SendmailFailedError, output
369 # Clean up any temporary files
373 ################################################################################
375 def poolify (source, component):
378 if source[:3] == "lib":
379 return component + source[:4] + '/' + source + '/'
381 return component + source[:1] + '/' + source + '/'
383 ################################################################################
385 def move (src, dest, overwrite = 0, perms = 0664):
386 if os.path.exists(dest) and os.path.isdir(dest):
389 dest_dir = os.path.dirname(dest)
390 if not os.path.exists(dest_dir):
391 umask = os.umask(00000)
392 os.makedirs(dest_dir, 02775)
394 #print "Moving %s to %s..." % (src, dest)
395 if os.path.exists(dest) and os.path.isdir(dest):
396 dest += '/' + os.path.basename(src)
397 # Don't overwrite unless forced to
398 if os.path.exists(dest):
400 fubar("Can't move %s to %s - file already exists." % (src, dest))
402 if not os.access(dest, os.W_OK):
403 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
404 shutil.copy2(src, dest)
405 os.chmod(dest, perms)
408 def copy (src, dest, overwrite = 0, perms = 0664):
409 if os.path.exists(dest) and os.path.isdir(dest):
412 dest_dir = os.path.dirname(dest)
413 if not os.path.exists(dest_dir):
414 umask = os.umask(00000)
415 os.makedirs(dest_dir, 02775)
417 #print "Copying %s to %s..." % (src, dest)
418 if os.path.exists(dest) and os.path.isdir(dest):
419 dest += '/' + os.path.basename(src)
420 # Don't overwrite unless forced to
421 if os.path.exists(dest):
423 raise FileExistsError
425 if not os.access(dest, os.W_OK):
426 raise CantOverwriteError
427 shutil.copy2(src, dest)
428 os.chmod(dest, perms)
430 ################################################################################
433 res = socket.gethostbyaddr(socket.gethostname())
434 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
435 if database_hostname:
436 return database_hostname
440 def which_conf_file ():
441 res = socket.gethostbyaddr(socket.gethostname())
442 if Cnf.get("Config::" + res[0] + "::DakConfig"):
443 return Cnf["Config::" + res[0] + "::DakConfig"]
445 return default_config
447 def which_apt_conf_file ():
448 res = socket.gethostbyaddr(socket.gethostname())
449 if Cnf.get("Config::" + res[0] + "::AptConfig"):
450 return Cnf["Config::" + res[0] + "::AptConfig"]
452 return default_apt_config
454 def which_alias_file():
455 hostname = socket.gethostbyaddr(socket.gethostname())[0]
456 aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
457 if os.path.exists(aliasfn):
462 ################################################################################
464 # Escape characters which have meaning to SQL's regex comparison operator ('~')
465 # (woefully incomplete)
468 s = s.replace('+', '\\\\+')
469 s = s.replace('.', '\\\\.')
472 ################################################################################
474 # Perform a substition of template
475 def TemplateSubst(map, filename):
476 file = open_file(filename)
477 template = file.read()
479 template = template.replace(x,map[x])
483 ################################################################################
485 def fubar(msg, exit_code=1):
486 sys.stderr.write("E: %s\n" % (msg))
490 sys.stderr.write("W: %s\n" % (msg))
492 ################################################################################
494 # Returns the user name with a laughable attempt at rfc822 conformancy
495 # (read: removing stray periods).
497 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
499 ################################################################################
509 return ("%d%s" % (c, t))
511 ################################################################################
513 def cc_fix_changes (changes):
514 o = changes.get("architecture", "")
516 del changes["architecture"]
517 changes["architecture"] = {}
519 changes["architecture"][j] = 1
521 # Sort by source name, source version, 'have source', and then by filename
522 def changes_compare (a, b):
524 a_changes = parse_changes(a)
529 b_changes = parse_changes(b)
533 cc_fix_changes (a_changes)
534 cc_fix_changes (b_changes)
536 # Sort by source name
537 a_source = a_changes.get("source")
538 b_source = b_changes.get("source")
539 q = cmp (a_source, b_source)
543 # Sort by source version
544 a_version = a_changes.get("version", "0")
545 b_version = b_changes.get("version", "0")
546 q = apt_pkg.VersionCompare(a_version, b_version)
550 # Sort by 'have source'
551 a_has_source = a_changes["architecture"].get("source")
552 b_has_source = b_changes["architecture"].get("source")
553 if a_has_source and not b_has_source:
555 elif b_has_source and not a_has_source:
558 # Fall back to sort by filename
561 ################################################################################
563 def find_next_free (dest, too_many=100):
566 while os.path.exists(dest) and extra < too_many:
567 dest = orig_dest + '.' + repr(extra)
569 if extra >= too_many:
570 raise NoFreeFilenameError
573 ################################################################################
575 def result_join (original, sep = '\t'):
577 for i in xrange(len(original)):
578 if original[i] == None:
581 list.append(original[i])
582 return sep.join(list)
584 ################################################################################
586 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
588 for line in str.split('\n'):
590 if line or include_blank_lines:
591 out += "%s%s\n" % (prefix, line)
592 # Strip trailing new line
597 ################################################################################
599 def validate_changes_file_arg(filename, require_changes=1):
600 """'filename' is either a .changes or .dak file. If 'filename' is a
601 .dak file, it's changed to be the corresponding .changes file. The
602 function then checks if the .changes file a) exists and b) is
603 readable and returns the .changes filename if so. If there's a
604 problem, the next action depends on the option 'require_changes'
607 o If 'require_changes' == -1, errors are ignored and the .changes
608 filename is returned.
609 o If 'require_changes' == 0, a warning is given and 'None' is returned.
610 o If 'require_changes' == 1, a fatal error is raised.
614 orig_filename = filename
615 if filename.endswith(".dak"):
616 filename = filename[:-4]+".changes"
618 if not filename.endswith(".changes"):
619 error = "invalid file type; not a changes file"
621 if not os.access(filename,os.R_OK):
622 if os.path.exists(filename):
623 error = "permission denied"
625 error = "file not found"
628 if require_changes == 1:
629 fubar("%s: %s." % (orig_filename, error))
630 elif require_changes == 0:
631 warn("Skipping %s - %s" % (orig_filename, error))
633 else: # We only care about the .dak file
638 ################################################################################
641 return (arch != "source" and arch != "all")
643 ################################################################################
645 def join_with_commas_and(list):
646 if len(list) == 0: return "nothing"
647 if len(list) == 1: return list[0]
648 return ", ".join(list[:-1]) + " and " + list[-1]
650 ################################################################################
655 (pkg, version, constraint) = atom
657 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
660 pp_deps.append(pp_dep)
661 return " |".join(pp_deps)
663 ################################################################################
668 ################################################################################
670 # Handle -a, -c and -s arguments; returns them as SQL constraints
671 def parse_args(Options):
675 for suite in split_args(Options["Suite"]):
676 suite_id = database.get_suite_id(suite)
678 warn("suite '%s' not recognised." % (suite))
680 suite_ids_list.append(suite_id)
682 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
684 fubar("No valid suite given.")
689 if Options["Component"]:
690 component_ids_list = []
691 for component in split_args(Options["Component"]):
692 component_id = database.get_component_id(component)
693 if component_id == -1:
694 warn("component '%s' not recognised." % (component))
696 component_ids_list.append(component_id)
697 if component_ids_list:
698 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
700 fubar("No valid component given.")
704 # Process architecture
705 con_architectures = ""
706 if Options["Architecture"]:
709 for architecture in split_args(Options["Architecture"]):
710 if architecture == "source":
713 architecture_id = database.get_architecture_id(architecture)
714 if architecture_id == -1:
715 warn("architecture '%s' not recognised." % (architecture))
717 arch_ids_list.append(architecture_id)
719 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
722 fubar("No valid architecture given.")
726 return (con_suites, con_architectures, con_components, check_source)
728 ################################################################################
730 # Inspired(tm) by Bryn Keller's print_exc_plus (See
731 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
734 tb = sys.exc_info()[2]
743 traceback.print_exc()
745 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
746 frame.f_code.co_filename,
748 for key, value in frame.f_locals.items():
749 print "\t%20s = " % key,
753 print "<unable to print>"
755 ################################################################################
757 def try_with_debug(function):
765 ################################################################################
767 # Function for use in sorting lists of architectures.
768 # Sorts normally except that 'source' dominates all others.
770 def arch_compare_sw (a, b):
771 if a == "source" and b == "source":
780 ################################################################################
782 # Split command line arguments which can be separated by either commas
783 # or whitespace. If dwim is set, it will complain about string ending
784 # in comma since this usually means someone did 'dak ls -a i386, m68k
785 # foo' or something and the inevitable confusion resulting from 'm68k'
786 # being treated as an argument is undesirable.
788 def split_args (s, dwim=1):
789 if s.find(",") == -1:
792 if s[-1:] == "," and dwim:
793 fubar("split_args: found trailing comma, spurious space maybe?")
796 ################################################################################
798 def Dict(**dict): return dict
800 ########################################
802 # Our very own version of commands.getouputstatus(), hacked to support
804 def gpgv_get_status_output(cmd, status_read, status_write):
805 cmd = ['/bin/sh', '-c', cmd]
806 p2cread, p2cwrite = os.pipe()
807 c2pread, c2pwrite = os.pipe()
808 errout, errin = os.pipe()
818 for i in range(3, 256):
819 if i != status_write:
825 os.execvp(cmd[0], cmd)
831 os.dup2(c2pread, c2pwrite)
832 os.dup2(errout, errin)
836 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
839 r = os.read(fd, 8196)
842 if fd == c2pwrite or fd == errin:
844 elif fd == status_read:
847 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
849 pid, exit_status = os.waitpid(pid, 0)
851 os.close(status_write)
852 os.close(status_read)
862 return output, status, exit_status
864 ################################################################################
866 def process_gpgv_output(status):
867 # Process the status-fd output
870 for line in status.split('\n'):
876 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
878 (gnupg, keyword) = split[:2]
879 if gnupg != "[GNUPG:]":
880 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
883 if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
884 internal_error += "found duplicate status token ('%s').\n" % (keyword)
887 keywords[keyword] = args
889 return (keywords, internal_error)
891 ################################################################################
893 def retrieve_key (filename, keyserver=None, keyring=None):
894 """Retrieve the key that signed 'filename' from 'keyserver' and
895 add it to 'keyring'. Returns nothing on success, or an error message
898 # Defaults for keyserver and keyring
900 keyserver = Cnf["Dinstall::KeyServer"]
902 keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
904 # Ensure the filename contains no shell meta-characters or other badness
905 if not re_taint_free.match(filename):
906 return "%s: tainted filename" % (filename)
908 # Invoke gpgv on the file
909 status_read, status_write = os.pipe();
910 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
911 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
913 # Process the status-fd output
914 (keywords, internal_error) = process_gpgv_output(status)
916 return internal_error
918 if not keywords.has_key("NO_PUBKEY"):
919 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
921 fingerprint = keywords["NO_PUBKEY"][0]
922 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
923 # it'll try to create a lockfile in /dev. A better solution might
924 # be a tempfile or something.
925 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
926 % (Cnf["Dinstall::SigningKeyring"])
927 cmd += " --keyring %s --keyserver %s --recv-key %s" \
928 % (keyring, keyserver, fingerprint)
929 (result, output) = commands.getstatusoutput(cmd)
931 return "'%s' failed with exit code %s" % (cmd, result)
935 ################################################################################
937 def gpg_keyring_args(keyrings=None):
939 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
941 return " ".join(["--keyring %s" % x for x in keyrings])
943 ################################################################################
945 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
946 """Check the signature of a file and return the fingerprint if the
947 signature is valid or 'None' if it's not. The first argument is the
948 filename whose signature should be checked. The second argument is a
949 reject function and is called when an error is found. The reject()
950 function must allow for two arguments: the first is the error message,
951 the second is an optional prefix string. It's possible for reject()
952 to be called more than once during an invocation of check_signature().
953 The third argument is optional and is the name of the files the
954 detached signature applies to. The fourth argument is optional and is
955 a *list* of keyrings to use. 'autofetch' can either be None, True or
956 False. If None, the default behaviour specified in the config will be
959 # Ensure the filename contains no shell meta-characters or other badness
960 if not re_taint_free.match(sig_filename):
961 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
964 if data_filename and not re_taint_free.match(data_filename):
965 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
969 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
971 # Autofetch the signing key if that's enabled
972 if autofetch == None:
973 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
975 error_msg = retrieve_key(sig_filename)
980 # Build the command line
981 status_read, status_write = os.pipe();
982 cmd = "gpgv --status-fd %s %s %s %s" % (
983 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
985 # Invoke gpgv on the file
986 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
988 # Process the status-fd output
989 (keywords, internal_error) = process_gpgv_output(status)
991 # If we failed to parse the status-fd output, let's just whine and bail now
993 reject("internal error while performing signature check on %s." % (sig_filename))
994 reject(internal_error, "")
995 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
999 # Now check for obviously bad things in the processed output
1000 if keywords.has_key("KEYREVOKED"):
1001 reject("The key used to sign %s has been revoked." % (sig_filename))
1003 if keywords.has_key("BADSIG"):
1004 reject("bad signature on %s." % (sig_filename))
1006 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1007 reject("failed to check signature on %s." % (sig_filename))
1009 if keywords.has_key("NO_PUBKEY"):
1010 args = keywords["NO_PUBKEY"]
1013 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1015 if keywords.has_key("BADARMOR"):
1016 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1018 if keywords.has_key("NODATA"):
1019 reject("no signature found in %s." % (sig_filename))
1021 if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1022 args = keywords["KEYEXPIRED"]
1025 reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
1031 # Next check gpgv exited with a zero return code
1033 reject("gpgv failed while checking %s." % (sig_filename))
1035 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1037 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1040 # Sanity check the good stuff we expect
1041 if not keywords.has_key("VALIDSIG"):
1042 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1045 args = keywords["VALIDSIG"]
1047 reject("internal error while checking signature on %s." % (sig_filename))
1050 fingerprint = args[0]
1051 if not keywords.has_key("GOODSIG"):
1052 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1054 if not keywords.has_key("SIG_ID"):
1055 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1058 # Finally ensure there's not something we don't recognise
1059 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1060 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1061 NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1063 for keyword in keywords.keys():
1064 if not known_keywords.has_key(keyword):
1065 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1073 ################################################################################
1075 def gpg_get_key_addresses(fingerprint):
1076 """retreive email addresses from gpg key uids for a given fingerprint"""
1077 addresses = key_uid_email_cache.get(fingerprint)
1078 if addresses != None:
1081 cmd = "gpg --no-default-keyring %s --fingerprint %s" \
1082 % (gpg_keyring_args(), fingerprint)
1083 (result, output) = commands.getstatusoutput(cmd)
1085 for l in output.split('\n'):
1086 m = re_gpg_uid.match(l)
1088 addresses.add(m.group(1))
1089 key_uid_email_cache[fingerprint] = addresses
1092 ################################################################################
1094 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1096 def wrap(paragraph, max_length, prefix=""):
1100 words = paragraph.split()
1103 word_size = len(word)
1104 if word_size > max_length:
1106 s += line + '\n' + prefix
1107 s += word + '\n' + prefix
1110 new_length = len(line) + word_size + 1
1111 if new_length > max_length:
1112 s += line + '\n' + prefix
1125 ################################################################################
1127 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1128 # Returns fixed 'src'
1129 def clean_symlink (src, dest, root):
1130 src = src.replace(root, '', 1)
1131 dest = dest.replace(root, '', 1)
1132 dest = os.path.dirname(dest)
1133 new_src = '../' * len(dest.split('/'))
1134 return new_src + src
1136 ################################################################################
1138 def temp_filename(directory=None, dotprefix=None, perms=0700):
1139 """Return a secure and unique filename by pre-creating it.
1140 If 'directory' is non-null, it will be the directory the file is pre-created in.
1141 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1144 old_tempdir = tempfile.tempdir
1145 tempfile.tempdir = directory
1147 filename = tempfile.mktemp()
1150 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1151 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1155 tempfile.tempdir = old_tempdir
1159 ################################################################################
1161 # checks if the user part of the email is listed in the alias file
1163 def is_email_alias(email):
1165 if alias_cache == None:
1166 aliasfn = which_alias_file()
1169 for l in open(aliasfn):
1170 alias_cache.add(l.split(':')[0])
1171 uid = email.split('@')[0]
1172 return uid in alias_cache
1174 ################################################################################
1178 Cnf = apt_pkg.newConfiguration()
1179 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1181 if which_conf_file() != default_config:
1182 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1184 ################################################################################