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 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 ################################################################################
100 def extract_component_from_section(section):
103 if section.find('/') != -1:
104 component = section.split('/')[0]
105 if component.lower() == "non-us" and section.find('/') != -1:
106 s = component + '/' + section.split('/')[1]
107 if Cnf.has_key("Component::%s" % s): # Avoid e.g. non-US/libs
110 if section.lower() == "non-us":
111 component = "non-US/main"
113 # non-US prefix is case insensitive
114 if component.lower()[:6] == "non-us":
115 component = "non-US"+component[6:]
117 # Expand default component
119 if Cnf.has_key("Component::%s" % section):
123 elif component == "non-US":
124 component = "non-US/main"
126 return (section, component)
128 ################################################################################
130 def parse_changes(filename, signing_rules=0):
131 """Parses a changes file and returns a dictionary where each field is a
132 key. The mandatory first argument is the filename of the .changes
135 signing_rules is an optional argument:
137 o If signing_rules == -1, no signature is required.
138 o If signing_rules == 0 (the default), a signature is required.
139 o If signing_rules == 1, it turns on the same strict format checking
142 The rules for (signing_rules == 1)-mode are:
144 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
145 followed by any PGP header data and must end with a blank line.
147 o The data section must end with a blank line and must be followed by
148 "-----BEGIN PGP SIGNATURE-----".
154 changes_in = open_file(filename)
155 lines = changes_in.readlines()
158 raise changes_parse_error_exc, "[Empty changes file]"
160 # Reindex by line number so we can easily verify the format of
166 indexed_lines[index] = line[:-1]
170 num_of_lines = len(indexed_lines.keys())
173 while index < num_of_lines:
175 line = indexed_lines[index]
177 if signing_rules == 1:
179 if index > num_of_lines:
180 raise invalid_dsc_format_exc, index
181 line = indexed_lines[index]
182 if not line.startswith("-----BEGIN PGP SIGNATURE"):
183 raise invalid_dsc_format_exc, index
188 if line.startswith("-----BEGIN PGP SIGNATURE"):
190 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
192 if signing_rules == 1:
193 while index < num_of_lines and line != "":
195 line = indexed_lines[index]
197 # If we're not inside the signed data, don't process anything
198 if signing_rules >= 0 and not inside_signature:
200 slf = re_single_line_field.match(line)
202 field = slf.groups()[0].lower()
203 changes[field] = slf.groups()[1]
207 changes[field] += '\n'
209 mlf = re_multi_line_field.match(line)
212 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
213 if first == 1 and changes[field] != "":
214 changes[field] += '\n'
216 changes[field] += mlf.groups()[0] + '\n'
220 if signing_rules == 1 and inside_signature:
221 raise invalid_dsc_format_exc, index
224 changes["filecontents"] = "".join(lines)
227 raise changes_parse_error_exc, error
231 ################################################################################
233 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
235 def build_file_list(changes, is_a_dsc=0):
238 # Make sure we have a Files: field to parse...
239 if not changes.has_key("files"):
242 # Make sure we recognise the format of the Files: field
243 format = changes.get("format", "")
245 format = float(format)
246 if not is_a_dsc and (format < 1.5 or format > 2.0):
247 raise nk_format_exc, format
249 # Parse each entry/line:
250 for i in changes["files"].split('\n'):
254 section = priority = ""
257 (md5, size, name) = s
259 (md5, size, section, priority, name) = s
261 raise changes_parse_error_exc, i
268 (section, component) = extract_component_from_section(section)
270 files[name] = Dict(md5sum=md5, size=size, section=section,
271 priority=priority, component=component)
275 ################################################################################
277 def force_to_utf8(s):
278 """Forces a string to UTF-8. If the string isn't already UTF-8,
279 it's assumed to be ISO-8859-1."""
284 latin1_s = unicode(s,'iso8859-1')
285 return latin1_s.encode('utf-8')
287 def rfc2047_encode(s):
288 """Encodes a (header) string per RFC2047 if necessary. If the
289 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
291 codecs.lookup('ascii')[1](s)
296 codecs.lookup('utf-8')[1](s)
297 h = email.Header.Header(s, 'utf-8', 998)
300 h = email.Header.Header(s, 'iso-8859-1', 998)
303 ################################################################################
305 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
306 # with it. I know - I'll fix the suckage and make things
309 def fix_maintainer (maintainer):
310 """Parses a Maintainer or Changed-By field and returns:
311 (1) an RFC822 compatible version,
312 (2) an RFC2047 compatible version,
316 The name is forced to UTF-8 for both (1) and (3). If the name field
317 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
318 switched to 'email (name)' format."""
319 maintainer = maintainer.strip()
321 return ('', '', '', '')
323 if maintainer.find("<") == -1:
326 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
327 email = maintainer[1:-1]
330 m = re_parse_maintainer.match(maintainer)
332 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
336 # Get an RFC2047 compliant version of the name
337 rfc2047_name = rfc2047_encode(name)
339 # Force the name to be UTF-8
340 name = force_to_utf8(name)
342 if name.find(',') != -1 or name.find('.') != -1:
343 rfc822_maint = "%s (%s)" % (email, name)
344 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
346 rfc822_maint = "%s <%s>" % (name, email)
347 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
349 if email.find("@") == -1 and email.find("buildd_") != 0:
350 raise ParseMaintError, "No @ found in email address part."
352 return (rfc822_maint, rfc2047_maint, name, email)
354 ################################################################################
356 # sendmail wrapper, takes _either_ a message string or a file as arguments
357 def send_mail (message, filename=""):
358 # If we've been passed a string dump it into a temporary file
360 filename = tempfile.mktemp()
361 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
362 os.write (fd, message)
366 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
368 raise sendmail_failed_exc, output
370 # Clean up any temporary files
374 ################################################################################
376 def poolify (source, component):
379 # FIXME: this is nasty
380 component = component.lower().replace("non-us/", "non-US/")
381 if source[:3] == "lib":
382 return component + source[:4] + '/' + source + '/'
384 return component + source[:1] + '/' + source + '/'
386 ################################################################################
388 def move (src, dest, overwrite = 0, perms = 0664):
389 if os.path.exists(dest) and os.path.isdir(dest):
392 dest_dir = os.path.dirname(dest)
393 if not os.path.exists(dest_dir):
394 umask = os.umask(00000)
395 os.makedirs(dest_dir, 02775)
397 #print "Moving %s to %s..." % (src, dest)
398 if os.path.exists(dest) and os.path.isdir(dest):
399 dest += '/' + os.path.basename(src)
400 # Don't overwrite unless forced to
401 if os.path.exists(dest):
403 fubar("Can't move %s to %s - file already exists." % (src, dest))
405 if not os.access(dest, os.W_OK):
406 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
407 shutil.copy2(src, dest)
408 os.chmod(dest, perms)
411 def copy (src, dest, overwrite = 0, perms = 0664):
412 if os.path.exists(dest) and os.path.isdir(dest):
415 dest_dir = os.path.dirname(dest)
416 if not os.path.exists(dest_dir):
417 umask = os.umask(00000)
418 os.makedirs(dest_dir, 02775)
420 #print "Copying %s to %s..." % (src, dest)
421 if os.path.exists(dest) and os.path.isdir(dest):
422 dest += '/' + os.path.basename(src)
423 # Don't overwrite unless forced to
424 if os.path.exists(dest):
426 raise file_exists_exc
428 if not os.access(dest, os.W_OK):
429 raise cant_overwrite_exc
430 shutil.copy2(src, dest)
431 os.chmod(dest, perms)
433 ################################################################################
436 res = socket.gethostbyaddr(socket.gethostname())
437 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
438 if database_hostname:
439 return database_hostname
443 def which_conf_file ():
444 res = socket.gethostbyaddr(socket.gethostname())
445 if Cnf.get("Config::" + res[0] + "::DakConfig"):
446 return Cnf["Config::" + res[0] + "::DakConfig"]
448 return default_config
450 def which_apt_conf_file ():
451 res = socket.gethostbyaddr(socket.gethostname())
452 if Cnf.get("Config::" + res[0] + "::AptConfig"):
453 return Cnf["Config::" + res[0] + "::AptConfig"]
455 return default_apt_config
457 ################################################################################
459 # Escape characters which have meaning to SQL's regex comparison operator ('~')
460 # (woefully incomplete)
463 s = s.replace('+', '\\\\+')
464 s = s.replace('.', '\\\\.')
467 ################################################################################
469 # Perform a substition of template
470 def TemplateSubst(map, filename):
471 file = open_file(filename)
472 template = file.read()
474 template = template.replace(x,map[x])
478 ################################################################################
480 def fubar(msg, exit_code=1):
481 sys.stderr.write("E: %s\n" % (msg))
485 sys.stderr.write("W: %s\n" % (msg))
487 ################################################################################
489 # Returns the user name with a laughable attempt at rfc822 conformancy
490 # (read: removing stray periods).
492 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
494 ################################################################################
504 return ("%d%s" % (c, t))
506 ################################################################################
508 def cc_fix_changes (changes):
509 o = changes.get("architecture", "")
511 del changes["architecture"]
512 changes["architecture"] = {}
514 changes["architecture"][j] = 1
516 # Sort by source name, source version, 'have source', and then by filename
517 def changes_compare (a, b):
519 a_changes = parse_changes(a)
524 b_changes = parse_changes(b)
528 cc_fix_changes (a_changes)
529 cc_fix_changes (b_changes)
531 # Sort by source name
532 a_source = a_changes.get("source")
533 b_source = b_changes.get("source")
534 q = cmp (a_source, b_source)
538 # Sort by source version
539 a_version = a_changes.get("version", "0")
540 b_version = b_changes.get("version", "0")
541 q = apt_pkg.VersionCompare(a_version, b_version)
545 # Sort by 'have source'
546 a_has_source = a_changes["architecture"].get("source")
547 b_has_source = b_changes["architecture"].get("source")
548 if a_has_source and not b_has_source:
550 elif b_has_source and not a_has_source:
553 # Fall back to sort by filename
556 ################################################################################
558 def find_next_free (dest, too_many=100):
561 while os.path.exists(dest) and extra < too_many:
562 dest = orig_dest + '.' + repr(extra)
564 if extra >= too_many:
565 raise tried_too_hard_exc
568 ################################################################################
570 def result_join (original, sep = '\t'):
572 for i in xrange(len(original)):
573 if original[i] == None:
576 list.append(original[i])
577 return sep.join(list)
579 ################################################################################
581 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
583 for line in str.split('\n'):
585 if line or include_blank_lines:
586 out += "%s%s\n" % (prefix, line)
587 # Strip trailing new line
592 ################################################################################
594 def validate_changes_file_arg(filename, require_changes=1):
595 """'filename' is either a .changes or .dak file. If 'filename' is a
596 .dak file, it's changed to be the corresponding .changes file. The
597 function then checks if the .changes file a) exists and b) is
598 readable and returns the .changes filename if so. If there's a
599 problem, the next action depends on the option 'require_changes'
602 o If 'require_changes' == -1, errors are ignored and the .changes
603 filename is returned.
604 o If 'require_changes' == 0, a warning is given and 'None' is returned.
605 o If 'require_changes' == 1, a fatal error is raised.
609 orig_filename = filename
610 if filename.endswith(".dak"):
611 filename = filename[:-4]+".changes"
613 if not filename.endswith(".changes"):
614 error = "invalid file type; not a changes file"
616 if not os.access(filename,os.R_OK):
617 if os.path.exists(filename):
618 error = "permission denied"
620 error = "file not found"
623 if require_changes == 1:
624 fubar("%s: %s." % (orig_filename, error))
625 elif require_changes == 0:
626 warn("Skipping %s - %s" % (orig_filename, error))
628 else: # We only care about the .dak file
633 ################################################################################
636 return (arch != "source" and arch != "all")
638 ################################################################################
640 def join_with_commas_and(list):
641 if len(list) == 0: return "nothing"
642 if len(list) == 1: return list[0]
643 return ", ".join(list[:-1]) + " and " + list[-1]
645 ################################################################################
650 (pkg, version, constraint) = atom
652 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
655 pp_deps.append(pp_dep)
656 return " |".join(pp_deps)
658 ################################################################################
663 ################################################################################
665 # Handle -a, -c and -s arguments; returns them as SQL constraints
666 def parse_args(Options):
670 for suite in split_args(Options["Suite"]):
671 suite_id = database.get_suite_id(suite)
673 warn("suite '%s' not recognised." % (suite))
675 suite_ids_list.append(suite_id)
677 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
679 fubar("No valid suite given.")
684 if Options["Component"]:
685 component_ids_list = []
686 for component in split_args(Options["Component"]):
687 component_id = database.get_component_id(component)
688 if component_id == -1:
689 warn("component '%s' not recognised." % (component))
691 component_ids_list.append(component_id)
692 if component_ids_list:
693 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
695 fubar("No valid component given.")
699 # Process architecture
700 con_architectures = ""
701 if Options["Architecture"]:
704 for architecture in split_args(Options["Architecture"]):
705 if architecture == "source":
708 architecture_id = database.get_architecture_id(architecture)
709 if architecture_id == -1:
710 warn("architecture '%s' not recognised." % (architecture))
712 arch_ids_list.append(architecture_id)
714 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
717 fubar("No valid architecture given.")
721 return (con_suites, con_architectures, con_components, check_source)
723 ################################################################################
725 # Inspired(tm) by Bryn Keller's print_exc_plus (See
726 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
729 tb = sys.exc_info()[2]
738 traceback.print_exc()
740 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
741 frame.f_code.co_filename,
743 for key, value in frame.f_locals.items():
744 print "\t%20s = " % key,
748 print "<unable to print>"
750 ################################################################################
752 def try_with_debug(function):
760 ################################################################################
762 # Function for use in sorting lists of architectures.
763 # Sorts normally except that 'source' dominates all others.
765 def arch_compare_sw (a, b):
766 if a == "source" and b == "source":
775 ################################################################################
777 # Split command line arguments which can be separated by either commas
778 # or whitespace. If dwim is set, it will complain about string ending
779 # in comma since this usually means someone did 'dak ls -a i386, m68k
780 # foo' or something and the inevitable confusion resulting from 'm68k'
781 # being treated as an argument is undesirable.
783 def split_args (s, dwim=1):
784 if s.find(",") == -1:
787 if s[-1:] == "," and dwim:
788 fubar("split_args: found trailing comma, spurious space maybe?")
791 ################################################################################
793 def Dict(**dict): return dict
795 ########################################
797 # Our very own version of commands.getouputstatus(), hacked to support
799 def gpgv_get_status_output(cmd, status_read, status_write):
800 cmd = ['/bin/sh', '-c', cmd]
801 p2cread, p2cwrite = os.pipe()
802 c2pread, c2pwrite = os.pipe()
803 errout, errin = os.pipe()
813 for i in range(3, 256):
814 if i != status_write:
820 os.execvp(cmd[0], cmd)
826 os.dup2(c2pread, c2pwrite)
827 os.dup2(errout, errin)
831 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
834 r = os.read(fd, 8196)
837 if fd == c2pwrite or fd == errin:
839 elif fd == status_read:
842 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
844 pid, exit_status = os.waitpid(pid, 0)
846 os.close(status_write)
847 os.close(status_read)
857 return output, status, exit_status
859 ################################################################################
861 def process_gpgv_output(status):
862 # Process the status-fd output
865 for line in status.split('\n'):
871 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
873 (gnupg, keyword) = split[:2]
874 if gnupg != "[GNUPG:]":
875 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
878 if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
879 internal_error += "found duplicate status token ('%s').\n" % (keyword)
882 keywords[keyword] = args
884 return (keywords, internal_error)
886 ################################################################################
888 def retrieve_key (filename, keyserver=None, keyring=None):
889 """Retrieve the key that signed 'filename' from 'keyserver' and
890 add it to 'keyring'. Returns nothing on success, or an error message
893 # Defaults for keyserver and keyring
895 keyserver = Cnf["Dinstall::KeyServer"]
897 keyring = Cnf["Dinstall::GPGKeyring"]
899 # Ensure the filename contains no shell meta-characters or other badness
900 if not re_taint_free.match(filename):
901 return "%s: tainted filename" % (filename)
903 # Invoke gpgv on the file
904 status_read, status_write = os.pipe();
905 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
906 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
908 # Process the status-fd output
909 (keywords, internal_error) = process_gpgv_output(status)
911 return internal_error
913 if not keywords.has_key("NO_PUBKEY"):
914 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
916 fingerprint = keywords["NO_PUBKEY"][0]
917 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
918 # it'll try to create a lockfile in /dev. A better solution might
919 # be a tempfile or something.
920 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
921 % (Cnf["Dinstall::SigningKeyring"])
922 cmd += " --keyring %s --keyserver %s --recv-key %s" \
923 % (keyring, keyserver, fingerprint)
924 (result, output) = commands.getstatusoutput(cmd)
926 return "'%s' failed with exit code %s" % (cmd, result)
930 ################################################################################
932 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
933 """Check the signature of a file and return the fingerprint if the
934 signature is valid or 'None' if it's not. The first argument is the
935 filename whose signature should be checked. The second argument is a
936 reject function and is called when an error is found. The reject()
937 function must allow for two arguments: the first is the error message,
938 the second is an optional prefix string. It's possible for reject()
939 to be called more than once during an invocation of check_signature().
940 The third argument is optional and is the name of the files the
941 detached signature applies to. The fourth argument is optional and is
942 a *list* of keyrings to use. 'autofetch' can either be None, True or
943 False. If None, the default behaviour specified in the config will be
946 # Ensure the filename contains no shell meta-characters or other badness
947 if not re_taint_free.match(sig_filename):
948 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
951 if data_filename and not re_taint_free.match(data_filename):
952 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
956 keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
958 # Autofetch the signing key if that's enabled
959 if autofetch == None:
960 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
962 error_msg = retrieve_key(sig_filename)
967 # Build the command line
968 status_read, status_write = os.pipe();
969 cmd = "gpgv --status-fd %s" % (status_write)
970 for keyring in keyrings:
971 cmd += " --keyring %s" % (keyring)
972 cmd += " %s %s" % (sig_filename, data_filename)
973 # Invoke gpgv on the file
974 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
976 # Process the status-fd output
977 (keywords, internal_error) = process_gpgv_output(status)
979 # If we failed to parse the status-fd output, let's just whine and bail now
981 reject("internal error while performing signature check on %s." % (sig_filename))
982 reject(internal_error, "")
983 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
987 # Now check for obviously bad things in the processed output
988 if keywords.has_key("SIGEXPIRED"):
989 reject("The key used to sign %s has expired." % (sig_filename))
991 if keywords.has_key("KEYREVOKED"):
992 reject("The key used to sign %s has been revoked." % (sig_filename))
994 if keywords.has_key("BADSIG"):
995 reject("bad signature on %s." % (sig_filename))
997 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
998 reject("failed to check signature on %s." % (sig_filename))
1000 if keywords.has_key("NO_PUBKEY"):
1001 args = keywords["NO_PUBKEY"]
1004 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1006 if keywords.has_key("BADARMOR"):
1007 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1009 if keywords.has_key("NODATA"):
1010 reject("no signature found in %s." % (sig_filename))
1016 # Next check gpgv exited with a zero return code
1018 reject("gpgv failed while checking %s." % (sig_filename))
1020 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1022 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1025 # Sanity check the good stuff we expect
1026 if not keywords.has_key("VALIDSIG"):
1027 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1030 args = keywords["VALIDSIG"]
1032 reject("internal error while checking signature on %s." % (sig_filename))
1035 fingerprint = args[0]
1036 if not keywords.has_key("GOODSIG"):
1037 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1039 if not keywords.has_key("SIG_ID"):
1040 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1043 # Finally ensure there's not something we don't recognise
1044 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1045 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1048 for keyword in keywords.keys():
1049 if not known_keywords.has_key(keyword):
1050 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1058 ################################################################################
1060 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1062 def wrap(paragraph, max_length, prefix=""):
1066 words = paragraph.split()
1069 word_size = len(word)
1070 if word_size > max_length:
1072 s += line + '\n' + prefix
1073 s += word + '\n' + prefix
1076 new_length = len(line) + word_size + 1
1077 if new_length > max_length:
1078 s += line + '\n' + prefix
1091 ################################################################################
1093 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1094 # Returns fixed 'src'
1095 def clean_symlink (src, dest, root):
1096 src = src.replace(root, '', 1)
1097 dest = dest.replace(root, '', 1)
1098 dest = os.path.dirname(dest)
1099 new_src = '../' * len(dest.split('/'))
1100 return new_src + src
1102 ################################################################################
1104 def temp_filename(directory=None, dotprefix=None, perms=0700):
1105 """Return a secure and unique filename by pre-creating it.
1106 If 'directory' is non-null, it will be the directory the file is pre-created in.
1107 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1110 old_tempdir = tempfile.tempdir
1111 tempfile.tempdir = directory
1113 filename = tempfile.mktemp()
1116 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1117 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1121 tempfile.tempdir = old_tempdir
1125 ################################################################################
1129 Cnf = apt_pkg.newConfiguration()
1130 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1132 if which_conf_file() != default_config:
1133 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1135 ################################################################################