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"
64 ################################################################################
66 class Error(Exception):
67 """Base class for exceptions in this module."""
70 class ParseMaintError(Error):
71 """Exception raised for errors in parsing a maintainer field.
74 message -- explanation of the error
77 def __init__(self, message):
79 self.message = message
81 ################################################################################
83 def open_file(filename, mode='r'):
85 f = open(filename, mode)
87 raise cant_open_exc, filename
90 ################################################################################
92 def our_raw_input(prompt=""):
94 sys.stdout.write(prompt)
100 sys.stderr.write("\nUser interrupt (^D).\n")
103 ################################################################################
105 def extract_component_from_section(section):
108 if section.find('/') != -1:
109 component = section.split('/')[0]
111 # Expand default component
113 if Cnf.has_key("Component::%s" % section):
118 return (section, component)
120 ################################################################################
122 def parse_changes(filename, signing_rules=0):
123 """Parses a changes file and returns a dictionary where each field is a
124 key. The mandatory first argument is the filename of the .changes
127 signing_rules is an optional argument:
129 o If signing_rules == -1, no signature is required.
130 o If signing_rules == 0 (the default), a signature is required.
131 o If signing_rules == 1, it turns on the same strict format checking
134 The rules for (signing_rules == 1)-mode are:
136 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
137 followed by any PGP header data and must end with a blank line.
139 o The data section must end with a blank line and must be followed by
140 "-----BEGIN PGP SIGNATURE-----".
146 changes_in = open_file(filename)
147 lines = changes_in.readlines()
150 raise changes_parse_error_exc, "[Empty changes file]"
152 # Reindex by line number so we can easily verify the format of
158 indexed_lines[index] = line[:-1]
162 num_of_lines = len(indexed_lines.keys())
165 while index < num_of_lines:
167 line = indexed_lines[index]
169 if signing_rules == 1:
171 if index > num_of_lines:
172 raise invalid_dsc_format_exc, index
173 line = indexed_lines[index]
174 if not line.startswith("-----BEGIN PGP SIGNATURE"):
175 raise invalid_dsc_format_exc, index
180 if line.startswith("-----BEGIN PGP SIGNATURE"):
182 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
184 if signing_rules == 1:
185 while index < num_of_lines and line != "":
187 line = indexed_lines[index]
189 # If we're not inside the signed data, don't process anything
190 if signing_rules >= 0 and not inside_signature:
192 slf = re_single_line_field.match(line)
194 field = slf.groups()[0].lower()
195 changes[field] = slf.groups()[1]
199 changes[field] += '\n'
201 mlf = re_multi_line_field.match(line)
204 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
205 if first == 1 and changes[field] != "":
206 changes[field] += '\n'
208 changes[field] += mlf.groups()[0] + '\n'
212 if signing_rules == 1 and inside_signature:
213 raise invalid_dsc_format_exc, index
216 changes["filecontents"] = "".join(lines)
218 if changes.has_key("source"):
219 # Strip the source version in brackets from the source field,
220 # put it in the "source-version" field instead.
221 srcver = re_srchasver.search(changes["source"])
223 changes["source"] = srcver.group(1)
224 changes["source-version"] = srcver.group(2)
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, field="files", hashname="md5sum"):
238 # Make sure we have a Files: field to parse...
239 if not changes.has_key(field):
242 # Make sure we recognise the format of the Files: field
243 format = re_verwithext.search(changes.get("format", "0.0"))
245 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
247 format = format.groups()
248 if format[1] == None:
249 format = int(float(format[0])), 0, format[2]
251 format = int(format[0]), int(format[1]), format[2]
252 if format[2] == None:
257 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
259 if (format < (1,5) or format > (1,8)):
260 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
261 if field != "files" and format < (1,8):
262 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
264 includes_section = (not is_a_dsc) and field == "files"
266 # Parse each entry/line:
267 for i in changes[field].split('\n'):
271 section = priority = ""
274 (md5, size, section, priority, name) = s
276 (md5, size, name) = s
278 raise changes_parse_error_exc, i
285 (section, component) = extract_component_from_section(section)
287 files[name] = Dict(size=size, section=section,
288 priority=priority, component=component)
289 files[name][hashname] = md5
293 ################################################################################
295 def force_to_utf8(s):
296 """Forces a string to UTF-8. If the string isn't already UTF-8,
297 it's assumed to be ISO-8859-1."""
302 latin1_s = unicode(s,'iso8859-1')
303 return latin1_s.encode('utf-8')
305 def rfc2047_encode(s):
306 """Encodes a (header) string per RFC2047 if necessary. If the
307 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
309 codecs.lookup('ascii')[1](s)
314 codecs.lookup('utf-8')[1](s)
315 h = email.Header.Header(s, 'utf-8', 998)
318 h = email.Header.Header(s, 'iso-8859-1', 998)
321 ################################################################################
323 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
324 # with it. I know - I'll fix the suckage and make things
327 def fix_maintainer (maintainer):
328 """Parses a Maintainer or Changed-By field and returns:
329 (1) an RFC822 compatible version,
330 (2) an RFC2047 compatible version,
334 The name is forced to UTF-8 for both (1) and (3). If the name field
335 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
336 switched to 'email (name)' format."""
337 maintainer = maintainer.strip()
339 return ('', '', '', '')
341 if maintainer.find("<") == -1:
344 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
345 email = maintainer[1:-1]
348 m = re_parse_maintainer.match(maintainer)
350 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
354 # Get an RFC2047 compliant version of the name
355 rfc2047_name = rfc2047_encode(name)
357 # Force the name to be UTF-8
358 name = force_to_utf8(name)
360 if name.find(',') != -1 or name.find('.') != -1:
361 rfc822_maint = "%s (%s)" % (email, name)
362 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
364 rfc822_maint = "%s <%s>" % (name, email)
365 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
367 if email.find("@") == -1 and email.find("buildd_") != 0:
368 raise ParseMaintError, "No @ found in email address part."
370 return (rfc822_maint, rfc2047_maint, name, email)
372 ################################################################################
374 # sendmail wrapper, takes _either_ a message string or a file as arguments
375 def send_mail (message, filename=""):
376 # If we've been passed a string dump it into a temporary file
378 filename = tempfile.mktemp()
379 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
380 os.write (fd, message)
384 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
386 raise sendmail_failed_exc, output
388 # Clean up any temporary files
392 ################################################################################
394 def poolify (source, component):
397 if source[:3] == "lib":
398 return component + source[:4] + '/' + source + '/'
400 return component + source[:1] + '/' + source + '/'
402 ################################################################################
404 def move (src, dest, overwrite = 0, perms = 0664):
405 if os.path.exists(dest) and os.path.isdir(dest):
408 dest_dir = os.path.dirname(dest)
409 if not os.path.exists(dest_dir):
410 umask = os.umask(00000)
411 os.makedirs(dest_dir, 02775)
413 #print "Moving %s to %s..." % (src, dest)
414 if os.path.exists(dest) and os.path.isdir(dest):
415 dest += '/' + os.path.basename(src)
416 # Don't overwrite unless forced to
417 if os.path.exists(dest):
419 fubar("Can't move %s to %s - file already exists." % (src, dest))
421 if not os.access(dest, os.W_OK):
422 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
423 shutil.copy2(src, dest)
424 os.chmod(dest, perms)
427 def copy (src, dest, overwrite = 0, perms = 0664):
428 if os.path.exists(dest) and os.path.isdir(dest):
431 dest_dir = os.path.dirname(dest)
432 if not os.path.exists(dest_dir):
433 umask = os.umask(00000)
434 os.makedirs(dest_dir, 02775)
436 #print "Copying %s to %s..." % (src, dest)
437 if os.path.exists(dest) and os.path.isdir(dest):
438 dest += '/' + os.path.basename(src)
439 # Don't overwrite unless forced to
440 if os.path.exists(dest):
442 raise file_exists_exc
444 if not os.access(dest, os.W_OK):
445 raise cant_overwrite_exc
446 shutil.copy2(src, dest)
447 os.chmod(dest, perms)
449 ################################################################################
452 res = socket.gethostbyaddr(socket.gethostname())
453 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
454 if database_hostname:
455 return database_hostname
459 def which_conf_file ():
460 res = socket.gethostbyaddr(socket.gethostname())
461 if Cnf.get("Config::" + res[0] + "::DakConfig"):
462 return Cnf["Config::" + res[0] + "::DakConfig"]
464 return default_config
466 def which_apt_conf_file ():
467 res = socket.gethostbyaddr(socket.gethostname())
468 if Cnf.get("Config::" + res[0] + "::AptConfig"):
469 return Cnf["Config::" + res[0] + "::AptConfig"]
471 return default_apt_config
473 def which_alias_file():
474 hostname = socket.gethostbyaddr(socket.gethostname())[0]
475 aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
476 if os.path.exists(aliasfn):
481 ################################################################################
483 # Escape characters which have meaning to SQL's regex comparison operator ('~')
484 # (woefully incomplete)
487 s = s.replace('+', '\\\\+')
488 s = s.replace('.', '\\\\.')
491 ################################################################################
493 # Perform a substition of template
494 def TemplateSubst(map, filename):
495 file = open_file(filename)
496 template = file.read()
498 template = template.replace(x,map[x])
502 ################################################################################
504 def fubar(msg, exit_code=1):
505 sys.stderr.write("E: %s\n" % (msg))
509 sys.stderr.write("W: %s\n" % (msg))
511 ################################################################################
513 # Returns the user name with a laughable attempt at rfc822 conformancy
514 # (read: removing stray periods).
516 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
518 ################################################################################
528 return ("%d%s" % (c, t))
530 ################################################################################
532 def cc_fix_changes (changes):
533 o = changes.get("architecture", "")
535 del changes["architecture"]
536 changes["architecture"] = {}
538 changes["architecture"][j] = 1
540 # Sort by source name, source version, 'have source', and then by filename
541 def changes_compare (a, b):
543 a_changes = parse_changes(a)
548 b_changes = parse_changes(b)
552 cc_fix_changes (a_changes)
553 cc_fix_changes (b_changes)
555 # Sort by source name
556 a_source = a_changes.get("source")
557 b_source = b_changes.get("source")
558 q = cmp (a_source, b_source)
562 # Sort by source version
563 a_version = a_changes.get("version", "0")
564 b_version = b_changes.get("version", "0")
565 q = apt_pkg.VersionCompare(a_version, b_version)
569 # Sort by 'have source'
570 a_has_source = a_changes["architecture"].get("source")
571 b_has_source = b_changes["architecture"].get("source")
572 if a_has_source and not b_has_source:
574 elif b_has_source and not a_has_source:
577 # Fall back to sort by filename
580 ################################################################################
582 def find_next_free (dest, too_many=100):
585 while os.path.exists(dest) and extra < too_many:
586 dest = orig_dest + '.' + repr(extra)
588 if extra >= too_many:
589 raise tried_too_hard_exc
592 ################################################################################
594 def result_join (original, sep = '\t'):
596 for i in xrange(len(original)):
597 if original[i] == None:
600 list.append(original[i])
601 return sep.join(list)
603 ################################################################################
605 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
607 for line in str.split('\n'):
609 if line or include_blank_lines:
610 out += "%s%s\n" % (prefix, line)
611 # Strip trailing new line
616 ################################################################################
618 def validate_changes_file_arg(filename, require_changes=1):
619 """'filename' is either a .changes or .dak file. If 'filename' is a
620 .dak file, it's changed to be the corresponding .changes file. The
621 function then checks if the .changes file a) exists and b) is
622 readable and returns the .changes filename if so. If there's a
623 problem, the next action depends on the option 'require_changes'
626 o If 'require_changes' == -1, errors are ignored and the .changes
627 filename is returned.
628 o If 'require_changes' == 0, a warning is given and 'None' is returned.
629 o If 'require_changes' == 1, a fatal error is raised.
633 orig_filename = filename
634 if filename.endswith(".dak"):
635 filename = filename[:-4]+".changes"
637 if not filename.endswith(".changes"):
638 error = "invalid file type; not a changes file"
640 if not os.access(filename,os.R_OK):
641 if os.path.exists(filename):
642 error = "permission denied"
644 error = "file not found"
647 if require_changes == 1:
648 fubar("%s: %s." % (orig_filename, error))
649 elif require_changes == 0:
650 warn("Skipping %s - %s" % (orig_filename, error))
652 else: # We only care about the .dak file
657 ################################################################################
660 return (arch != "source" and arch != "all")
662 ################################################################################
664 def join_with_commas_and(list):
665 if len(list) == 0: return "nothing"
666 if len(list) == 1: return list[0]
667 return ", ".join(list[:-1]) + " and " + list[-1]
669 ################################################################################
674 (pkg, version, constraint) = atom
676 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
679 pp_deps.append(pp_dep)
680 return " |".join(pp_deps)
682 ################################################################################
687 ################################################################################
689 # Handle -a, -c and -s arguments; returns them as SQL constraints
690 def parse_args(Options):
694 for suite in split_args(Options["Suite"]):
695 suite_id = database.get_suite_id(suite)
697 warn("suite '%s' not recognised." % (suite))
699 suite_ids_list.append(suite_id)
701 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
703 fubar("No valid suite given.")
708 if Options["Component"]:
709 component_ids_list = []
710 for component in split_args(Options["Component"]):
711 component_id = database.get_component_id(component)
712 if component_id == -1:
713 warn("component '%s' not recognised." % (component))
715 component_ids_list.append(component_id)
716 if component_ids_list:
717 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
719 fubar("No valid component given.")
723 # Process architecture
724 con_architectures = ""
725 if Options["Architecture"]:
728 for architecture in split_args(Options["Architecture"]):
729 if architecture == "source":
732 architecture_id = database.get_architecture_id(architecture)
733 if architecture_id == -1:
734 warn("architecture '%s' not recognised." % (architecture))
736 arch_ids_list.append(architecture_id)
738 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
741 fubar("No valid architecture given.")
745 return (con_suites, con_architectures, con_components, check_source)
747 ################################################################################
749 # Inspired(tm) by Bryn Keller's print_exc_plus (See
750 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
753 tb = sys.exc_info()[2]
762 traceback.print_exc()
764 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
765 frame.f_code.co_filename,
767 for key, value in frame.f_locals.items():
768 print "\t%20s = " % key,
772 print "<unable to print>"
774 ################################################################################
776 def try_with_debug(function):
784 ################################################################################
786 # Function for use in sorting lists of architectures.
787 # Sorts normally except that 'source' dominates all others.
789 def arch_compare_sw (a, b):
790 if a == "source" and b == "source":
799 ################################################################################
801 # Split command line arguments which can be separated by either commas
802 # or whitespace. If dwim is set, it will complain about string ending
803 # in comma since this usually means someone did 'dak ls -a i386, m68k
804 # foo' or something and the inevitable confusion resulting from 'm68k'
805 # being treated as an argument is undesirable.
807 def split_args (s, dwim=1):
808 if s.find(",") == -1:
811 if s[-1:] == "," and dwim:
812 fubar("split_args: found trailing comma, spurious space maybe?")
815 ################################################################################
817 def Dict(**dict): return dict
819 ########################################
821 # Our very own version of commands.getouputstatus(), hacked to support
823 def gpgv_get_status_output(cmd, status_read, status_write):
824 cmd = ['/bin/sh', '-c', cmd]
825 p2cread, p2cwrite = os.pipe()
826 c2pread, c2pwrite = os.pipe()
827 errout, errin = os.pipe()
837 for i in range(3, 256):
838 if i != status_write:
844 os.execvp(cmd[0], cmd)
850 os.dup2(c2pread, c2pwrite)
851 os.dup2(errout, errin)
855 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
858 r = os.read(fd, 8196)
861 if fd == c2pwrite or fd == errin:
863 elif fd == status_read:
866 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
868 pid, exit_status = os.waitpid(pid, 0)
870 os.close(status_write)
871 os.close(status_read)
881 return output, status, exit_status
883 ################################################################################
885 def process_gpgv_output(status):
886 # Process the status-fd output
889 for line in status.split('\n'):
895 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
897 (gnupg, keyword) = split[:2]
898 if gnupg != "[GNUPG:]":
899 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
902 if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
903 internal_error += "found duplicate status token ('%s').\n" % (keyword)
906 keywords[keyword] = args
908 return (keywords, internal_error)
910 ################################################################################
912 def retrieve_key (filename, keyserver=None, keyring=None):
913 """Retrieve the key that signed 'filename' from 'keyserver' and
914 add it to 'keyring'. Returns nothing on success, or an error message
917 # Defaults for keyserver and keyring
919 keyserver = Cnf["Dinstall::KeyServer"]
921 keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
923 # Ensure the filename contains no shell meta-characters or other badness
924 if not re_taint_free.match(filename):
925 return "%s: tainted filename" % (filename)
927 # Invoke gpgv on the file
928 status_read, status_write = os.pipe();
929 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
930 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
932 # Process the status-fd output
933 (keywords, internal_error) = process_gpgv_output(status)
935 return internal_error
937 if not keywords.has_key("NO_PUBKEY"):
938 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
940 fingerprint = keywords["NO_PUBKEY"][0]
941 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
942 # it'll try to create a lockfile in /dev. A better solution might
943 # be a tempfile or something.
944 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
945 % (Cnf["Dinstall::SigningKeyring"])
946 cmd += " --keyring %s --keyserver %s --recv-key %s" \
947 % (keyring, keyserver, fingerprint)
948 (result, output) = commands.getstatusoutput(cmd)
950 return "'%s' failed with exit code %s" % (cmd, result)
954 ################################################################################
956 def gpg_keyring_args(keyrings=None):
958 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
960 return " ".join(["--keyring %s" % x for x in keyrings])
962 ################################################################################
964 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
965 """Check the signature of a file and return the fingerprint if the
966 signature is valid or 'None' if it's not. The first argument is the
967 filename whose signature should be checked. The second argument is a
968 reject function and is called when an error is found. The reject()
969 function must allow for two arguments: the first is the error message,
970 the second is an optional prefix string. It's possible for reject()
971 to be called more than once during an invocation of check_signature().
972 The third argument is optional and is the name of the files the
973 detached signature applies to. The fourth argument is optional and is
974 a *list* of keyrings to use. 'autofetch' can either be None, True or
975 False. If None, the default behaviour specified in the config will be
978 # Ensure the filename contains no shell meta-characters or other badness
979 if not re_taint_free.match(sig_filename):
980 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
983 if data_filename and not re_taint_free.match(data_filename):
984 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
988 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
990 # Autofetch the signing key if that's enabled
991 if autofetch == None:
992 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
994 error_msg = retrieve_key(sig_filename)
999 # Build the command line
1000 status_read, status_write = os.pipe();
1001 cmd = "gpgv --status-fd %s %s %s %s" % (
1002 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
1004 # Invoke gpgv on the file
1005 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1007 # Process the status-fd output
1008 (keywords, internal_error) = process_gpgv_output(status)
1010 # If we failed to parse the status-fd output, let's just whine and bail now
1012 reject("internal error while performing signature check on %s." % (sig_filename))
1013 reject(internal_error, "")
1014 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1018 # Now check for obviously bad things in the processed output
1019 if keywords.has_key("KEYREVOKED"):
1020 reject("The key used to sign %s has been revoked." % (sig_filename))
1022 if keywords.has_key("BADSIG"):
1023 reject("bad signature on %s." % (sig_filename))
1025 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1026 reject("failed to check signature on %s." % (sig_filename))
1028 if keywords.has_key("NO_PUBKEY"):
1029 args = keywords["NO_PUBKEY"]
1032 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1034 if keywords.has_key("BADARMOR"):
1035 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1037 if keywords.has_key("NODATA"):
1038 reject("no signature found in %s." % (sig_filename))
1040 if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1041 args = keywords["KEYEXPIRED"]
1044 reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
1050 # Next check gpgv exited with a zero return code
1052 reject("gpgv failed while checking %s." % (sig_filename))
1054 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1056 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1059 # Sanity check the good stuff we expect
1060 if not keywords.has_key("VALIDSIG"):
1061 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1064 args = keywords["VALIDSIG"]
1066 reject("internal error while checking signature on %s." % (sig_filename))
1069 fingerprint = args[0]
1070 if not keywords.has_key("GOODSIG"):
1071 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1073 if not keywords.has_key("SIG_ID"):
1074 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1077 # Finally ensure there's not something we don't recognise
1078 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1079 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1080 NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1082 for keyword in keywords.keys():
1083 if not known_keywords.has_key(keyword):
1084 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1092 ################################################################################
1094 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1096 def wrap(paragraph, max_length, prefix=""):
1100 words = paragraph.split()
1103 word_size = len(word)
1104 if word_size > max_length:
1106 s += line + '\n' + prefix
1107 s += word + '\n' + prefix
1110 new_length = len(line) + word_size + 1
1111 if new_length > max_length:
1112 s += line + '\n' + prefix
1125 ################################################################################
1127 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1128 # Returns fixed 'src'
1129 def clean_symlink (src, dest, root):
1130 src = src.replace(root, '', 1)
1131 dest = dest.replace(root, '', 1)
1132 dest = os.path.dirname(dest)
1133 new_src = '../' * len(dest.split('/'))
1134 return new_src + src
1136 ################################################################################
1138 def temp_filename(directory=None, dotprefix=None, perms=0700):
1139 """Return a secure and unique filename by pre-creating it.
1140 If 'directory' is non-null, it will be the directory the file is pre-created in.
1141 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1144 old_tempdir = tempfile.tempdir
1145 tempfile.tempdir = directory
1147 filename = tempfile.mktemp()
1150 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1151 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1155 tempfile.tempdir = old_tempdir
1159 ################################################################################
1161 # checks if the user part of the email is listed in the alias file
1163 def is_email_alias(email):
1165 if alias_cache == None:
1166 aliasfn = which_alias_file()
1169 for l in open(aliasfn):
1170 alias_cache.add(l.split(':')[0])
1171 uid = email.split('@')[0]
1172 return uid in alias_cache
1174 ################################################################################
1178 Cnf = apt_pkg.newConfiguration()
1179 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1181 if which_conf_file() != default_config:
1182 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1184 ################################################################################