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 string, 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 changes_parse_error_exc = "Can't parse line in .changes file"
46 invalid_dsc_format_exc = "Invalid .dsc file"
47 nk_format_exc = "Unknown Format: in .changes file"
48 no_files_exc = "No Files: field in .dsc or .changes file."
49 cant_open_exc = "Can't open file"
50 unknown_hostname_exc = "Unknown hostname"
51 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
52 file_exists_exc = "Destination file exists"
53 sendmail_failed_exc = "Sendmail invocation failed"
54 tried_too_hard_exc = "Tried too hard to find a free filename."
56 default_config = "/etc/dak/dak.conf"
57 default_apt_config = "/etc/dak/apt.conf"
59 ################################################################################
61 class Error(Exception):
62 """Base class for exceptions in this module."""
65 class ParseMaintError(Error):
66 """Exception raised for errors in parsing a maintainer field.
69 message -- explanation of the error
72 def __init__(self, message):
74 self.message = message
76 ################################################################################
78 def open_file(filename, mode='r'):
80 f = open(filename, mode)
82 raise cant_open_exc, filename
85 ################################################################################
87 def our_raw_input(prompt=""):
89 sys.stdout.write(prompt)
95 sys.stderr.write("\nUser interrupt (^D).\n")
98 ################################################################################
102 if c not in string.digits:
106 ################################################################################
108 def extract_component_from_section(section):
111 if section.find('/') != -1:
112 component = section.split('/')[0]
113 if component.lower() == "non-us" and section.find('/') != -1:
114 s = component + '/' + section.split('/')[1]
115 if Cnf.has_key("Component::%s" % s): # Avoid e.g. non-US/libs
118 if section.lower() == "non-us":
119 component = "non-US/main"
121 # non-US prefix is case insensitive
122 if component.lower()[:6] == "non-us":
123 component = "non-US"+component[6:]
125 # Expand default component
127 if Cnf.has_key("Component::%s" % section):
131 elif component == "non-US":
132 component = "non-US/main"
134 return (section, component)
136 ################################################################################
138 def parse_changes(filename, signing_rules=0):
139 """Parses a changes file and returns a dictionary where each field is a
140 key. The mandatory first argument is the filename of the .changes
143 signing_rules is an optional argument:
145 o If signing_rules == -1, no signature is required.
146 o If signing_rules == 0 (the default), a signature is required.
147 o If signing_rules == 1, it turns on the same strict format checking
150 The rules for (signing_rules == 1)-mode are:
152 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
153 followed by any PGP header data and must end with a blank line.
155 o The data section must end with a blank line and must be followed by
156 "-----BEGIN PGP SIGNATURE-----".
162 changes_in = open_file(filename)
163 lines = changes_in.readlines()
166 raise changes_parse_error_exc, "[Empty changes file]"
168 # Reindex by line number so we can easily verify the format of
174 indexed_lines[index] = line[:-1]
178 num_of_lines = len(indexed_lines.keys())
181 while index < num_of_lines:
183 line = indexed_lines[index]
185 if signing_rules == 1:
187 if index > num_of_lines:
188 raise invalid_dsc_format_exc, index
189 line = indexed_lines[index]
190 if not line.startswith("-----BEGIN PGP SIGNATURE"):
191 raise invalid_dsc_format_exc, index
196 if line.startswith("-----BEGIN PGP SIGNATURE"):
198 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
200 if signing_rules == 1:
201 while index < num_of_lines and line != "":
203 line = indexed_lines[index]
205 # If we're not inside the signed data, don't process anything
206 if signing_rules >= 0 and not inside_signature:
208 slf = re_single_line_field.match(line)
210 field = slf.groups()[0].lower()
211 changes[field] = slf.groups()[1]
215 changes[field] += '\n'
217 mlf = re_multi_line_field.match(line)
220 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
221 if first == 1 and changes[field] != "":
222 changes[field] += '\n'
224 changes[field] += mlf.groups()[0] + '\n'
228 if signing_rules == 1 and inside_signature:
229 raise invalid_dsc_format_exc, index
232 changes["filecontents"] = "".join(lines)
235 raise changes_parse_error_exc, error
239 ################################################################################
241 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
243 def build_file_list(changes, is_a_dsc=0):
246 # Make sure we have a Files: field to parse...
247 if not changes.has_key("files"):
250 # Make sure we recognise the format of the Files: field
251 format = changes.get("format", "")
253 format = float(format)
254 if not is_a_dsc and (format < 1.5 or format > 2.0):
255 raise nk_format_exc, format
257 # Parse each entry/line:
258 for i in changes["files"].split('\n'):
262 section = priority = ""
265 (md5, size, name) = s
267 (md5, size, section, priority, name) = s
269 raise changes_parse_error_exc, i
276 (section, component) = extract_component_from_section(section)
278 files[name] = Dict(md5sum=md5, size=size, section=section,
279 priority=priority, component=component)
283 ################################################################################
285 def force_to_utf8(s):
286 """Forces a string to UTF-8. If the string isn't already UTF-8,
287 it's assumed to be ISO-8859-1."""
292 latin1_s = unicode(s,'iso8859-1')
293 return latin1_s.encode('utf-8')
295 def rfc2047_encode(s):
296 """Encodes a (header) string per RFC2047 if necessary. If the
297 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
299 codecs.lookup('ascii')[1](s)
304 codecs.lookup('utf-8')[1](s)
305 h = email.Header.Header(s, 'utf-8', 998)
308 h = email.Header.Header(s, 'iso-8859-1', 998)
311 ################################################################################
313 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
314 # with it. I know - I'll fix the suckage and make things
317 def fix_maintainer (maintainer):
318 """Parses a Maintainer or Changed-By field and returns:
319 (1) an RFC822 compatible version,
320 (2) an RFC2047 compatible version,
324 The name is forced to UTF-8 for both (1) and (3). If the name field
325 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
326 switched to 'email (name)' format."""
327 maintainer = maintainer.strip()
329 return ('', '', '', '')
331 if maintainer.find("<") == -1:
334 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
335 email = maintainer[1:-1]
338 m = re_parse_maintainer.match(maintainer)
340 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
344 # Get an RFC2047 compliant version of the name
345 rfc2047_name = rfc2047_encode(name)
347 # Force the name to be UTF-8
348 name = force_to_utf8(name)
350 if name.find(',') != -1 or name.find('.') != -1:
351 rfc822_maint = "%s (%s)" % (email, name)
352 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
354 rfc822_maint = "%s <%s>" % (name, email)
355 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
357 if email.find("@") == -1 and email.find("buildd_") != 0:
358 raise ParseMaintError, "No @ found in email address part."
360 return (rfc822_maint, rfc2047_maint, name, email)
362 ################################################################################
364 # sendmail wrapper, takes _either_ a message string or a file as arguments
365 def send_mail (message, filename=""):
366 # If we've been passed a string dump it into a temporary file
368 filename = tempfile.mktemp()
369 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
370 os.write (fd, message)
374 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
376 raise sendmail_failed_exc, output
378 # Clean up any temporary files
382 ################################################################################
384 def poolify (source, component):
387 # FIXME: this is nasty
388 component = component.lower().replace("non-us/", "non-US/")
389 if source[:3] == "lib":
390 return component + source[:4] + '/' + source + '/'
392 return component + source[:1] + '/' + source + '/'
394 ################################################################################
396 def move (src, dest, overwrite = 0, perms = 0664):
397 if os.path.exists(dest) and os.path.isdir(dest):
400 dest_dir = os.path.dirname(dest)
401 if not os.path.exists(dest_dir):
402 umask = os.umask(00000)
403 os.makedirs(dest_dir, 02775)
405 #print "Moving %s to %s..." % (src, dest)
406 if os.path.exists(dest) and os.path.isdir(dest):
407 dest += '/' + os.path.basename(src)
408 # Don't overwrite unless forced to
409 if os.path.exists(dest):
411 fubar("Can't move %s to %s - file already exists." % (src, dest))
413 if not os.access(dest, os.W_OK):
414 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
415 shutil.copy2(src, dest)
416 os.chmod(dest, perms)
419 def copy (src, dest, overwrite = 0, perms = 0664):
420 if os.path.exists(dest) and os.path.isdir(dest):
423 dest_dir = os.path.dirname(dest)
424 if not os.path.exists(dest_dir):
425 umask = os.umask(00000)
426 os.makedirs(dest_dir, 02775)
428 #print "Copying %s to %s..." % (src, dest)
429 if os.path.exists(dest) and os.path.isdir(dest):
430 dest += '/' + os.path.basename(src)
431 # Don't overwrite unless forced to
432 if os.path.exists(dest):
434 raise file_exists_exc
436 if not os.access(dest, os.W_OK):
437 raise cant_overwrite_exc
438 shutil.copy2(src, dest)
439 os.chmod(dest, perms)
441 ################################################################################
444 res = socket.gethostbyaddr(socket.gethostname())
445 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
446 if database_hostname:
447 return database_hostname
451 def which_conf_file ():
452 res = socket.gethostbyaddr(socket.gethostname())
453 if Cnf.get("Config::" + res[0] + "::DakConfig"):
454 return Cnf["Config::" + res[0] + "::DakConfig"]
456 return default_config
458 def which_apt_conf_file ():
459 res = socket.gethostbyaddr(socket.gethostname())
460 if Cnf.get("Config::" + res[0] + "::AptConfig"):
461 return Cnf["Config::" + res[0] + "::AptConfig"]
463 return default_apt_config
465 ################################################################################
467 # Escape characters which have meaning to SQL's regex comparison operator ('~')
468 # (woefully incomplete)
471 s = s.replace('+', '\\\\+')
472 s = s.replace('.', '\\\\.')
475 ################################################################################
477 # Perform a substition of template
478 def TemplateSubst(map, filename):
479 file = open_file(filename)
480 template = file.read()
482 template = template.replace(x,map[x])
486 ################################################################################
488 def fubar(msg, exit_code=1):
489 sys.stderr.write("E: %s\n" % (msg))
493 sys.stderr.write("W: %s\n" % (msg))
495 ################################################################################
497 # Returns the user name with a laughable attempt at rfc822 conformancy
498 # (read: removing stray periods).
500 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
502 ################################################################################
512 return ("%d%s" % (c, t))
514 ################################################################################
516 def cc_fix_changes (changes):
517 o = changes.get("architecture", "")
519 del changes["architecture"]
520 changes["architecture"] = {}
522 changes["architecture"][j] = 1
524 # Sort by source name, source version, 'have source', and then by filename
525 def changes_compare (a, b):
527 a_changes = parse_changes(a)
532 b_changes = parse_changes(b)
536 cc_fix_changes (a_changes)
537 cc_fix_changes (b_changes)
539 # Sort by source name
540 a_source = a_changes.get("source")
541 b_source = b_changes.get("source")
542 q = cmp (a_source, b_source)
546 # Sort by source version
547 a_version = a_changes.get("version", "0")
548 b_version = b_changes.get("version", "0")
549 q = apt_pkg.VersionCompare(a_version, b_version)
553 # Sort by 'have source'
554 a_has_source = a_changes["architecture"].get("source")
555 b_has_source = b_changes["architecture"].get("source")
556 if a_has_source and not b_has_source:
558 elif b_has_source and not a_has_source:
561 # Fall back to sort by filename
564 ################################################################################
566 def find_next_free (dest, too_many=100):
569 while os.path.exists(dest) and extra < too_many:
570 dest = orig_dest + '.' + repr(extra)
572 if extra >= too_many:
573 raise tried_too_hard_exc
576 ################################################################################
578 def result_join (original, sep = '\t'):
580 for i in xrange(len(original)):
581 if original[i] == None:
584 list.append(original[i])
585 return sep.join(list)
587 ################################################################################
589 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
591 for line in str.split('\n'):
593 if line or include_blank_lines:
594 out += "%s%s\n" % (prefix, line)
595 # Strip trailing new line
600 ################################################################################
602 def validate_changes_file_arg(filename, require_changes=1):
603 """'filename' is either a .changes or .dak file. If 'filename' is a
604 .dak file, it's changed to be the corresponding .changes file. The
605 function then checks if the .changes file a) exists and b) is
606 readable and returns the .changes filename if so. If there's a
607 problem, the next action depends on the option 'require_changes'
610 o If 'require_changes' == -1, errors are ignored and the .changes
611 filename is returned.
612 o If 'require_changes' == 0, a warning is given and 'None' is returned.
613 o If 'require_changes' == 1, a fatal error is raised.
617 orig_filename = filename
618 if filename.endswith(".dak"):
619 filename = filename[:-6]+".changes"
621 if not filename.endswith(".changes"):
622 error = "invalid file type; not a changes file"
624 if not os.access(filename,os.R_OK):
625 if os.path.exists(filename):
626 error = "permission denied"
628 error = "file not found"
631 if require_changes == 1:
632 fubar("%s: %s." % (orig_filename, error))
633 elif require_changes == 0:
634 warn("Skipping %s - %s" % (orig_filename, error))
636 else: # We only care about the .dak file
641 ################################################################################
644 return (arch != "source" and arch != "all")
646 ################################################################################
648 def join_with_commas_and(list):
649 if len(list) == 0: return "nothing"
650 if len(list) == 1: return list[0]
651 return ", ".join(list[:-1]) + " and " + list[-1]
653 ################################################################################
658 (pkg, version, constraint) = atom
660 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
663 pp_deps.append(pp_dep)
664 return " |".join(pp_deps)
666 ################################################################################
671 ################################################################################
673 # Handle -a, -c and -s arguments; returns them as SQL constraints
674 def parse_args(Options):
678 for suite in split_args(Options["Suite"]):
679 suite_id = database.get_suite_id(suite)
681 warn("suite '%s' not recognised." % (suite))
683 suite_ids_list.append(suite_id)
685 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
687 fubar("No valid suite given.")
692 if Options["Component"]:
693 component_ids_list = []
694 for component in split_args(Options["Component"]):
695 component_id = database.get_component_id(component)
696 if component_id == -1:
697 warn("component '%s' not recognised." % (component))
699 component_ids_list.append(component_id)
700 if component_ids_list:
701 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
703 fubar("No valid component given.")
707 # Process architecture
708 con_architectures = ""
709 if Options["Architecture"]:
712 for architecture in split_args(Options["Architecture"]):
713 if architecture == "source":
716 architecture_id = database.get_architecture_id(architecture)
717 if architecture_id == -1:
718 warn("architecture '%s' not recognised." % (architecture))
720 arch_ids_list.append(architecture_id)
722 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
725 fubar("No valid architecture given.")
729 return (con_suites, con_architectures, con_components, check_source)
731 ################################################################################
733 # Inspired(tm) by Bryn Keller's print_exc_plus (See
734 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
737 tb = sys.exc_info()[2]
746 traceback.print_exc()
748 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
749 frame.f_code.co_filename,
751 for key, value in frame.f_locals.items():
752 print "\t%20s = " % key,
756 print "<unable to print>"
758 ################################################################################
760 def try_with_debug(function):
768 ################################################################################
770 # Function for use in sorting lists of architectures.
771 # Sorts normally except that 'source' dominates all others.
773 def arch_compare_sw (a, b):
774 if a == "source" and b == "source":
783 ################################################################################
785 # Split command line arguments which can be separated by either commas
786 # or whitespace. If dwim is set, it will complain about string ending
787 # in comma since this usually means someone did 'dak ls -a i386, m68k
788 # foo' or something and the inevitable confusion resulting from 'm68k'
789 # being treated as an argument is undesirable.
791 def split_args (s, dwim=1):
792 if s.find(",") == -1:
795 if s[-1:] == "," and dwim:
796 fubar("split_args: found trailing comma, spurious space maybe?")
799 ################################################################################
801 def Dict(**dict): return dict
803 ########################################
805 # Our very own version of commands.getouputstatus(), hacked to support
807 def gpgv_get_status_output(cmd, status_read, status_write):
808 cmd = ['/bin/sh', '-c', cmd]
809 p2cread, p2cwrite = os.pipe()
810 c2pread, c2pwrite = os.pipe()
811 errout, errin = os.pipe()
821 for i in range(3, 256):
822 if i != status_write:
828 os.execvp(cmd[0], cmd)
834 os.dup2(c2pread, c2pwrite)
835 os.dup2(errout, errin)
839 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
842 r = os.read(fd, 8196)
845 if fd == c2pwrite or fd == errin:
847 elif fd == status_read:
850 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
852 pid, exit_status = os.waitpid(pid, 0)
854 os.close(status_write)
855 os.close(status_read)
865 return output, status, exit_status
867 ################################################################################
869 def process_gpgv_output(status):
870 # Process the status-fd output
873 for line in status.split('\n'):
879 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
881 (gnupg, keyword) = split[:2]
882 if gnupg != "[GNUPG:]":
883 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
886 if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
887 internal_error += "found duplicate status token ('%s').\n" % (keyword)
890 keywords[keyword] = args
892 return (keywords, internal_error)
894 ################################################################################
896 def retrieve_key (filename, keyserver=None, keyring=None):
897 """Retrieve the key that signed 'filename' from 'keyserver' and
898 add it to 'keyring'. Returns nothing on success, or an error message
901 # Defaults for keyserver and keyring
903 keyserver = Cnf["Dinstall::KeyServer"]
905 keyring = Cnf["Dinstall::GPGKeyring"]
907 # Ensure the filename contains no shell meta-characters or other badness
908 if not re_taint_free.match(filename):
909 return "%s: tainted filename" % (filename)
911 # Invoke gpgv on the file
912 status_read, status_write = os.pipe();
913 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
914 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
916 # Process the status-fd output
917 (keywords, internal_error) = process_gpgv_output(status)
919 return internal_error
921 if not keywords.has_key("NO_PUBKEY"):
922 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
924 fingerprint = keywords["NO_PUBKEY"][0]
925 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
926 # it'll try to create a lockfile in /dev. A better solution might
927 # be a tempfile or something.
928 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
929 % (Cnf["Dinstall::SigningKeyring"])
930 cmd += " --keyring %s --keyserver %s --recv-key %s" \
931 % (keyring, keyserver, fingerprint)
932 (result, output) = commands.getstatusoutput(cmd)
934 return "'%s' failed with exit code %s" % (cmd, result)
938 ################################################################################
940 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
941 """Check the signature of a file and return the fingerprint if the
942 signature is valid or 'None' if it's not. The first argument is the
943 filename whose signature should be checked. The second argument is a
944 reject function and is called when an error is found. The reject()
945 function must allow for two arguments: the first is the error message,
946 the second is an optional prefix string. It's possible for reject()
947 to be called more than once during an invocation of check_signature().
948 The third argument is optional and is the name of the files the
949 detached signature applies to. The fourth argument is optional and is
950 a *list* of keyrings to use. 'autofetch' can either be None, True or
951 False. If None, the default behaviour specified in the config will be
954 # Ensure the filename contains no shell meta-characters or other badness
955 if not re_taint_free.match(sig_filename):
956 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
959 if data_filename and not re_taint_free.match(data_filename):
960 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
964 keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
966 # Autofetch the signing key if that's enabled
967 if autofetch == None:
968 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
970 error_msg = retrieve_key(sig_filename)
975 # Build the command line
976 status_read, status_write = os.pipe();
977 cmd = "gpgv --status-fd %s" % (status_write)
978 for keyring in keyrings:
979 cmd += " --keyring %s" % (keyring)
980 cmd += " %s %s" % (sig_filename, data_filename)
981 # Invoke gpgv on the file
982 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
984 # Process the status-fd output
985 (keywords, internal_error) = process_gpgv_output(status)
987 # If we failed to parse the status-fd output, let's just whine and bail now
989 reject("internal error while performing signature check on %s." % (sig_filename))
990 reject(internal_error, "")
991 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
995 # Now check for obviously bad things in the processed output
996 if keywords.has_key("SIGEXPIRED"):
997 reject("The key used to sign %s has expired." % (sig_filename))
999 if keywords.has_key("KEYREVOKED"):
1000 reject("The key used to sign %s has been revoked." % (sig_filename))
1002 if keywords.has_key("BADSIG"):
1003 reject("bad signature on %s." % (sig_filename))
1005 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1006 reject("failed to check signature on %s." % (sig_filename))
1008 if keywords.has_key("NO_PUBKEY"):
1009 args = keywords["NO_PUBKEY"]
1012 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1014 if keywords.has_key("BADARMOR"):
1015 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1017 if keywords.has_key("NODATA"):
1018 reject("no signature found in %s." % (sig_filename))
1024 # Next check gpgv exited with a zero return code
1026 reject("gpgv failed while checking %s." % (sig_filename))
1028 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1030 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1033 # Sanity check the good stuff we expect
1034 if not keywords.has_key("VALIDSIG"):
1035 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1038 args = keywords["VALIDSIG"]
1040 reject("internal error while checking signature on %s." % (sig_filename))
1043 fingerprint = args[0]
1044 if not keywords.has_key("GOODSIG"):
1045 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1047 if not keywords.has_key("SIG_ID"):
1048 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1051 # Finally ensure there's not something we don't recognise
1052 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1053 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1056 for keyword in keywords.keys():
1057 if not known_keywords.has_key(keyword):
1058 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1066 ################################################################################
1068 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1070 def wrap(paragraph, max_length, prefix=""):
1074 words = paragraph.split()
1077 word_size = len(word)
1078 if word_size > max_length:
1080 s += line + '\n' + prefix
1081 s += word + '\n' + prefix
1084 new_length = len(line) + word_size + 1
1085 if new_length > max_length:
1086 s += line + '\n' + prefix
1099 ################################################################################
1101 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1102 # Returns fixed 'src'
1103 def clean_symlink (src, dest, root):
1104 src = src.replace(root, '', 1)
1105 dest = dest.replace(root, '', 1)
1106 dest = os.path.dirname(dest)
1107 new_src = '../' * len(dest.split('/'))
1108 return new_src + src
1110 ################################################################################
1112 def temp_filename(directory=None, dotprefix=None, perms=0700):
1113 """Return a secure and unique filename by pre-creating it.
1114 If 'directory' is non-null, it will be the directory the file is pre-created in.
1115 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1118 old_tempdir = tempfile.tempdir
1119 tempfile.tempdir = directory
1121 filename = tempfile.mktemp()
1124 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1125 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1129 tempfile.tempdir = old_tempdir
1133 ################################################################################
1137 Cnf = apt_pkg.newConfiguration()
1138 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1140 if which_conf_file() != default_config:
1141 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1143 ################################################################################