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]
108 # Expand default component
110 if Cnf.has_key("Component::%s" % section):
115 return (section, component)
117 ################################################################################
119 def parse_changes(filename, signing_rules=0):
120 """Parses a changes file and returns a dictionary where each field is a
121 key. The mandatory first argument is the filename of the .changes
124 signing_rules is an optional argument:
126 o If signing_rules == -1, no signature is required.
127 o If signing_rules == 0 (the default), a signature is required.
128 o If signing_rules == 1, it turns on the same strict format checking
131 The rules for (signing_rules == 1)-mode are:
133 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
134 followed by any PGP header data and must end with a blank line.
136 o The data section must end with a blank line and must be followed by
137 "-----BEGIN PGP SIGNATURE-----".
143 changes_in = open_file(filename)
144 lines = changes_in.readlines()
147 raise changes_parse_error_exc, "[Empty changes file]"
149 # Reindex by line number so we can easily verify the format of
155 indexed_lines[index] = line[:-1]
159 num_of_lines = len(indexed_lines.keys())
162 while index < num_of_lines:
164 line = indexed_lines[index]
166 if signing_rules == 1:
168 if index > num_of_lines:
169 raise invalid_dsc_format_exc, index
170 line = indexed_lines[index]
171 if not line.startswith("-----BEGIN PGP SIGNATURE"):
172 raise invalid_dsc_format_exc, index
177 if line.startswith("-----BEGIN PGP SIGNATURE"):
179 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
181 if signing_rules == 1:
182 while index < num_of_lines and line != "":
184 line = indexed_lines[index]
186 # If we're not inside the signed data, don't process anything
187 if signing_rules >= 0 and not inside_signature:
189 slf = re_single_line_field.match(line)
191 field = slf.groups()[0].lower()
192 changes[field] = slf.groups()[1]
196 changes[field] += '\n'
198 mlf = re_multi_line_field.match(line)
201 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
202 if first == 1 and changes[field] != "":
203 changes[field] += '\n'
205 changes[field] += mlf.groups()[0] + '\n'
209 if signing_rules == 1 and inside_signature:
210 raise invalid_dsc_format_exc, index
213 changes["filecontents"] = "".join(lines)
215 if changes.has_key("source"):
216 # Strip the source version in brackets from the source field,
217 # put it in the "source-version" field instead.
218 srcver = re_srchasver.search(changes["source"])
220 changes["source"] = srcver.group(1)
221 changes["source-version"] = srcver.group(2)
224 raise changes_parse_error_exc, error
228 ################################################################################
230 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
232 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
235 # Make sure we have a Files: field to parse...
236 if not changes.has_key(field):
239 # Make sure we recognise the format of the Files: field
240 format = changes.get("format", "0.0").split(".",1)
242 format = int(format[0]), int(format[1])
244 format = int(float(format[0])), 0
248 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
250 if (format < (1,5) or format > (1,8)):
251 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
252 if field != "files" and format < (1,8):
253 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
255 includes_section = (not is_a_dsc) and field == "files"
257 # Parse each entry/line:
258 for i in changes[field].split('\n'):
262 section = priority = ""
265 (md5, size, section, priority, name) = s
267 (md5, size, name) = s
269 raise changes_parse_error_exc, i
276 (section, component) = extract_component_from_section(section)
278 files[name] = Dict(size=size, section=section,
279 priority=priority, component=component)
280 files[name][hashname] = md5
284 ################################################################################
286 def force_to_utf8(s):
287 """Forces a string to UTF-8. If the string isn't already UTF-8,
288 it's assumed to be ISO-8859-1."""
293 latin1_s = unicode(s,'iso8859-1')
294 return latin1_s.encode('utf-8')
296 def rfc2047_encode(s):
297 """Encodes a (header) string per RFC2047 if necessary. If the
298 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
300 codecs.lookup('ascii')[1](s)
305 codecs.lookup('utf-8')[1](s)
306 h = email.Header.Header(s, 'utf-8', 998)
309 h = email.Header.Header(s, 'iso-8859-1', 998)
312 ################################################################################
314 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
315 # with it. I know - I'll fix the suckage and make things
318 def fix_maintainer (maintainer):
319 """Parses a Maintainer or Changed-By field and returns:
320 (1) an RFC822 compatible version,
321 (2) an RFC2047 compatible version,
325 The name is forced to UTF-8 for both (1) and (3). If the name field
326 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
327 switched to 'email (name)' format."""
328 maintainer = maintainer.strip()
330 return ('', '', '', '')
332 if maintainer.find("<") == -1:
335 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
336 email = maintainer[1:-1]
339 m = re_parse_maintainer.match(maintainer)
341 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
345 # Get an RFC2047 compliant version of the name
346 rfc2047_name = rfc2047_encode(name)
348 # Force the name to be UTF-8
349 name = force_to_utf8(name)
351 if name.find(',') != -1 or name.find('.') != -1:
352 rfc822_maint = "%s (%s)" % (email, name)
353 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
355 rfc822_maint = "%s <%s>" % (name, email)
356 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
358 if email.find("@") == -1 and email.find("buildd_") != 0:
359 raise ParseMaintError, "No @ found in email address part."
361 return (rfc822_maint, rfc2047_maint, name, email)
363 ################################################################################
365 # sendmail wrapper, takes _either_ a message string or a file as arguments
366 def send_mail (message, filename=""):
367 # If we've been passed a string dump it into a temporary file
369 filename = tempfile.mktemp()
370 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
371 os.write (fd, message)
375 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
377 raise sendmail_failed_exc, output
379 # Clean up any temporary files
383 ################################################################################
385 def poolify (source, component):
388 if source[:3] == "lib":
389 return component + source[:4] + '/' + source + '/'
391 return component + source[:1] + '/' + source + '/'
393 ################################################################################
395 def move (src, dest, overwrite = 0, perms = 0664):
396 if os.path.exists(dest) and os.path.isdir(dest):
399 dest_dir = os.path.dirname(dest)
400 if not os.path.exists(dest_dir):
401 umask = os.umask(00000)
402 os.makedirs(dest_dir, 02775)
404 #print "Moving %s to %s..." % (src, dest)
405 if os.path.exists(dest) and os.path.isdir(dest):
406 dest += '/' + os.path.basename(src)
407 # Don't overwrite unless forced to
408 if os.path.exists(dest):
410 fubar("Can't move %s to %s - file already exists." % (src, dest))
412 if not os.access(dest, os.W_OK):
413 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
414 shutil.copy2(src, dest)
415 os.chmod(dest, perms)
418 def copy (src, dest, overwrite = 0, perms = 0664):
419 if os.path.exists(dest) and os.path.isdir(dest):
422 dest_dir = os.path.dirname(dest)
423 if not os.path.exists(dest_dir):
424 umask = os.umask(00000)
425 os.makedirs(dest_dir, 02775)
427 #print "Copying %s to %s..." % (src, dest)
428 if os.path.exists(dest) and os.path.isdir(dest):
429 dest += '/' + os.path.basename(src)
430 # Don't overwrite unless forced to
431 if os.path.exists(dest):
433 raise file_exists_exc
435 if not os.access(dest, os.W_OK):
436 raise cant_overwrite_exc
437 shutil.copy2(src, dest)
438 os.chmod(dest, perms)
440 ################################################################################
443 res = socket.gethostbyaddr(socket.gethostname())
444 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
445 if database_hostname:
446 return database_hostname
450 def which_conf_file ():
451 res = socket.gethostbyaddr(socket.gethostname())
452 if Cnf.get("Config::" + res[0] + "::DakConfig"):
453 return Cnf["Config::" + res[0] + "::DakConfig"]
455 return default_config
457 def which_apt_conf_file ():
458 res = socket.gethostbyaddr(socket.gethostname())
459 if Cnf.get("Config::" + res[0] + "::AptConfig"):
460 return Cnf["Config::" + res[0] + "::AptConfig"]
462 return default_apt_config
464 ################################################################################
466 # Escape characters which have meaning to SQL's regex comparison operator ('~')
467 # (woefully incomplete)
470 s = s.replace('+', '\\\\+')
471 s = s.replace('.', '\\\\.')
474 ################################################################################
476 # Perform a substition of template
477 def TemplateSubst(map, filename):
478 file = open_file(filename)
479 template = file.read()
481 template = template.replace(x,map[x])
485 ################################################################################
487 def fubar(msg, exit_code=1):
488 sys.stderr.write("E: %s\n" % (msg))
492 sys.stderr.write("W: %s\n" % (msg))
494 ################################################################################
496 # Returns the user name with a laughable attempt at rfc822 conformancy
497 # (read: removing stray periods).
499 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
501 ################################################################################
511 return ("%d%s" % (c, t))
513 ################################################################################
515 def cc_fix_changes (changes):
516 o = changes.get("architecture", "")
518 del changes["architecture"]
519 changes["architecture"] = {}
521 changes["architecture"][j] = 1
523 # Sort by source name, source version, 'have source', and then by filename
524 def changes_compare (a, b):
526 a_changes = parse_changes(a)
531 b_changes = parse_changes(b)
535 cc_fix_changes (a_changes)
536 cc_fix_changes (b_changes)
538 # Sort by source name
539 a_source = a_changes.get("source")
540 b_source = b_changes.get("source")
541 q = cmp (a_source, b_source)
545 # Sort by source version
546 a_version = a_changes.get("version", "0")
547 b_version = b_changes.get("version", "0")
548 q = apt_pkg.VersionCompare(a_version, b_version)
552 # Sort by 'have source'
553 a_has_source = a_changes["architecture"].get("source")
554 b_has_source = b_changes["architecture"].get("source")
555 if a_has_source and not b_has_source:
557 elif b_has_source and not a_has_source:
560 # Fall back to sort by filename
563 ################################################################################
565 def find_next_free (dest, too_many=100):
568 while os.path.exists(dest) and extra < too_many:
569 dest = orig_dest + '.' + repr(extra)
571 if extra >= too_many:
572 raise tried_too_hard_exc
575 ################################################################################
577 def result_join (original, sep = '\t'):
579 for i in xrange(len(original)):
580 if original[i] == None:
583 list.append(original[i])
584 return sep.join(list)
586 ################################################################################
588 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
590 for line in str.split('\n'):
592 if line or include_blank_lines:
593 out += "%s%s\n" % (prefix, line)
594 # Strip trailing new line
599 ################################################################################
601 def validate_changes_file_arg(filename, require_changes=1):
602 """'filename' is either a .changes or .dak file. If 'filename' is a
603 .dak file, it's changed to be the corresponding .changes file. The
604 function then checks if the .changes file a) exists and b) is
605 readable and returns the .changes filename if so. If there's a
606 problem, the next action depends on the option 'require_changes'
609 o If 'require_changes' == -1, errors are ignored and the .changes
610 filename is returned.
611 o If 'require_changes' == 0, a warning is given and 'None' is returned.
612 o If 'require_changes' == 1, a fatal error is raised.
616 orig_filename = filename
617 if filename.endswith(".dak"):
618 filename = filename[:-4]+".changes"
620 if not filename.endswith(".changes"):
621 error = "invalid file type; not a changes file"
623 if not os.access(filename,os.R_OK):
624 if os.path.exists(filename):
625 error = "permission denied"
627 error = "file not found"
630 if require_changes == 1:
631 fubar("%s: %s." % (orig_filename, error))
632 elif require_changes == 0:
633 warn("Skipping %s - %s" % (orig_filename, error))
635 else: # We only care about the .dak file
640 ################################################################################
643 return (arch != "source" and arch != "all")
645 ################################################################################
647 def join_with_commas_and(list):
648 if len(list) == 0: return "nothing"
649 if len(list) == 1: return list[0]
650 return ", ".join(list[:-1]) + " and " + list[-1]
652 ################################################################################
657 (pkg, version, constraint) = atom
659 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
662 pp_deps.append(pp_dep)
663 return " |".join(pp_deps)
665 ################################################################################
670 ################################################################################
672 # Handle -a, -c and -s arguments; returns them as SQL constraints
673 def parse_args(Options):
677 for suite in split_args(Options["Suite"]):
678 suite_id = database.get_suite_id(suite)
680 warn("suite '%s' not recognised." % (suite))
682 suite_ids_list.append(suite_id)
684 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
686 fubar("No valid suite given.")
691 if Options["Component"]:
692 component_ids_list = []
693 for component in split_args(Options["Component"]):
694 component_id = database.get_component_id(component)
695 if component_id == -1:
696 warn("component '%s' not recognised." % (component))
698 component_ids_list.append(component_id)
699 if component_ids_list:
700 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
702 fubar("No valid component given.")
706 # Process architecture
707 con_architectures = ""
708 if Options["Architecture"]:
711 for architecture in split_args(Options["Architecture"]):
712 if architecture == "source":
715 architecture_id = database.get_architecture_id(architecture)
716 if architecture_id == -1:
717 warn("architecture '%s' not recognised." % (architecture))
719 arch_ids_list.append(architecture_id)
721 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
724 fubar("No valid architecture given.")
728 return (con_suites, con_architectures, con_components, check_source)
730 ################################################################################
732 # Inspired(tm) by Bryn Keller's print_exc_plus (See
733 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
736 tb = sys.exc_info()[2]
745 traceback.print_exc()
747 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
748 frame.f_code.co_filename,
750 for key, value in frame.f_locals.items():
751 print "\t%20s = " % key,
755 print "<unable to print>"
757 ################################################################################
759 def try_with_debug(function):
767 ################################################################################
769 # Function for use in sorting lists of architectures.
770 # Sorts normally except that 'source' dominates all others.
772 def arch_compare_sw (a, b):
773 if a == "source" and b == "source":
782 ################################################################################
784 # Split command line arguments which can be separated by either commas
785 # or whitespace. If dwim is set, it will complain about string ending
786 # in comma since this usually means someone did 'dak ls -a i386, m68k
787 # foo' or something and the inevitable confusion resulting from 'm68k'
788 # being treated as an argument is undesirable.
790 def split_args (s, dwim=1):
791 if s.find(",") == -1:
794 if s[-1:] == "," and dwim:
795 fubar("split_args: found trailing comma, spurious space maybe?")
798 ################################################################################
800 def Dict(**dict): return dict
802 ########################################
804 # Our very own version of commands.getouputstatus(), hacked to support
806 def gpgv_get_status_output(cmd, status_read, status_write):
807 cmd = ['/bin/sh', '-c', cmd]
808 p2cread, p2cwrite = os.pipe()
809 c2pread, c2pwrite = os.pipe()
810 errout, errin = os.pipe()
820 for i in range(3, 256):
821 if i != status_write:
827 os.execvp(cmd[0], cmd)
833 os.dup2(c2pread, c2pwrite)
834 os.dup2(errout, errin)
838 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
841 r = os.read(fd, 8196)
844 if fd == c2pwrite or fd == errin:
846 elif fd == status_read:
849 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
851 pid, exit_status = os.waitpid(pid, 0)
853 os.close(status_write)
854 os.close(status_read)
864 return output, status, exit_status
866 ################################################################################
868 def process_gpgv_output(status):
869 # Process the status-fd output
872 for line in status.split('\n'):
878 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
880 (gnupg, keyword) = split[:2]
881 if gnupg != "[GNUPG:]":
882 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
885 if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
886 internal_error += "found duplicate status token ('%s').\n" % (keyword)
889 keywords[keyword] = args
891 return (keywords, internal_error)
893 ################################################################################
895 def retrieve_key (filename, keyserver=None, keyring=None):
896 """Retrieve the key that signed 'filename' from 'keyserver' and
897 add it to 'keyring'. Returns nothing on success, or an error message
900 # Defaults for keyserver and keyring
902 keyserver = Cnf["Dinstall::KeyServer"]
904 keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
906 # Ensure the filename contains no shell meta-characters or other badness
907 if not re_taint_free.match(filename):
908 return "%s: tainted filename" % (filename)
910 # Invoke gpgv on the file
911 status_read, status_write = os.pipe();
912 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
913 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
915 # Process the status-fd output
916 (keywords, internal_error) = process_gpgv_output(status)
918 return internal_error
920 if not keywords.has_key("NO_PUBKEY"):
921 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
923 fingerprint = keywords["NO_PUBKEY"][0]
924 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
925 # it'll try to create a lockfile in /dev. A better solution might
926 # be a tempfile or something.
927 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
928 % (Cnf["Dinstall::SigningKeyring"])
929 cmd += " --keyring %s --keyserver %s --recv-key %s" \
930 % (keyring, keyserver, fingerprint)
931 (result, output) = commands.getstatusoutput(cmd)
933 return "'%s' failed with exit code %s" % (cmd, result)
937 ################################################################################
939 def gpg_keyring_args(keyrings=None):
941 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
943 return " ".join(["--keyring %s" % x for x in keyrings])
945 ################################################################################
947 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
948 """Check the signature of a file and return the fingerprint if the
949 signature is valid or 'None' if it's not. The first argument is the
950 filename whose signature should be checked. The second argument is a
951 reject function and is called when an error is found. The reject()
952 function must allow for two arguments: the first is the error message,
953 the second is an optional prefix string. It's possible for reject()
954 to be called more than once during an invocation of check_signature().
955 The third argument is optional and is the name of the files the
956 detached signature applies to. The fourth argument is optional and is
957 a *list* of keyrings to use. 'autofetch' can either be None, True or
958 False. If None, the default behaviour specified in the config will be
961 # Ensure the filename contains no shell meta-characters or other badness
962 if not re_taint_free.match(sig_filename):
963 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
966 if data_filename and not re_taint_free.match(data_filename):
967 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
971 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
973 # Autofetch the signing key if that's enabled
974 if autofetch == None:
975 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
977 error_msg = retrieve_key(sig_filename)
982 # Build the command line
983 status_read, status_write = os.pipe();
984 cmd = "gpgv --status-fd %s %s %s %s" % (
985 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
987 # Invoke gpgv on the file
988 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
990 # Process the status-fd output
991 (keywords, internal_error) = process_gpgv_output(status)
993 # If we failed to parse the status-fd output, let's just whine and bail now
995 reject("internal error while performing signature check on %s." % (sig_filename))
996 reject(internal_error, "")
997 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1001 # Now check for obviously bad things in the processed output
1002 if keywords.has_key("KEYREVOKED"):
1003 reject("The key used to sign %s has been revoked." % (sig_filename))
1005 if keywords.has_key("BADSIG"):
1006 reject("bad signature on %s." % (sig_filename))
1008 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1009 reject("failed to check signature on %s." % (sig_filename))
1011 if keywords.has_key("NO_PUBKEY"):
1012 args = keywords["NO_PUBKEY"]
1015 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1017 if keywords.has_key("BADARMOR"):
1018 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1020 if keywords.has_key("NODATA"):
1021 reject("no signature found in %s." % (sig_filename))
1023 if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1024 args = keywords["KEYEXPIRED"]
1027 reject("The key (0x%s) used to sign %s has expired." % (key, 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="",
1063 NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
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 ################################################################################