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
29 ################################################################################
31 re_comments = re.compile(r"\#.*")
32 re_no_epoch = re.compile(r"^\d+\:")
33 re_no_revision = re.compile(r"-[^-]+$")
34 re_arch_from_filename = re.compile(r"/binary-[^/]+/")
35 re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
36 re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$")
37 re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$")
39 re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)")
40 re_multi_line_field = re.compile(r"^\s(.*)")
41 re_taint_free = re.compile(r"^[-+~/\.\w]+$")
43 re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>")
45 re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
47 changes_parse_error_exc = "Can't parse line in .changes file"
48 invalid_dsc_format_exc = "Invalid .dsc file"
49 nk_format_exc = "Unknown Format: in .changes file"
50 no_files_exc = "No Files: field in .dsc or .changes file."
51 cant_open_exc = "Can't open file"
52 unknown_hostname_exc = "Unknown hostname"
53 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
54 file_exists_exc = "Destination file exists"
55 sendmail_failed_exc = "Sendmail invocation failed"
56 tried_too_hard_exc = "Tried too hard to find a free filename."
58 default_config = "/etc/dak/dak.conf"
59 default_apt_config = "/etc/dak/apt.conf"
61 ################################################################################
63 class Error(Exception):
64 """Base class for exceptions in this module."""
67 class ParseMaintError(Error):
68 """Exception raised for errors in parsing a maintainer field.
71 message -- explanation of the error
74 def __init__(self, message):
76 self.message = message
78 ################################################################################
80 def open_file(filename, mode='r'):
82 f = open(filename, mode)
84 raise cant_open_exc, filename
87 ################################################################################
89 def our_raw_input(prompt=""):
91 sys.stdout.write(prompt)
97 sys.stderr.write("\nUser interrupt (^D).\n")
100 ################################################################################
102 def extract_component_from_section(section):
105 if section.find('/') != -1:
106 component = section.split('/')[0]
107 if component.lower() == "non-us" and section.find('/') != -1:
108 s = component + '/' + section.split('/')[1]
109 if Cnf.has_key("Component::%s" % s): # Avoid e.g. non-US/libs
112 if section.lower() == "non-us":
113 component = "non-US/main"
115 # non-US prefix is case insensitive
116 if component.lower()[:6] == "non-us":
117 component = "non-US"+component[6:]
119 # Expand default component
121 if Cnf.has_key("Component::%s" % section):
125 elif component == "non-US":
126 component = "non-US/main"
128 return (section, component)
130 ################################################################################
132 def parse_changes(filename, signing_rules=0):
133 """Parses a changes file and returns a dictionary where each field is a
134 key. The mandatory first argument is the filename of the .changes
137 signing_rules is an optional argument:
139 o If signing_rules == -1, no signature is required.
140 o If signing_rules == 0 (the default), a signature is required.
141 o If signing_rules == 1, it turns on the same strict format checking
144 The rules for (signing_rules == 1)-mode are:
146 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
147 followed by any PGP header data and must end with a blank line.
149 o The data section must end with a blank line and must be followed by
150 "-----BEGIN PGP SIGNATURE-----".
156 changes_in = open_file(filename)
157 lines = changes_in.readlines()
160 raise changes_parse_error_exc, "[Empty changes file]"
162 # Reindex by line number so we can easily verify the format of
168 indexed_lines[index] = line[:-1]
172 num_of_lines = len(indexed_lines.keys())
175 while index < num_of_lines:
177 line = indexed_lines[index]
179 if signing_rules == 1:
181 if index > num_of_lines:
182 raise invalid_dsc_format_exc, index
183 line = indexed_lines[index]
184 if not line.startswith("-----BEGIN PGP SIGNATURE"):
185 raise invalid_dsc_format_exc, index
190 if line.startswith("-----BEGIN PGP SIGNATURE"):
192 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
194 if signing_rules == 1:
195 while index < num_of_lines and line != "":
197 line = indexed_lines[index]
199 # If we're not inside the signed data, don't process anything
200 if signing_rules >= 0 and not inside_signature:
202 slf = re_single_line_field.match(line)
204 field = slf.groups()[0].lower()
205 changes[field] = slf.groups()[1]
209 changes[field] += '\n'
211 mlf = re_multi_line_field.match(line)
214 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
215 if first == 1 and changes[field] != "":
216 changes[field] += '\n'
218 changes[field] += mlf.groups()[0] + '\n'
222 if signing_rules == 1 and inside_signature:
223 raise invalid_dsc_format_exc, index
226 changes["filecontents"] = "".join(lines)
228 if changes.has_key("source"):
229 # Strip the source version in brackets from the source field,
230 # put it in the "source-version" field instead.
231 srcver = re_srchasver.search(changes["source"])
233 changes["source"] = srcver.group(1)
234 changes["source-version"] = srcver.group(2)
237 raise changes_parse_error_exc, error
241 ################################################################################
243 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
245 def build_file_list(changes, is_a_dsc=0):
248 # Make sure we have a Files: field to parse...
249 if not changes.has_key("files"):
252 # Make sure we recognise the format of the Files: field
253 format = changes.get("format", "")
255 format = float(format)
256 if not is_a_dsc and (format < 1.5 or format > 2.0):
257 raise nk_format_exc, format
259 # Parse each entry/line:
260 for i in changes["files"].split('\n'):
264 section = priority = ""
267 (md5, size, name) = s
269 (md5, size, section, priority, name) = s
271 raise changes_parse_error_exc, i
278 (section, component) = extract_component_from_section(section)
280 files[name] = Dict(md5sum=md5, size=size, section=section,
281 priority=priority, component=component)
285 ################################################################################
287 def force_to_utf8(s):
288 """Forces a string to UTF-8. If the string isn't already UTF-8,
289 it's assumed to be ISO-8859-1."""
294 latin1_s = unicode(s,'iso8859-1')
295 return latin1_s.encode('utf-8')
297 def rfc2047_encode(s):
298 """Encodes a (header) string per RFC2047 if necessary. If the
299 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
301 codecs.lookup('ascii')[1](s)
306 codecs.lookup('utf-8')[1](s)
307 h = email.Header.Header(s, 'utf-8', 998)
310 h = email.Header.Header(s, 'iso-8859-1', 998)
313 ################################################################################
315 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
316 # with it. I know - I'll fix the suckage and make things
319 def fix_maintainer (maintainer):
320 """Parses a Maintainer or Changed-By field and returns:
321 (1) an RFC822 compatible version,
322 (2) an RFC2047 compatible version,
326 The name is forced to UTF-8 for both (1) and (3). If the name field
327 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
328 switched to 'email (name)' format."""
329 maintainer = maintainer.strip()
331 return ('', '', '', '')
333 if maintainer.find("<") == -1:
336 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
337 email = maintainer[1:-1]
340 m = re_parse_maintainer.match(maintainer)
342 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
346 # Get an RFC2047 compliant version of the name
347 rfc2047_name = rfc2047_encode(name)
349 # Force the name to be UTF-8
350 name = force_to_utf8(name)
352 if name.find(',') != -1 or name.find('.') != -1:
353 rfc822_maint = "%s (%s)" % (email, name)
354 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
356 rfc822_maint = "%s <%s>" % (name, email)
357 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
359 if email.find("@") == -1 and email.find("buildd_") != 0:
360 raise ParseMaintError, "No @ found in email address part."
362 return (rfc822_maint, rfc2047_maint, name, email)
364 ################################################################################
366 # sendmail wrapper, takes _either_ a message string or a file as arguments
367 def send_mail (message, filename=""):
368 # If we've been passed a string dump it into a temporary file
370 filename = tempfile.mktemp()
371 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
372 os.write (fd, message)
376 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
378 raise sendmail_failed_exc, output
380 # Clean up any temporary files
384 ################################################################################
386 def poolify (source, component):
389 # FIXME: this is nasty
390 component = component.lower().replace("non-us/", "non-US/")
391 if source[:3] == "lib":
392 return component + source[:4] + '/' + source + '/'
394 return component + source[:1] + '/' + source + '/'
396 ################################################################################
398 def move (src, dest, overwrite = 0, perms = 0664):
399 if os.path.exists(dest) and os.path.isdir(dest):
402 dest_dir = os.path.dirname(dest)
403 if not os.path.exists(dest_dir):
404 umask = os.umask(00000)
405 os.makedirs(dest_dir, 02775)
407 #print "Moving %s to %s..." % (src, dest)
408 if os.path.exists(dest) and os.path.isdir(dest):
409 dest += '/' + os.path.basename(src)
410 # Don't overwrite unless forced to
411 if os.path.exists(dest):
413 fubar("Can't move %s to %s - file already exists." % (src, dest))
415 if not os.access(dest, os.W_OK):
416 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
417 shutil.copy2(src, dest)
418 os.chmod(dest, perms)
421 def copy (src, dest, overwrite = 0, perms = 0664):
422 if os.path.exists(dest) and os.path.isdir(dest):
425 dest_dir = os.path.dirname(dest)
426 if not os.path.exists(dest_dir):
427 umask = os.umask(00000)
428 os.makedirs(dest_dir, 02775)
430 #print "Copying %s to %s..." % (src, dest)
431 if os.path.exists(dest) and os.path.isdir(dest):
432 dest += '/' + os.path.basename(src)
433 # Don't overwrite unless forced to
434 if os.path.exists(dest):
436 raise file_exists_exc
438 if not os.access(dest, os.W_OK):
439 raise cant_overwrite_exc
440 shutil.copy2(src, dest)
441 os.chmod(dest, perms)
443 ################################################################################
446 res = socket.gethostbyaddr(socket.gethostname())
447 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
448 if database_hostname:
449 return database_hostname
453 def which_conf_file ():
454 res = socket.gethostbyaddr(socket.gethostname())
455 if Cnf.get("Config::" + res[0] + "::DakConfig"):
456 return Cnf["Config::" + res[0] + "::DakConfig"]
458 return default_config
460 def which_apt_conf_file ():
461 res = socket.gethostbyaddr(socket.gethostname())
462 if Cnf.get("Config::" + res[0] + "::AptConfig"):
463 return Cnf["Config::" + res[0] + "::AptConfig"]
465 return default_apt_config
467 ################################################################################
469 # Escape characters which have meaning to SQL's regex comparison operator ('~')
470 # (woefully incomplete)
473 s = s.replace('+', '\\\\+')
474 s = s.replace('.', '\\\\.')
477 ################################################################################
479 # Perform a substition of template
480 def TemplateSubst(map, filename):
481 file = open_file(filename)
482 template = file.read()
484 template = template.replace(x,map[x])
488 ################################################################################
490 def fubar(msg, exit_code=1):
491 sys.stderr.write("E: %s\n" % (msg))
495 sys.stderr.write("W: %s\n" % (msg))
497 ################################################################################
499 # Returns the user name with a laughable attempt at rfc822 conformancy
500 # (read: removing stray periods).
502 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
504 ################################################################################
514 return ("%d%s" % (c, t))
516 ################################################################################
518 def cc_fix_changes (changes):
519 o = changes.get("architecture", "")
521 del changes["architecture"]
522 changes["architecture"] = {}
524 changes["architecture"][j] = 1
526 # Sort by source name, source version, 'have source', and then by filename
527 def changes_compare (a, b):
529 a_changes = parse_changes(a)
534 b_changes = parse_changes(b)
538 cc_fix_changes (a_changes)
539 cc_fix_changes (b_changes)
541 # Sort by source name
542 a_source = a_changes.get("source")
543 b_source = b_changes.get("source")
544 q = cmp (a_source, b_source)
548 # Sort by source version
549 a_version = a_changes.get("version", "0")
550 b_version = b_changes.get("version", "0")
551 q = apt_pkg.VersionCompare(a_version, b_version)
555 # Sort by 'have source'
556 a_has_source = a_changes["architecture"].get("source")
557 b_has_source = b_changes["architecture"].get("source")
558 if a_has_source and not b_has_source:
560 elif b_has_source and not a_has_source:
563 # Fall back to sort by filename
566 ################################################################################
568 def find_next_free (dest, too_many=100):
571 while os.path.exists(dest) and extra < too_many:
572 dest = orig_dest + '.' + repr(extra)
574 if extra >= too_many:
575 raise tried_too_hard_exc
578 ################################################################################
580 def result_join (original, sep = '\t'):
582 for i in xrange(len(original)):
583 if original[i] == None:
586 list.append(original[i])
587 return sep.join(list)
589 ################################################################################
591 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
593 for line in str.split('\n'):
595 if line or include_blank_lines:
596 out += "%s%s\n" % (prefix, line)
597 # Strip trailing new line
602 ################################################################################
604 def validate_changes_file_arg(filename, require_changes=1):
605 """'filename' is either a .changes or .dak file. If 'filename' is a
606 .dak file, it's changed to be the corresponding .changes file. The
607 function then checks if the .changes file a) exists and b) is
608 readable and returns the .changes filename if so. If there's a
609 problem, the next action depends on the option 'require_changes'
612 o If 'require_changes' == -1, errors are ignored and the .changes
613 filename is returned.
614 o If 'require_changes' == 0, a warning is given and 'None' is returned.
615 o If 'require_changes' == 1, a fatal error is raised.
619 orig_filename = filename
620 if filename.endswith(".dak"):
621 filename = filename[:-4]+".changes"
623 if not filename.endswith(".changes"):
624 error = "invalid file type; not a changes file"
626 if not os.access(filename,os.R_OK):
627 if os.path.exists(filename):
628 error = "permission denied"
630 error = "file not found"
633 if require_changes == 1:
634 fubar("%s: %s." % (orig_filename, error))
635 elif require_changes == 0:
636 warn("Skipping %s - %s" % (orig_filename, error))
638 else: # We only care about the .dak file
643 ################################################################################
646 return (arch != "source" and arch != "all")
648 ################################################################################
650 def join_with_commas_and(list):
651 if len(list) == 0: return "nothing"
652 if len(list) == 1: return list[0]
653 return ", ".join(list[:-1]) + " and " + list[-1]
655 ################################################################################
660 (pkg, version, constraint) = atom
662 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
665 pp_deps.append(pp_dep)
666 return " |".join(pp_deps)
668 ################################################################################
673 ################################################################################
675 # Handle -a, -c and -s arguments; returns them as SQL constraints
676 def parse_args(Options):
680 for suite in split_args(Options["Suite"]):
681 suite_id = database.get_suite_id(suite)
683 warn("suite '%s' not recognised." % (suite))
685 suite_ids_list.append(suite_id)
687 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
689 fubar("No valid suite given.")
694 if Options["Component"]:
695 component_ids_list = []
696 for component in split_args(Options["Component"]):
697 component_id = database.get_component_id(component)
698 if component_id == -1:
699 warn("component '%s' not recognised." % (component))
701 component_ids_list.append(component_id)
702 if component_ids_list:
703 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
705 fubar("No valid component given.")
709 # Process architecture
710 con_architectures = ""
711 if Options["Architecture"]:
714 for architecture in split_args(Options["Architecture"]):
715 if architecture == "source":
718 architecture_id = database.get_architecture_id(architecture)
719 if architecture_id == -1:
720 warn("architecture '%s' not recognised." % (architecture))
722 arch_ids_list.append(architecture_id)
724 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
727 fubar("No valid architecture given.")
731 return (con_suites, con_architectures, con_components, check_source)
733 ################################################################################
735 # Inspired(tm) by Bryn Keller's print_exc_plus (See
736 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
739 tb = sys.exc_info()[2]
748 traceback.print_exc()
750 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
751 frame.f_code.co_filename,
753 for key, value in frame.f_locals.items():
754 print "\t%20s = " % key,
758 print "<unable to print>"
760 ################################################################################
762 def try_with_debug(function):
770 ################################################################################
772 # Function for use in sorting lists of architectures.
773 # Sorts normally except that 'source' dominates all others.
775 def arch_compare_sw (a, b):
776 if a == "source" and b == "source":
785 ################################################################################
787 # Split command line arguments which can be separated by either commas
788 # or whitespace. If dwim is set, it will complain about string ending
789 # in comma since this usually means someone did 'dak ls -a i386, m68k
790 # foo' or something and the inevitable confusion resulting from 'm68k'
791 # being treated as an argument is undesirable.
793 def split_args (s, dwim=1):
794 if s.find(",") == -1:
797 if s[-1:] == "," and dwim:
798 fubar("split_args: found trailing comma, spurious space maybe?")
801 ################################################################################
803 def Dict(**dict): return dict
805 ########################################
807 # Our very own version of commands.getouputstatus(), hacked to support
809 def gpgv_get_status_output(cmd, status_read, status_write):
810 cmd = ['/bin/sh', '-c', cmd]
811 p2cread, p2cwrite = os.pipe()
812 c2pread, c2pwrite = os.pipe()
813 errout, errin = os.pipe()
823 for i in range(3, 256):
824 if i != status_write:
830 os.execvp(cmd[0], cmd)
836 os.dup2(c2pread, c2pwrite)
837 os.dup2(errout, errin)
841 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
844 r = os.read(fd, 8196)
847 if fd == c2pwrite or fd == errin:
849 elif fd == status_read:
852 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
854 pid, exit_status = os.waitpid(pid, 0)
856 os.close(status_write)
857 os.close(status_read)
867 return output, status, exit_status
869 ################################################################################
871 def process_gpgv_output(status):
872 # Process the status-fd output
875 for line in status.split('\n'):
881 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
883 (gnupg, keyword) = split[:2]
884 if gnupg != "[GNUPG:]":
885 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
888 if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
889 internal_error += "found duplicate status token ('%s').\n" % (keyword)
892 keywords[keyword] = args
894 return (keywords, internal_error)
896 ################################################################################
898 def retrieve_key (filename, keyserver=None, keyring=None):
899 """Retrieve the key that signed 'filename' from 'keyserver' and
900 add it to 'keyring'. Returns nothing on success, or an error message
903 # Defaults for keyserver and keyring
905 keyserver = Cnf["Dinstall::KeyServer"]
907 keyring = Cnf["Dinstall::GPGKeyring"]
909 # Ensure the filename contains no shell meta-characters or other badness
910 if not re_taint_free.match(filename):
911 return "%s: tainted filename" % (filename)
913 # Invoke gpgv on the file
914 status_read, status_write = os.pipe();
915 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
916 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
918 # Process the status-fd output
919 (keywords, internal_error) = process_gpgv_output(status)
921 return internal_error
923 if not keywords.has_key("NO_PUBKEY"):
924 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
926 fingerprint = keywords["NO_PUBKEY"][0]
927 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
928 # it'll try to create a lockfile in /dev. A better solution might
929 # be a tempfile or something.
930 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
931 % (Cnf["Dinstall::SigningKeyring"])
932 cmd += " --keyring %s --keyserver %s --recv-key %s" \
933 % (keyring, keyserver, fingerprint)
934 (result, output) = commands.getstatusoutput(cmd)
936 return "'%s' failed with exit code %s" % (cmd, result)
940 ################################################################################
942 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
943 """Check the signature of a file and return the fingerprint if the
944 signature is valid or 'None' if it's not. The first argument is the
945 filename whose signature should be checked. The second argument is a
946 reject function and is called when an error is found. The reject()
947 function must allow for two arguments: the first is the error message,
948 the second is an optional prefix string. It's possible for reject()
949 to be called more than once during an invocation of check_signature().
950 The third argument is optional and is the name of the files the
951 detached signature applies to. The fourth argument is optional and is
952 a *list* of keyrings to use. 'autofetch' can either be None, True or
953 False. If None, the default behaviour specified in the config will be
956 # Ensure the filename contains no shell meta-characters or other badness
957 if not re_taint_free.match(sig_filename):
958 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
961 if data_filename and not re_taint_free.match(data_filename):
962 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
966 keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
968 # Autofetch the signing key if that's enabled
969 if autofetch == None:
970 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
972 error_msg = retrieve_key(sig_filename)
977 # Build the command line
978 status_read, status_write = os.pipe();
979 cmd = "gpgv --status-fd %s" % (status_write)
980 for keyring in keyrings:
981 cmd += " --keyring %s" % (keyring)
982 cmd += " %s %s" % (sig_filename, data_filename)
983 # Invoke gpgv on the file
984 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
986 # Process the status-fd output
987 (keywords, internal_error) = process_gpgv_output(status)
989 # If we failed to parse the status-fd output, let's just whine and bail now
991 reject("internal error while performing signature check on %s." % (sig_filename))
992 reject(internal_error, "")
993 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
997 # Now check for obviously bad things in the processed output
998 if keywords.has_key("SIGEXPIRED"):
999 reject("The key used to sign %s has expired." % (sig_filename))
1001 if keywords.has_key("KEYREVOKED"):
1002 reject("The key used to sign %s has been revoked." % (sig_filename))
1004 if keywords.has_key("BADSIG"):
1005 reject("bad signature on %s." % (sig_filename))
1007 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1008 reject("failed to check signature on %s." % (sig_filename))
1010 if keywords.has_key("NO_PUBKEY"):
1011 args = keywords["NO_PUBKEY"]
1014 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1016 if keywords.has_key("BADARMOR"):
1017 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1019 if keywords.has_key("NODATA"):
1020 reject("no signature found in %s." % (sig_filename))
1026 # Next check gpgv exited with a zero return code
1028 reject("gpgv failed while checking %s." % (sig_filename))
1030 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1032 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1035 # Sanity check the good stuff we expect
1036 if not keywords.has_key("VALIDSIG"):
1037 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1040 args = keywords["VALIDSIG"]
1042 reject("internal error while checking signature on %s." % (sig_filename))
1045 fingerprint = args[0]
1046 if not keywords.has_key("GOODSIG"):
1047 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1049 if not keywords.has_key("SIG_ID"):
1050 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1053 # Finally ensure there's not something we don't recognise
1054 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1055 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1058 for keyword in keywords.keys():
1059 if not known_keywords.has_key(keyword):
1060 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1068 ################################################################################
1070 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1072 def wrap(paragraph, max_length, prefix=""):
1076 words = paragraph.split()
1079 word_size = len(word)
1080 if word_size > max_length:
1082 s += line + '\n' + prefix
1083 s += word + '\n' + prefix
1086 new_length = len(line) + word_size + 1
1087 if new_length > max_length:
1088 s += line + '\n' + prefix
1101 ################################################################################
1103 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1104 # Returns fixed 'src'
1105 def clean_symlink (src, dest, root):
1106 src = src.replace(root, '', 1)
1107 dest = dest.replace(root, '', 1)
1108 dest = os.path.dirname(dest)
1109 new_src = '../' * len(dest.split('/'))
1110 return new_src + src
1112 ################################################################################
1114 def temp_filename(directory=None, dotprefix=None, perms=0700):
1115 """Return a secure and unique filename by pre-creating it.
1116 If 'directory' is non-null, it will be the directory the file is pre-created in.
1117 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1120 old_tempdir = tempfile.tempdir
1121 tempfile.tempdir = directory
1123 filename = tempfile.mktemp()
1126 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1127 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1131 tempfile.tempdir = old_tempdir
1135 ################################################################################
1139 Cnf = apt_pkg.newConfiguration()
1140 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1142 if which_conf_file() != default_config:
1143 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1145 ################################################################################