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.ValueList("Dinstall::GPGKeyring")[0]
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 gpg_keyring_args(keyrings=None):
944 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
946 return " ".join(["--keyring %s" % x for x in keyrings])
948 ################################################################################
950 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
951 """Check the signature of a file and return the fingerprint if the
952 signature is valid or 'None' if it's not. The first argument is the
953 filename whose signature should be checked. The second argument is a
954 reject function and is called when an error is found. The reject()
955 function must allow for two arguments: the first is the error message,
956 the second is an optional prefix string. It's possible for reject()
957 to be called more than once during an invocation of check_signature().
958 The third argument is optional and is the name of the files the
959 detached signature applies to. The fourth argument is optional and is
960 a *list* of keyrings to use. 'autofetch' can either be None, True or
961 False. If None, the default behaviour specified in the config will be
964 # Ensure the filename contains no shell meta-characters or other badness
965 if not re_taint_free.match(sig_filename):
966 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
969 if data_filename and not re_taint_free.match(data_filename):
970 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
974 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
976 # Autofetch the signing key if that's enabled
977 if autofetch == None:
978 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
980 error_msg = retrieve_key(sig_filename)
985 # Build the command line
986 status_read, status_write = os.pipe();
987 cmd = "gpgv --status-fd %s %s %s %s" % (
988 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
990 # Invoke gpgv on the file
991 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
993 # Process the status-fd output
994 (keywords, internal_error) = process_gpgv_output(status)
996 # If we failed to parse the status-fd output, let's just whine and bail now
998 reject("internal error while performing signature check on %s." % (sig_filename))
999 reject(internal_error, "")
1000 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1004 # Now check for obviously bad things in the processed output
1005 if keywords.has_key("SIGEXPIRED"):
1006 reject("The key used to sign %s has expired." % (sig_filename))
1008 if keywords.has_key("KEYREVOKED"):
1009 reject("The key used to sign %s has been revoked." % (sig_filename))
1011 if keywords.has_key("BADSIG"):
1012 reject("bad signature on %s." % (sig_filename))
1014 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1015 reject("failed to check signature on %s." % (sig_filename))
1017 if keywords.has_key("NO_PUBKEY"):
1018 args = keywords["NO_PUBKEY"]
1021 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1023 if keywords.has_key("BADARMOR"):
1024 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1026 if keywords.has_key("NODATA"):
1027 reject("no signature found in %s." % (sig_filename))
1033 # Next check gpgv exited with a zero return code
1035 reject("gpgv failed while checking %s." % (sig_filename))
1037 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1039 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1042 # Sanity check the good stuff we expect
1043 if not keywords.has_key("VALIDSIG"):
1044 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1047 args = keywords["VALIDSIG"]
1049 reject("internal error while checking signature on %s." % (sig_filename))
1052 fingerprint = args[0]
1053 if not keywords.has_key("GOODSIG"):
1054 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1056 if not keywords.has_key("SIG_ID"):
1057 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1060 # Finally ensure there's not something we don't recognise
1061 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1062 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1065 for keyword in keywords.keys():
1066 if not known_keywords.has_key(keyword):
1067 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1075 ################################################################################
1077 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1079 def wrap(paragraph, max_length, prefix=""):
1083 words = paragraph.split()
1086 word_size = len(word)
1087 if word_size > max_length:
1089 s += line + '\n' + prefix
1090 s += word + '\n' + prefix
1093 new_length = len(line) + word_size + 1
1094 if new_length > max_length:
1095 s += line + '\n' + prefix
1108 ################################################################################
1110 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1111 # Returns fixed 'src'
1112 def clean_symlink (src, dest, root):
1113 src = src.replace(root, '', 1)
1114 dest = dest.replace(root, '', 1)
1115 dest = os.path.dirname(dest)
1116 new_src = '../' * len(dest.split('/'))
1117 return new_src + src
1119 ################################################################################
1121 def temp_filename(directory=None, dotprefix=None, perms=0700):
1122 """Return a secure and unique filename by pre-creating it.
1123 If 'directory' is non-null, it will be the directory the file is pre-created in.
1124 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1127 old_tempdir = tempfile.tempdir
1128 tempfile.tempdir = directory
1130 filename = tempfile.mktemp()
1133 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1134 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1138 tempfile.tempdir = old_tempdir
1142 ################################################################################
1146 Cnf = apt_pkg.newConfiguration()
1147 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1149 if which_conf_file() != default_config:
1150 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1152 ################################################################################