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):
235 # Make sure we have a Files: field to parse...
236 if not changes.has_key("files"):
239 # Make sure we recognise the format of the Files: field
240 format = changes.get("format", "")
242 format = float(format)
243 if not is_a_dsc and (format < 1.5 or format > 2.0):
244 raise nk_format_exc, format
246 # Parse each entry/line:
247 for i in changes["files"].split('\n'):
251 section = priority = ""
254 (md5, size, name) = s
256 (md5, size, section, priority, name) = s
258 raise changes_parse_error_exc, i
265 (section, component) = extract_component_from_section(section)
267 files[name] = Dict(md5sum=md5, size=size, section=section,
268 priority=priority, component=component)
272 ################################################################################
274 def force_to_utf8(s):
275 """Forces a string to UTF-8. If the string isn't already UTF-8,
276 it's assumed to be ISO-8859-1."""
281 latin1_s = unicode(s,'iso8859-1')
282 return latin1_s.encode('utf-8')
284 def rfc2047_encode(s):
285 """Encodes a (header) string per RFC2047 if necessary. If the
286 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
288 codecs.lookup('ascii')[1](s)
293 codecs.lookup('utf-8')[1](s)
294 h = email.Header.Header(s, 'utf-8', 998)
297 h = email.Header.Header(s, 'iso-8859-1', 998)
300 ################################################################################
302 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
303 # with it. I know - I'll fix the suckage and make things
306 def fix_maintainer (maintainer):
307 """Parses a Maintainer or Changed-By field and returns:
308 (1) an RFC822 compatible version,
309 (2) an RFC2047 compatible version,
313 The name is forced to UTF-8 for both (1) and (3). If the name field
314 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
315 switched to 'email (name)' format."""
316 maintainer = maintainer.strip()
318 return ('', '', '', '')
320 if maintainer.find("<") == -1:
323 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
324 email = maintainer[1:-1]
327 m = re_parse_maintainer.match(maintainer)
329 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
333 # Get an RFC2047 compliant version of the name
334 rfc2047_name = rfc2047_encode(name)
336 # Force the name to be UTF-8
337 name = force_to_utf8(name)
339 if name.find(',') != -1 or name.find('.') != -1:
340 rfc822_maint = "%s (%s)" % (email, name)
341 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
343 rfc822_maint = "%s <%s>" % (name, email)
344 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
346 if email.find("@") == -1 and email.find("buildd_") != 0:
347 raise ParseMaintError, "No @ found in email address part."
349 return (rfc822_maint, rfc2047_maint, name, email)
351 ################################################################################
353 # sendmail wrapper, takes _either_ a message string or a file as arguments
354 def send_mail (message, filename=""):
355 # If we've been passed a string dump it into a temporary file
357 filename = tempfile.mktemp()
358 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
359 os.write (fd, message)
363 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
365 raise sendmail_failed_exc, output
367 # Clean up any temporary files
371 ################################################################################
373 def poolify (source, component):
376 if source[:3] == "lib":
377 return component + source[:4] + '/' + source + '/'
379 return component + source[:1] + '/' + source + '/'
381 ################################################################################
383 def move (src, dest, overwrite = 0, perms = 0664):
384 if os.path.exists(dest) and os.path.isdir(dest):
387 dest_dir = os.path.dirname(dest)
388 if not os.path.exists(dest_dir):
389 umask = os.umask(00000)
390 os.makedirs(dest_dir, 02775)
392 #print "Moving %s to %s..." % (src, dest)
393 if os.path.exists(dest) and os.path.isdir(dest):
394 dest += '/' + os.path.basename(src)
395 # Don't overwrite unless forced to
396 if os.path.exists(dest):
398 fubar("Can't move %s to %s - file already exists." % (src, dest))
400 if not os.access(dest, os.W_OK):
401 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
402 shutil.copy2(src, dest)
403 os.chmod(dest, perms)
406 def copy (src, dest, overwrite = 0, perms = 0664):
407 if os.path.exists(dest) and os.path.isdir(dest):
410 dest_dir = os.path.dirname(dest)
411 if not os.path.exists(dest_dir):
412 umask = os.umask(00000)
413 os.makedirs(dest_dir, 02775)
415 #print "Copying %s to %s..." % (src, dest)
416 if os.path.exists(dest) and os.path.isdir(dest):
417 dest += '/' + os.path.basename(src)
418 # Don't overwrite unless forced to
419 if os.path.exists(dest):
421 raise file_exists_exc
423 if not os.access(dest, os.W_OK):
424 raise cant_overwrite_exc
425 shutil.copy2(src, dest)
426 os.chmod(dest, perms)
428 ################################################################################
431 res = socket.gethostbyaddr(socket.gethostname())
432 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
433 if database_hostname:
434 return database_hostname
438 def which_conf_file ():
439 res = socket.gethostbyaddr(socket.gethostname())
440 if Cnf.get("Config::" + res[0] + "::DakConfig"):
441 return Cnf["Config::" + res[0] + "::DakConfig"]
443 return default_config
445 def which_apt_conf_file ():
446 res = socket.gethostbyaddr(socket.gethostname())
447 if Cnf.get("Config::" + res[0] + "::AptConfig"):
448 return Cnf["Config::" + res[0] + "::AptConfig"]
450 return default_apt_config
452 ################################################################################
454 # Escape characters which have meaning to SQL's regex comparison operator ('~')
455 # (woefully incomplete)
458 s = s.replace('+', '\\\\+')
459 s = s.replace('.', '\\\\.')
462 ################################################################################
464 # Perform a substition of template
465 def TemplateSubst(map, filename):
466 file = open_file(filename)
467 template = file.read()
469 template = template.replace(x,map[x])
473 ################################################################################
475 def fubar(msg, exit_code=1):
476 sys.stderr.write("E: %s\n" % (msg))
480 sys.stderr.write("W: %s\n" % (msg))
482 ################################################################################
484 # Returns the user name with a laughable attempt at rfc822 conformancy
485 # (read: removing stray periods).
487 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
489 ################################################################################
499 return ("%d%s" % (c, t))
501 ################################################################################
503 def cc_fix_changes (changes):
504 o = changes.get("architecture", "")
506 del changes["architecture"]
507 changes["architecture"] = {}
509 changes["architecture"][j] = 1
511 # Sort by source name, source version, 'have source', and then by filename
512 def changes_compare (a, b):
514 a_changes = parse_changes(a)
519 b_changes = parse_changes(b)
523 cc_fix_changes (a_changes)
524 cc_fix_changes (b_changes)
526 # Sort by source name
527 a_source = a_changes.get("source")
528 b_source = b_changes.get("source")
529 q = cmp (a_source, b_source)
533 # Sort by source version
534 a_version = a_changes.get("version", "0")
535 b_version = b_changes.get("version", "0")
536 q = apt_pkg.VersionCompare(a_version, b_version)
540 # Sort by 'have source'
541 a_has_source = a_changes["architecture"].get("source")
542 b_has_source = b_changes["architecture"].get("source")
543 if a_has_source and not b_has_source:
545 elif b_has_source and not a_has_source:
548 # Fall back to sort by filename
551 ################################################################################
553 def find_next_free (dest, too_many=100):
556 while os.path.exists(dest) and extra < too_many:
557 dest = orig_dest + '.' + repr(extra)
559 if extra >= too_many:
560 raise tried_too_hard_exc
563 ################################################################################
565 def result_join (original, sep = '\t'):
567 for i in xrange(len(original)):
568 if original[i] == None:
571 list.append(original[i])
572 return sep.join(list)
574 ################################################################################
576 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
578 for line in str.split('\n'):
580 if line or include_blank_lines:
581 out += "%s%s\n" % (prefix, line)
582 # Strip trailing new line
587 ################################################################################
589 def validate_changes_file_arg(filename, require_changes=1):
590 """'filename' is either a .changes or .dak file. If 'filename' is a
591 .dak file, it's changed to be the corresponding .changes file. The
592 function then checks if the .changes file a) exists and b) is
593 readable and returns the .changes filename if so. If there's a
594 problem, the next action depends on the option 'require_changes'
597 o If 'require_changes' == -1, errors are ignored and the .changes
598 filename is returned.
599 o If 'require_changes' == 0, a warning is given and 'None' is returned.
600 o If 'require_changes' == 1, a fatal error is raised.
604 orig_filename = filename
605 if filename.endswith(".dak"):
606 filename = filename[:-4]+".changes"
608 if not filename.endswith(".changes"):
609 error = "invalid file type; not a changes file"
611 if not os.access(filename,os.R_OK):
612 if os.path.exists(filename):
613 error = "permission denied"
615 error = "file not found"
618 if require_changes == 1:
619 fubar("%s: %s." % (orig_filename, error))
620 elif require_changes == 0:
621 warn("Skipping %s - %s" % (orig_filename, error))
623 else: # We only care about the .dak file
628 ################################################################################
631 return (arch != "source" and arch != "all")
633 ################################################################################
635 def join_with_commas_and(list):
636 if len(list) == 0: return "nothing"
637 if len(list) == 1: return list[0]
638 return ", ".join(list[:-1]) + " and " + list[-1]
640 ################################################################################
645 (pkg, version, constraint) = atom
647 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
650 pp_deps.append(pp_dep)
651 return " |".join(pp_deps)
653 ################################################################################
658 ################################################################################
660 # Handle -a, -c and -s arguments; returns them as SQL constraints
661 def parse_args(Options):
665 for suite in split_args(Options["Suite"]):
666 suite_id = database.get_suite_id(suite)
668 warn("suite '%s' not recognised." % (suite))
670 suite_ids_list.append(suite_id)
672 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
674 fubar("No valid suite given.")
679 if Options["Component"]:
680 component_ids_list = []
681 for component in split_args(Options["Component"]):
682 component_id = database.get_component_id(component)
683 if component_id == -1:
684 warn("component '%s' not recognised." % (component))
686 component_ids_list.append(component_id)
687 if component_ids_list:
688 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
690 fubar("No valid component given.")
694 # Process architecture
695 con_architectures = ""
696 if Options["Architecture"]:
699 for architecture in split_args(Options["Architecture"]):
700 if architecture == "source":
703 architecture_id = database.get_architecture_id(architecture)
704 if architecture_id == -1:
705 warn("architecture '%s' not recognised." % (architecture))
707 arch_ids_list.append(architecture_id)
709 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
712 fubar("No valid architecture given.")
716 return (con_suites, con_architectures, con_components, check_source)
718 ################################################################################
720 # Inspired(tm) by Bryn Keller's print_exc_plus (See
721 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
724 tb = sys.exc_info()[2]
733 traceback.print_exc()
735 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
736 frame.f_code.co_filename,
738 for key, value in frame.f_locals.items():
739 print "\t%20s = " % key,
743 print "<unable to print>"
745 ################################################################################
747 def try_with_debug(function):
755 ################################################################################
757 # Function for use in sorting lists of architectures.
758 # Sorts normally except that 'source' dominates all others.
760 def arch_compare_sw (a, b):
761 if a == "source" and b == "source":
770 ################################################################################
772 # Split command line arguments which can be separated by either commas
773 # or whitespace. If dwim is set, it will complain about string ending
774 # in comma since this usually means someone did 'dak ls -a i386, m68k
775 # foo' or something and the inevitable confusion resulting from 'm68k'
776 # being treated as an argument is undesirable.
778 def split_args (s, dwim=1):
779 if s.find(",") == -1:
782 if s[-1:] == "," and dwim:
783 fubar("split_args: found trailing comma, spurious space maybe?")
786 ################################################################################
788 def Dict(**dict): return dict
790 ########################################
792 # Our very own version of commands.getouputstatus(), hacked to support
794 def gpgv_get_status_output(cmd, status_read, status_write):
795 cmd = ['/bin/sh', '-c', cmd]
796 p2cread, p2cwrite = os.pipe()
797 c2pread, c2pwrite = os.pipe()
798 errout, errin = os.pipe()
808 for i in range(3, 256):
809 if i != status_write:
815 os.execvp(cmd[0], cmd)
821 os.dup2(c2pread, c2pwrite)
822 os.dup2(errout, errin)
826 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
829 r = os.read(fd, 8196)
832 if fd == c2pwrite or fd == errin:
834 elif fd == status_read:
837 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
839 pid, exit_status = os.waitpid(pid, 0)
841 os.close(status_write)
842 os.close(status_read)
852 return output, status, exit_status
854 ################################################################################
856 def process_gpgv_output(status):
857 # Process the status-fd output
860 for line in status.split('\n'):
866 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
868 (gnupg, keyword) = split[:2]
869 if gnupg != "[GNUPG:]":
870 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
873 if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
874 internal_error += "found duplicate status token ('%s').\n" % (keyword)
877 keywords[keyword] = args
879 return (keywords, internal_error)
881 ################################################################################
883 def retrieve_key (filename, keyserver=None, keyring=None):
884 """Retrieve the key that signed 'filename' from 'keyserver' and
885 add it to 'keyring'. Returns nothing on success, or an error message
888 # Defaults for keyserver and keyring
890 keyserver = Cnf["Dinstall::KeyServer"]
892 keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
894 # Ensure the filename contains no shell meta-characters or other badness
895 if not re_taint_free.match(filename):
896 return "%s: tainted filename" % (filename)
898 # Invoke gpgv on the file
899 status_read, status_write = os.pipe();
900 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
901 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
903 # Process the status-fd output
904 (keywords, internal_error) = process_gpgv_output(status)
906 return internal_error
908 if not keywords.has_key("NO_PUBKEY"):
909 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
911 fingerprint = keywords["NO_PUBKEY"][0]
912 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
913 # it'll try to create a lockfile in /dev. A better solution might
914 # be a tempfile or something.
915 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
916 % (Cnf["Dinstall::SigningKeyring"])
917 cmd += " --keyring %s --keyserver %s --recv-key %s" \
918 % (keyring, keyserver, fingerprint)
919 (result, output) = commands.getstatusoutput(cmd)
921 return "'%s' failed with exit code %s" % (cmd, result)
925 ################################################################################
927 def gpg_keyring_args(keyrings=None):
929 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
931 return " ".join(["--keyring %s" % x for x in keyrings])
933 ################################################################################
935 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
936 """Check the signature of a file and return the fingerprint if the
937 signature is valid or 'None' if it's not. The first argument is the
938 filename whose signature should be checked. The second argument is a
939 reject function and is called when an error is found. The reject()
940 function must allow for two arguments: the first is the error message,
941 the second is an optional prefix string. It's possible for reject()
942 to be called more than once during an invocation of check_signature().
943 The third argument is optional and is the name of the files the
944 detached signature applies to. The fourth argument is optional and is
945 a *list* of keyrings to use. 'autofetch' can either be None, True or
946 False. If None, the default behaviour specified in the config will be
949 # Ensure the filename contains no shell meta-characters or other badness
950 if not re_taint_free.match(sig_filename):
951 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
954 if data_filename and not re_taint_free.match(data_filename):
955 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
959 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
961 # Autofetch the signing key if that's enabled
962 if autofetch == None:
963 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
965 error_msg = retrieve_key(sig_filename)
970 # Build the command line
971 status_read, status_write = os.pipe();
972 cmd = "gpgv --status-fd %s %s %s %s" % (
973 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
975 # Invoke gpgv on the file
976 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
978 # Process the status-fd output
979 (keywords, internal_error) = process_gpgv_output(status)
981 # If we failed to parse the status-fd output, let's just whine and bail now
983 reject("internal error while performing signature check on %s." % (sig_filename))
984 reject(internal_error, "")
985 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
989 # Now check for obviously bad things in the processed output
990 if keywords.has_key("KEYREVOKED"):
991 reject("The key used to sign %s has been revoked." % (sig_filename))
993 if keywords.has_key("BADSIG"):
994 reject("bad signature on %s." % (sig_filename))
996 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
997 reject("failed to check signature on %s." % (sig_filename))
999 if keywords.has_key("NO_PUBKEY"):
1000 args = keywords["NO_PUBKEY"]
1003 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1005 if keywords.has_key("BADARMOR"):
1006 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1008 if keywords.has_key("NODATA"):
1009 reject("no signature found in %s." % (sig_filename))
1011 if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1012 args = keywords["KEYEXPIRED"]
1015 reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
1021 # Next check gpgv exited with a zero return code
1023 reject("gpgv failed while checking %s." % (sig_filename))
1025 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1027 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1030 # Sanity check the good stuff we expect
1031 if not keywords.has_key("VALIDSIG"):
1032 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1035 args = keywords["VALIDSIG"]
1037 reject("internal error while checking signature on %s." % (sig_filename))
1040 fingerprint = args[0]
1041 if not keywords.has_key("GOODSIG"):
1042 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1044 if not keywords.has_key("SIG_ID"):
1045 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1048 # Finally ensure there's not something we don't recognise
1049 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1050 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1051 NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1053 for keyword in keywords.keys():
1054 if not known_keywords.has_key(keyword):
1055 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1063 ################################################################################
1065 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1067 def wrap(paragraph, max_length, prefix=""):
1071 words = paragraph.split()
1074 word_size = len(word)
1075 if word_size > max_length:
1077 s += line + '\n' + prefix
1078 s += word + '\n' + prefix
1081 new_length = len(line) + word_size + 1
1082 if new_length > max_length:
1083 s += line + '\n' + prefix
1096 ################################################################################
1098 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1099 # Returns fixed 'src'
1100 def clean_symlink (src, dest, root):
1101 src = src.replace(root, '', 1)
1102 dest = dest.replace(root, '', 1)
1103 dest = os.path.dirname(dest)
1104 new_src = '../' * len(dest.split('/'))
1105 return new_src + src
1107 ################################################################################
1109 def temp_filename(directory=None, dotprefix=None, perms=0700):
1110 """Return a secure and unique filename by pre-creating it.
1111 If 'directory' is non-null, it will be the directory the file is pre-created in.
1112 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1115 old_tempdir = tempfile.tempdir
1116 tempfile.tempdir = directory
1118 filename = tempfile.mktemp()
1121 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1122 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1126 tempfile.tempdir = old_tempdir
1130 ################################################################################
1134 Cnf = apt_pkg.newConfiguration()
1135 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1137 if which_conf_file() != default_config:
1138 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1140 ################################################################################