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+)\)$")
46 re_verwithext = re.compile(r"^(\d+)(?:\.(\d+))(?:\s+\((\S+)\))?$")
48 changes_parse_error_exc = "Can't parse line in .changes file"
49 invalid_dsc_format_exc = "Invalid .dsc file"
50 nk_format_exc = "Unknown Format: in .changes file"
51 no_files_exc = "No Files: field in .dsc or .changes file."
52 cant_open_exc = "Can't open file"
53 unknown_hostname_exc = "Unknown hostname"
54 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
55 file_exists_exc = "Destination file exists"
56 sendmail_failed_exc = "Sendmail invocation failed"
57 tried_too_hard_exc = "Tried too hard to find a free filename."
59 default_config = "/etc/dak/dak.conf"
60 default_apt_config = "/etc/dak/apt.conf"
62 ################################################################################
64 class Error(Exception):
65 """Base class for exceptions in this module."""
68 class ParseMaintError(Error):
69 """Exception raised for errors in parsing a maintainer field.
72 message -- explanation of the error
75 def __init__(self, message):
77 self.message = message
79 ################################################################################
81 def open_file(filename, mode='r'):
83 f = open(filename, mode)
85 raise cant_open_exc, filename
88 ################################################################################
90 def our_raw_input(prompt=""):
92 sys.stdout.write(prompt)
98 sys.stderr.write("\nUser interrupt (^D).\n")
101 ################################################################################
103 def extract_component_from_section(section):
106 if section.find('/') != -1:
107 component = section.split('/')[0]
109 # Expand default component
111 if Cnf.has_key("Component::%s" % section):
116 return (section, component)
118 ################################################################################
120 def parse_changes(filename, signing_rules=0):
121 """Parses a changes file and returns a dictionary where each field is a
122 key. The mandatory first argument is the filename of the .changes
125 signing_rules is an optional argument:
127 o If signing_rules == -1, no signature is required.
128 o If signing_rules == 0 (the default), a signature is required.
129 o If signing_rules == 1, it turns on the same strict format checking
132 The rules for (signing_rules == 1)-mode are:
134 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
135 followed by any PGP header data and must end with a blank line.
137 o The data section must end with a blank line and must be followed by
138 "-----BEGIN PGP SIGNATURE-----".
144 changes_in = open_file(filename)
145 lines = changes_in.readlines()
148 raise changes_parse_error_exc, "[Empty changes file]"
150 # Reindex by line number so we can easily verify the format of
156 indexed_lines[index] = line[:-1]
160 num_of_lines = len(indexed_lines.keys())
163 while index < num_of_lines:
165 line = indexed_lines[index]
167 if signing_rules == 1:
169 if index > num_of_lines:
170 raise invalid_dsc_format_exc, index
171 line = indexed_lines[index]
172 if not line.startswith("-----BEGIN PGP SIGNATURE"):
173 raise invalid_dsc_format_exc, index
178 if line.startswith("-----BEGIN PGP SIGNATURE"):
180 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
182 if signing_rules == 1:
183 while index < num_of_lines and line != "":
185 line = indexed_lines[index]
187 # If we're not inside the signed data, don't process anything
188 if signing_rules >= 0 and not inside_signature:
190 slf = re_single_line_field.match(line)
192 field = slf.groups()[0].lower()
193 changes[field] = slf.groups()[1]
197 changes[field] += '\n'
199 mlf = re_multi_line_field.match(line)
202 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
203 if first == 1 and changes[field] != "":
204 changes[field] += '\n'
206 changes[field] += mlf.groups()[0] + '\n'
210 if signing_rules == 1 and inside_signature:
211 raise invalid_dsc_format_exc, index
214 changes["filecontents"] = "".join(lines)
216 if changes.has_key("source"):
217 # Strip the source version in brackets from the source field,
218 # put it in the "source-version" field instead.
219 srcver = re_srchasver.search(changes["source"])
221 changes["source"] = srcver.group(1)
222 changes["source-version"] = srcver.group(2)
225 raise changes_parse_error_exc, error
229 ################################################################################
231 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
233 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
236 # Make sure we have a Files: field to parse...
237 if not changes.has_key(field):
240 # Make sure we recognise the format of the Files: field
241 format = re_verwithext.search(changes.get("format", "0.0"))
243 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
245 format = format.groups()
246 if format[1] == None:
247 format = int(float(format[0])), 0, format[2]
249 format = int(format[0]), int(format[1]), format[2]
250 if format[2] == None:
255 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
257 if (format < (1,5) or format > (1,8)):
258 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
259 if field != "files" and format < (1,8):
260 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
262 includes_section = (not is_a_dsc) and field == "files"
264 # Parse each entry/line:
265 for i in changes[field].split('\n'):
269 section = priority = ""
272 (md5, size, section, priority, name) = s
274 (md5, size, name) = s
276 raise changes_parse_error_exc, i
283 (section, component) = extract_component_from_section(section)
285 files[name] = Dict(size=size, section=section,
286 priority=priority, component=component)
287 files[name][hashname] = md5
291 ################################################################################
293 def force_to_utf8(s):
294 """Forces a string to UTF-8. If the string isn't already UTF-8,
295 it's assumed to be ISO-8859-1."""
300 latin1_s = unicode(s,'iso8859-1')
301 return latin1_s.encode('utf-8')
303 def rfc2047_encode(s):
304 """Encodes a (header) string per RFC2047 if necessary. If the
305 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
307 codecs.lookup('ascii')[1](s)
312 codecs.lookup('utf-8')[1](s)
313 h = email.Header.Header(s, 'utf-8', 998)
316 h = email.Header.Header(s, 'iso-8859-1', 998)
319 ################################################################################
321 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
322 # with it. I know - I'll fix the suckage and make things
325 def fix_maintainer (maintainer):
326 """Parses a Maintainer or Changed-By field and returns:
327 (1) an RFC822 compatible version,
328 (2) an RFC2047 compatible version,
332 The name is forced to UTF-8 for both (1) and (3). If the name field
333 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
334 switched to 'email (name)' format."""
335 maintainer = maintainer.strip()
337 return ('', '', '', '')
339 if maintainer.find("<") == -1:
342 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
343 email = maintainer[1:-1]
346 m = re_parse_maintainer.match(maintainer)
348 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
352 # Get an RFC2047 compliant version of the name
353 rfc2047_name = rfc2047_encode(name)
355 # Force the name to be UTF-8
356 name = force_to_utf8(name)
358 if name.find(',') != -1 or name.find('.') != -1:
359 rfc822_maint = "%s (%s)" % (email, name)
360 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
362 rfc822_maint = "%s <%s>" % (name, email)
363 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
365 if email.find("@") == -1 and email.find("buildd_") != 0:
366 raise ParseMaintError, "No @ found in email address part."
368 return (rfc822_maint, rfc2047_maint, name, email)
370 ################################################################################
372 # sendmail wrapper, takes _either_ a message string or a file as arguments
373 def send_mail (message, filename=""):
374 # If we've been passed a string dump it into a temporary file
376 filename = tempfile.mktemp()
377 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
378 os.write (fd, message)
382 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
384 raise sendmail_failed_exc, output
386 # Clean up any temporary files
390 ################################################################################
392 def poolify (source, component):
395 if source[:3] == "lib":
396 return component + source[:4] + '/' + source + '/'
398 return component + source[:1] + '/' + source + '/'
400 ################################################################################
402 def move (src, dest, overwrite = 0, perms = 0664):
403 if os.path.exists(dest) and os.path.isdir(dest):
406 dest_dir = os.path.dirname(dest)
407 if not os.path.exists(dest_dir):
408 umask = os.umask(00000)
409 os.makedirs(dest_dir, 02775)
411 #print "Moving %s to %s..." % (src, dest)
412 if os.path.exists(dest) and os.path.isdir(dest):
413 dest += '/' + os.path.basename(src)
414 # Don't overwrite unless forced to
415 if os.path.exists(dest):
417 fubar("Can't move %s to %s - file already exists." % (src, dest))
419 if not os.access(dest, os.W_OK):
420 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
421 shutil.copy2(src, dest)
422 os.chmod(dest, perms)
425 def copy (src, dest, overwrite = 0, perms = 0664):
426 if os.path.exists(dest) and os.path.isdir(dest):
429 dest_dir = os.path.dirname(dest)
430 if not os.path.exists(dest_dir):
431 umask = os.umask(00000)
432 os.makedirs(dest_dir, 02775)
434 #print "Copying %s to %s..." % (src, dest)
435 if os.path.exists(dest) and os.path.isdir(dest):
436 dest += '/' + os.path.basename(src)
437 # Don't overwrite unless forced to
438 if os.path.exists(dest):
440 raise file_exists_exc
442 if not os.access(dest, os.W_OK):
443 raise cant_overwrite_exc
444 shutil.copy2(src, dest)
445 os.chmod(dest, perms)
447 ################################################################################
450 res = socket.gethostbyaddr(socket.gethostname())
451 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
452 if database_hostname:
453 return database_hostname
457 def which_conf_file ():
458 res = socket.gethostbyaddr(socket.gethostname())
459 if Cnf.get("Config::" + res[0] + "::DakConfig"):
460 return Cnf["Config::" + res[0] + "::DakConfig"]
462 return default_config
464 def which_apt_conf_file ():
465 res = socket.gethostbyaddr(socket.gethostname())
466 if Cnf.get("Config::" + res[0] + "::AptConfig"):
467 return Cnf["Config::" + res[0] + "::AptConfig"]
469 return default_apt_config
471 ################################################################################
473 # Escape characters which have meaning to SQL's regex comparison operator ('~')
474 # (woefully incomplete)
477 s = s.replace('+', '\\\\+')
478 s = s.replace('.', '\\\\.')
481 ################################################################################
483 # Perform a substition of template
484 def TemplateSubst(map, filename):
485 file = open_file(filename)
486 template = file.read()
488 template = template.replace(x,map[x])
492 ################################################################################
494 def fubar(msg, exit_code=1):
495 sys.stderr.write("E: %s\n" % (msg))
499 sys.stderr.write("W: %s\n" % (msg))
501 ################################################################################
503 # Returns the user name with a laughable attempt at rfc822 conformancy
504 # (read: removing stray periods).
506 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
508 ################################################################################
518 return ("%d%s" % (c, t))
520 ################################################################################
522 def cc_fix_changes (changes):
523 o = changes.get("architecture", "")
525 del changes["architecture"]
526 changes["architecture"] = {}
528 changes["architecture"][j] = 1
530 # Sort by source name, source version, 'have source', and then by filename
531 def changes_compare (a, b):
533 a_changes = parse_changes(a)
538 b_changes = parse_changes(b)
542 cc_fix_changes (a_changes)
543 cc_fix_changes (b_changes)
545 # Sort by source name
546 a_source = a_changes.get("source")
547 b_source = b_changes.get("source")
548 q = cmp (a_source, b_source)
552 # Sort by source version
553 a_version = a_changes.get("version", "0")
554 b_version = b_changes.get("version", "0")
555 q = apt_pkg.VersionCompare(a_version, b_version)
559 # Sort by 'have source'
560 a_has_source = a_changes["architecture"].get("source")
561 b_has_source = b_changes["architecture"].get("source")
562 if a_has_source and not b_has_source:
564 elif b_has_source and not a_has_source:
567 # Fall back to sort by filename
570 ################################################################################
572 def find_next_free (dest, too_many=100):
575 while os.path.exists(dest) and extra < too_many:
576 dest = orig_dest + '.' + repr(extra)
578 if extra >= too_many:
579 raise tried_too_hard_exc
582 ################################################################################
584 def result_join (original, sep = '\t'):
586 for i in xrange(len(original)):
587 if original[i] == None:
590 list.append(original[i])
591 return sep.join(list)
593 ################################################################################
595 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
597 for line in str.split('\n'):
599 if line or include_blank_lines:
600 out += "%s%s\n" % (prefix, line)
601 # Strip trailing new line
606 ################################################################################
608 def validate_changes_file_arg(filename, require_changes=1):
609 """'filename' is either a .changes or .dak file. If 'filename' is a
610 .dak file, it's changed to be the corresponding .changes file. The
611 function then checks if the .changes file a) exists and b) is
612 readable and returns the .changes filename if so. If there's a
613 problem, the next action depends on the option 'require_changes'
616 o If 'require_changes' == -1, errors are ignored and the .changes
617 filename is returned.
618 o If 'require_changes' == 0, a warning is given and 'None' is returned.
619 o If 'require_changes' == 1, a fatal error is raised.
623 orig_filename = filename
624 if filename.endswith(".dak"):
625 filename = filename[:-4]+".changes"
627 if not filename.endswith(".changes"):
628 error = "invalid file type; not a changes file"
630 if not os.access(filename,os.R_OK):
631 if os.path.exists(filename):
632 error = "permission denied"
634 error = "file not found"
637 if require_changes == 1:
638 fubar("%s: %s." % (orig_filename, error))
639 elif require_changes == 0:
640 warn("Skipping %s - %s" % (orig_filename, error))
642 else: # We only care about the .dak file
647 ################################################################################
650 return (arch != "source" and arch != "all")
652 ################################################################################
654 def join_with_commas_and(list):
655 if len(list) == 0: return "nothing"
656 if len(list) == 1: return list[0]
657 return ", ".join(list[:-1]) + " and " + list[-1]
659 ################################################################################
664 (pkg, version, constraint) = atom
666 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
669 pp_deps.append(pp_dep)
670 return " |".join(pp_deps)
672 ################################################################################
677 ################################################################################
679 # Handle -a, -c and -s arguments; returns them as SQL constraints
680 def parse_args(Options):
684 for suite in split_args(Options["Suite"]):
685 suite_id = database.get_suite_id(suite)
687 warn("suite '%s' not recognised." % (suite))
689 suite_ids_list.append(suite_id)
691 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
693 fubar("No valid suite given.")
698 if Options["Component"]:
699 component_ids_list = []
700 for component in split_args(Options["Component"]):
701 component_id = database.get_component_id(component)
702 if component_id == -1:
703 warn("component '%s' not recognised." % (component))
705 component_ids_list.append(component_id)
706 if component_ids_list:
707 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
709 fubar("No valid component given.")
713 # Process architecture
714 con_architectures = ""
715 if Options["Architecture"]:
718 for architecture in split_args(Options["Architecture"]):
719 if architecture == "source":
722 architecture_id = database.get_architecture_id(architecture)
723 if architecture_id == -1:
724 warn("architecture '%s' not recognised." % (architecture))
726 arch_ids_list.append(architecture_id)
728 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
731 fubar("No valid architecture given.")
735 return (con_suites, con_architectures, con_components, check_source)
737 ################################################################################
739 # Inspired(tm) by Bryn Keller's print_exc_plus (See
740 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
743 tb = sys.exc_info()[2]
752 traceback.print_exc()
754 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
755 frame.f_code.co_filename,
757 for key, value in frame.f_locals.items():
758 print "\t%20s = " % key,
762 print "<unable to print>"
764 ################################################################################
766 def try_with_debug(function):
774 ################################################################################
776 # Function for use in sorting lists of architectures.
777 # Sorts normally except that 'source' dominates all others.
779 def arch_compare_sw (a, b):
780 if a == "source" and b == "source":
789 ################################################################################
791 # Split command line arguments which can be separated by either commas
792 # or whitespace. If dwim is set, it will complain about string ending
793 # in comma since this usually means someone did 'dak ls -a i386, m68k
794 # foo' or something and the inevitable confusion resulting from 'm68k'
795 # being treated as an argument is undesirable.
797 def split_args (s, dwim=1):
798 if s.find(",") == -1:
801 if s[-1:] == "," and dwim:
802 fubar("split_args: found trailing comma, spurious space maybe?")
805 ################################################################################
807 def Dict(**dict): return dict
809 ########################################
811 # Our very own version of commands.getouputstatus(), hacked to support
813 def gpgv_get_status_output(cmd, status_read, status_write):
814 cmd = ['/bin/sh', '-c', cmd]
815 p2cread, p2cwrite = os.pipe()
816 c2pread, c2pwrite = os.pipe()
817 errout, errin = os.pipe()
827 for i in range(3, 256):
828 if i != status_write:
834 os.execvp(cmd[0], cmd)
840 os.dup2(c2pread, c2pwrite)
841 os.dup2(errout, errin)
845 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
848 r = os.read(fd, 8196)
851 if fd == c2pwrite or fd == errin:
853 elif fd == status_read:
856 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
858 pid, exit_status = os.waitpid(pid, 0)
860 os.close(status_write)
861 os.close(status_read)
871 return output, status, exit_status
873 ################################################################################
875 def process_gpgv_output(status):
876 # Process the status-fd output
879 for line in status.split('\n'):
885 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
887 (gnupg, keyword) = split[:2]
888 if gnupg != "[GNUPG:]":
889 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
892 if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
893 internal_error += "found duplicate status token ('%s').\n" % (keyword)
896 keywords[keyword] = args
898 return (keywords, internal_error)
900 ################################################################################
902 def retrieve_key (filename, keyserver=None, keyring=None):
903 """Retrieve the key that signed 'filename' from 'keyserver' and
904 add it to 'keyring'. Returns nothing on success, or an error message
907 # Defaults for keyserver and keyring
909 keyserver = Cnf["Dinstall::KeyServer"]
911 keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
913 # Ensure the filename contains no shell meta-characters or other badness
914 if not re_taint_free.match(filename):
915 return "%s: tainted filename" % (filename)
917 # Invoke gpgv on the file
918 status_read, status_write = os.pipe();
919 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
920 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
922 # Process the status-fd output
923 (keywords, internal_error) = process_gpgv_output(status)
925 return internal_error
927 if not keywords.has_key("NO_PUBKEY"):
928 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
930 fingerprint = keywords["NO_PUBKEY"][0]
931 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
932 # it'll try to create a lockfile in /dev. A better solution might
933 # be a tempfile or something.
934 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
935 % (Cnf["Dinstall::SigningKeyring"])
936 cmd += " --keyring %s --keyserver %s --recv-key %s" \
937 % (keyring, keyserver, fingerprint)
938 (result, output) = commands.getstatusoutput(cmd)
940 return "'%s' failed with exit code %s" % (cmd, result)
944 ################################################################################
946 def gpg_keyring_args(keyrings=None):
948 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
950 return " ".join(["--keyring %s" % x for x in keyrings])
952 ################################################################################
954 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
955 """Check the signature of a file and return the fingerprint if the
956 signature is valid or 'None' if it's not. The first argument is the
957 filename whose signature should be checked. The second argument is a
958 reject function and is called when an error is found. The reject()
959 function must allow for two arguments: the first is the error message,
960 the second is an optional prefix string. It's possible for reject()
961 to be called more than once during an invocation of check_signature().
962 The third argument is optional and is the name of the files the
963 detached signature applies to. The fourth argument is optional and is
964 a *list* of keyrings to use. 'autofetch' can either be None, True or
965 False. If None, the default behaviour specified in the config will be
968 # Ensure the filename contains no shell meta-characters or other badness
969 if not re_taint_free.match(sig_filename):
970 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
973 if data_filename and not re_taint_free.match(data_filename):
974 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
978 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
980 # Autofetch the signing key if that's enabled
981 if autofetch == None:
982 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
984 error_msg = retrieve_key(sig_filename)
989 # Build the command line
990 status_read, status_write = os.pipe();
991 cmd = "gpgv --status-fd %s %s %s %s" % (
992 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
994 # Invoke gpgv on the file
995 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
997 # Process the status-fd output
998 (keywords, internal_error) = process_gpgv_output(status)
1000 # If we failed to parse the status-fd output, let's just whine and bail now
1002 reject("internal error while performing signature check on %s." % (sig_filename))
1003 reject(internal_error, "")
1004 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1008 # Now check for obviously bad things in the processed output
1009 if keywords.has_key("KEYREVOKED"):
1010 reject("The key used to sign %s has been revoked." % (sig_filename))
1012 if keywords.has_key("BADSIG"):
1013 reject("bad signature on %s." % (sig_filename))
1015 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1016 reject("failed to check signature on %s." % (sig_filename))
1018 if keywords.has_key("NO_PUBKEY"):
1019 args = keywords["NO_PUBKEY"]
1022 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1024 if keywords.has_key("BADARMOR"):
1025 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1027 if keywords.has_key("NODATA"):
1028 reject("no signature found in %s." % (sig_filename))
1030 if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1031 args = keywords["KEYEXPIRED"]
1034 reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
1040 # Next check gpgv exited with a zero return code
1042 reject("gpgv failed while checking %s." % (sig_filename))
1044 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1046 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1049 # Sanity check the good stuff we expect
1050 if not keywords.has_key("VALIDSIG"):
1051 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1054 args = keywords["VALIDSIG"]
1056 reject("internal error while checking signature on %s." % (sig_filename))
1059 fingerprint = args[0]
1060 if not keywords.has_key("GOODSIG"):
1061 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1063 if not keywords.has_key("SIG_ID"):
1064 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1067 # Finally ensure there's not something we don't recognise
1068 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1069 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1070 NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1072 for keyword in keywords.keys():
1073 if not known_keywords.has_key(keyword):
1074 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1082 ################################################################################
1084 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1086 def wrap(paragraph, max_length, prefix=""):
1090 words = paragraph.split()
1093 word_size = len(word)
1094 if word_size > max_length:
1096 s += line + '\n' + prefix
1097 s += word + '\n' + prefix
1100 new_length = len(line) + word_size + 1
1101 if new_length > max_length:
1102 s += line + '\n' + prefix
1115 ################################################################################
1117 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1118 # Returns fixed 'src'
1119 def clean_symlink (src, dest, root):
1120 src = src.replace(root, '', 1)
1121 dest = dest.replace(root, '', 1)
1122 dest = os.path.dirname(dest)
1123 new_src = '../' * len(dest.split('/'))
1124 return new_src + src
1126 ################################################################################
1128 def temp_filename(directory=None, dotprefix=None, perms=0700):
1129 """Return a secure and unique filename by pre-creating it.
1130 If 'directory' is non-null, it will be the directory the file is pre-created in.
1131 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1134 old_tempdir = tempfile.tempdir
1135 tempfile.tempdir = directory
1137 filename = tempfile.mktemp()
1140 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1141 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1145 tempfile.tempdir = old_tempdir
1149 ################################################################################
1153 Cnf = apt_pkg.newConfiguration()
1154 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1156 if which_conf_file() != default_config:
1157 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1159 ################################################################################