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
28 from dak_exceptions import *
30 ################################################################################
32 re_comments = re.compile(r"\#.*")
33 re_no_epoch = re.compile(r"^\d+\:")
34 re_no_revision = re.compile(r"-[^-]+$")
35 re_arch_from_filename = re.compile(r"/binary-[^/]+/")
36 re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
37 re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$")
38 re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$")
40 re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)")
41 re_multi_line_field = re.compile(r"^\s(.*)")
42 re_taint_free = re.compile(r"^[-+~/\.\w]+$")
44 re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>")
45 re_gpg_uid = re.compile('^uid.*<([^>]*)>')
47 re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
48 re_verwithext = re.compile(r"^(\d+)(?:\.(\d+))(?:\s+\((\S+)\))?$")
50 re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
52 changes_parse_error_exc = "Can't parse line in .changes file"
53 invalid_dsc_format_exc = "Invalid .dsc file"
54 nk_format_exc = "Unknown Format: in .changes file"
55 no_files_exc = "No Files: field in .dsc or .changes file."
56 cant_open_exc = "Can't open file"
57 unknown_hostname_exc = "Unknown hostname"
58 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
59 file_exists_exc = "Destination file exists"
60 sendmail_failed_exc = "Sendmail invocation failed"
61 tried_too_hard_exc = "Tried too hard to find a free filename."
63 default_config = "/etc/dak/dak.conf"
64 default_apt_config = "/etc/dak/apt.conf"
67 key_uid_email_cache = {}
69 ################################################################################
71 def open_file(filename, mode='r'):
73 f = open(filename, mode)
75 raise cant_open_exc, filename
78 ################################################################################
80 def our_raw_input(prompt=""):
82 sys.stdout.write(prompt)
88 sys.stderr.write("\nUser interrupt (^D).\n")
91 ################################################################################
93 def extract_component_from_section(section):
96 if section.find('/') != -1:
97 component = section.split('/')[0]
99 # Expand default component
101 if Cnf.has_key("Component::%s" % section):
106 return (section, component)
108 ################################################################################
110 def parse_changes(filename, signing_rules=0):
111 """Parses a changes file and returns a dictionary where each field is a
112 key. The mandatory first argument is the filename of the .changes
115 signing_rules is an optional argument:
117 o If signing_rules == -1, no signature is required.
118 o If signing_rules == 0 (the default), a signature is required.
119 o If signing_rules == 1, it turns on the same strict format checking
122 The rules for (signing_rules == 1)-mode are:
124 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
125 followed by any PGP header data and must end with a blank line.
127 o The data section must end with a blank line and must be followed by
128 "-----BEGIN PGP SIGNATURE-----".
134 changes_in = open_file(filename)
135 lines = changes_in.readlines()
138 raise changes_parse_error_exc, "[Empty changes file]"
140 # Reindex by line number so we can easily verify the format of
146 indexed_lines[index] = line[:-1]
150 num_of_lines = len(indexed_lines.keys())
153 while index < num_of_lines:
155 line = indexed_lines[index]
157 if signing_rules == 1:
159 if index > num_of_lines:
160 raise invalid_dsc_format_exc, index
161 line = indexed_lines[index]
162 if not line.startswith("-----BEGIN PGP SIGNATURE"):
163 raise invalid_dsc_format_exc, index
168 if line.startswith("-----BEGIN PGP SIGNATURE"):
170 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
172 if signing_rules == 1:
173 while index < num_of_lines and line != "":
175 line = indexed_lines[index]
177 # If we're not inside the signed data, don't process anything
178 if signing_rules >= 0 and not inside_signature:
180 slf = re_single_line_field.match(line)
182 field = slf.groups()[0].lower()
183 changes[field] = slf.groups()[1]
187 changes[field] += '\n'
189 mlf = re_multi_line_field.match(line)
192 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
193 if first == 1 and changes[field] != "":
194 changes[field] += '\n'
196 changes[field] += mlf.groups()[0] + '\n'
200 if signing_rules == 1 and inside_signature:
201 raise invalid_dsc_format_exc, index
204 changes["filecontents"] = "".join(lines)
206 if changes.has_key("source"):
207 # Strip the source version in brackets from the source field,
208 # put it in the "source-version" field instead.
209 srcver = re_srchasver.search(changes["source"])
211 changes["source"] = srcver.group(1)
212 changes["source-version"] = srcver.group(2)
215 raise changes_parse_error_exc, error
219 ################################################################################
221 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
223 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
226 # Make sure we have a Files: field to parse...
227 if not changes.has_key(field):
230 # Make sure we recognise the format of the Files: field
231 format = re_verwithext.search(changes.get("format", "0.0"))
233 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
235 format = format.groups()
236 if format[1] == None:
237 format = int(float(format[0])), 0, format[2]
239 format = int(format[0]), int(format[1]), format[2]
240 if format[2] == None:
245 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
247 if (format < (1,5) or format > (1,8)):
248 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
249 if field != "files" and format < (1,8):
250 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
252 includes_section = (not is_a_dsc) and field == "files"
254 # Parse each entry/line:
255 for i in changes[field].split('\n'):
259 section = priority = ""
262 (md5, size, section, priority, name) = s
264 (md5, size, name) = s
266 raise changes_parse_error_exc, i
273 (section, component) = extract_component_from_section(section)
275 files[name] = Dict(size=size, section=section,
276 priority=priority, component=component)
277 files[name][hashname] = md5
281 ################################################################################
283 def force_to_utf8(s):
284 """Forces a string to UTF-8. If the string isn't already UTF-8,
285 it's assumed to be ISO-8859-1."""
290 latin1_s = unicode(s,'iso8859-1')
291 return latin1_s.encode('utf-8')
293 def rfc2047_encode(s):
294 """Encodes a (header) string per RFC2047 if necessary. If the
295 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
297 codecs.lookup('ascii')[1](s)
302 codecs.lookup('utf-8')[1](s)
303 h = email.Header.Header(s, 'utf-8', 998)
306 h = email.Header.Header(s, 'iso-8859-1', 998)
309 ################################################################################
311 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
312 # with it. I know - I'll fix the suckage and make things
315 def fix_maintainer (maintainer):
316 """Parses a Maintainer or Changed-By field and returns:
317 (1) an RFC822 compatible version,
318 (2) an RFC2047 compatible version,
322 The name is forced to UTF-8 for both (1) and (3). If the name field
323 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
324 switched to 'email (name)' format."""
325 maintainer = maintainer.strip()
327 return ('', '', '', '')
329 if maintainer.find("<") == -1:
332 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
333 email = maintainer[1:-1]
336 m = re_parse_maintainer.match(maintainer)
338 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
342 # Get an RFC2047 compliant version of the name
343 rfc2047_name = rfc2047_encode(name)
345 # Force the name to be UTF-8
346 name = force_to_utf8(name)
348 if name.find(',') != -1 or name.find('.') != -1:
349 rfc822_maint = "%s (%s)" % (email, name)
350 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
352 rfc822_maint = "%s <%s>" % (name, email)
353 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
355 if email.find("@") == -1 and email.find("buildd_") != 0:
356 raise ParseMaintError, "No @ found in email address part."
358 return (rfc822_maint, rfc2047_maint, name, email)
360 ################################################################################
362 # sendmail wrapper, takes _either_ a message string or a file as arguments
363 def send_mail (message, filename=""):
364 # If we've been passed a string dump it into a temporary file
366 filename = tempfile.mktemp()
367 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
368 os.write (fd, message)
372 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
374 raise sendmail_failed_exc, output
376 # Clean up any temporary files
380 ################################################################################
382 def poolify (source, component):
385 if source[:3] == "lib":
386 return component + source[:4] + '/' + source + '/'
388 return component + source[:1] + '/' + source + '/'
390 ################################################################################
392 def move (src, dest, overwrite = 0, perms = 0664):
393 if os.path.exists(dest) and os.path.isdir(dest):
396 dest_dir = os.path.dirname(dest)
397 if not os.path.exists(dest_dir):
398 umask = os.umask(00000)
399 os.makedirs(dest_dir, 02775)
401 #print "Moving %s to %s..." % (src, dest)
402 if os.path.exists(dest) and os.path.isdir(dest):
403 dest += '/' + os.path.basename(src)
404 # Don't overwrite unless forced to
405 if os.path.exists(dest):
407 fubar("Can't move %s to %s - file already exists." % (src, dest))
409 if not os.access(dest, os.W_OK):
410 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
411 shutil.copy2(src, dest)
412 os.chmod(dest, perms)
415 def copy (src, dest, overwrite = 0, perms = 0664):
416 if os.path.exists(dest) and os.path.isdir(dest):
419 dest_dir = os.path.dirname(dest)
420 if not os.path.exists(dest_dir):
421 umask = os.umask(00000)
422 os.makedirs(dest_dir, 02775)
424 #print "Copying %s to %s..." % (src, dest)
425 if os.path.exists(dest) and os.path.isdir(dest):
426 dest += '/' + os.path.basename(src)
427 # Don't overwrite unless forced to
428 if os.path.exists(dest):
430 raise file_exists_exc
432 if not os.access(dest, os.W_OK):
433 raise cant_overwrite_exc
434 shutil.copy2(src, dest)
435 os.chmod(dest, perms)
437 ################################################################################
440 res = socket.gethostbyaddr(socket.gethostname())
441 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
442 if database_hostname:
443 return database_hostname
447 def which_conf_file ():
448 res = socket.gethostbyaddr(socket.gethostname())
449 if Cnf.get("Config::" + res[0] + "::DakConfig"):
450 return Cnf["Config::" + res[0] + "::DakConfig"]
452 return default_config
454 def which_apt_conf_file ():
455 res = socket.gethostbyaddr(socket.gethostname())
456 if Cnf.get("Config::" + res[0] + "::AptConfig"):
457 return Cnf["Config::" + res[0] + "::AptConfig"]
459 return default_apt_config
461 def which_alias_file():
462 hostname = socket.gethostbyaddr(socket.gethostname())[0]
463 aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
464 if os.path.exists(aliasfn):
469 ################################################################################
471 # Escape characters which have meaning to SQL's regex comparison operator ('~')
472 # (woefully incomplete)
475 s = s.replace('+', '\\\\+')
476 s = s.replace('.', '\\\\.')
479 ################################################################################
481 # Perform a substition of template
482 def TemplateSubst(map, filename):
483 file = open_file(filename)
484 template = file.read()
486 template = template.replace(x,map[x])
490 ################################################################################
492 def fubar(msg, exit_code=1):
493 sys.stderr.write("E: %s\n" % (msg))
497 sys.stderr.write("W: %s\n" % (msg))
499 ################################################################################
501 # Returns the user name with a laughable attempt at rfc822 conformancy
502 # (read: removing stray periods).
504 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
506 ################################################################################
516 return ("%d%s" % (c, t))
518 ################################################################################
520 def cc_fix_changes (changes):
521 o = changes.get("architecture", "")
523 del changes["architecture"]
524 changes["architecture"] = {}
526 changes["architecture"][j] = 1
528 # Sort by source name, source version, 'have source', and then by filename
529 def changes_compare (a, b):
531 a_changes = parse_changes(a)
536 b_changes = parse_changes(b)
540 cc_fix_changes (a_changes)
541 cc_fix_changes (b_changes)
543 # Sort by source name
544 a_source = a_changes.get("source")
545 b_source = b_changes.get("source")
546 q = cmp (a_source, b_source)
550 # Sort by source version
551 a_version = a_changes.get("version", "0")
552 b_version = b_changes.get("version", "0")
553 q = apt_pkg.VersionCompare(a_version, b_version)
557 # Sort by 'have source'
558 a_has_source = a_changes["architecture"].get("source")
559 b_has_source = b_changes["architecture"].get("source")
560 if a_has_source and not b_has_source:
562 elif b_has_source and not a_has_source:
565 # Fall back to sort by filename
568 ################################################################################
570 def find_next_free (dest, too_many=100):
573 while os.path.exists(dest) and extra < too_many:
574 dest = orig_dest + '.' + repr(extra)
576 if extra >= too_many:
577 raise tried_too_hard_exc
580 ################################################################################
582 def result_join (original, sep = '\t'):
584 for i in xrange(len(original)):
585 if original[i] == None:
588 list.append(original[i])
589 return sep.join(list)
591 ################################################################################
593 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
595 for line in str.split('\n'):
597 if line or include_blank_lines:
598 out += "%s%s\n" % (prefix, line)
599 # Strip trailing new line
604 ################################################################################
606 def validate_changes_file_arg(filename, require_changes=1):
607 """'filename' is either a .changes or .dak file. If 'filename' is a
608 .dak file, it's changed to be the corresponding .changes file. The
609 function then checks if the .changes file a) exists and b) is
610 readable and returns the .changes filename if so. If there's a
611 problem, the next action depends on the option 'require_changes'
614 o If 'require_changes' == -1, errors are ignored and the .changes
615 filename is returned.
616 o If 'require_changes' == 0, a warning is given and 'None' is returned.
617 o If 'require_changes' == 1, a fatal error is raised.
621 orig_filename = filename
622 if filename.endswith(".dak"):
623 filename = filename[:-4]+".changes"
625 if not filename.endswith(".changes"):
626 error = "invalid file type; not a changes file"
628 if not os.access(filename,os.R_OK):
629 if os.path.exists(filename):
630 error = "permission denied"
632 error = "file not found"
635 if require_changes == 1:
636 fubar("%s: %s." % (orig_filename, error))
637 elif require_changes == 0:
638 warn("Skipping %s - %s" % (orig_filename, error))
640 else: # We only care about the .dak file
645 ################################################################################
648 return (arch != "source" and arch != "all")
650 ################################################################################
652 def join_with_commas_and(list):
653 if len(list) == 0: return "nothing"
654 if len(list) == 1: return list[0]
655 return ", ".join(list[:-1]) + " and " + list[-1]
657 ################################################################################
662 (pkg, version, constraint) = atom
664 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
667 pp_deps.append(pp_dep)
668 return " |".join(pp_deps)
670 ################################################################################
675 ################################################################################
677 # Handle -a, -c and -s arguments; returns them as SQL constraints
678 def parse_args(Options):
682 for suite in split_args(Options["Suite"]):
683 suite_id = database.get_suite_id(suite)
685 warn("suite '%s' not recognised." % (suite))
687 suite_ids_list.append(suite_id)
689 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
691 fubar("No valid suite given.")
696 if Options["Component"]:
697 component_ids_list = []
698 for component in split_args(Options["Component"]):
699 component_id = database.get_component_id(component)
700 if component_id == -1:
701 warn("component '%s' not recognised." % (component))
703 component_ids_list.append(component_id)
704 if component_ids_list:
705 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
707 fubar("No valid component given.")
711 # Process architecture
712 con_architectures = ""
713 if Options["Architecture"]:
716 for architecture in split_args(Options["Architecture"]):
717 if architecture == "source":
720 architecture_id = database.get_architecture_id(architecture)
721 if architecture_id == -1:
722 warn("architecture '%s' not recognised." % (architecture))
724 arch_ids_list.append(architecture_id)
726 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
729 fubar("No valid architecture given.")
733 return (con_suites, con_architectures, con_components, check_source)
735 ################################################################################
737 # Inspired(tm) by Bryn Keller's print_exc_plus (See
738 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
741 tb = sys.exc_info()[2]
750 traceback.print_exc()
752 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
753 frame.f_code.co_filename,
755 for key, value in frame.f_locals.items():
756 print "\t%20s = " % key,
760 print "<unable to print>"
762 ################################################################################
764 def try_with_debug(function):
772 ################################################################################
774 # Function for use in sorting lists of architectures.
775 # Sorts normally except that 'source' dominates all others.
777 def arch_compare_sw (a, b):
778 if a == "source" and b == "source":
787 ################################################################################
789 # Split command line arguments which can be separated by either commas
790 # or whitespace. If dwim is set, it will complain about string ending
791 # in comma since this usually means someone did 'dak ls -a i386, m68k
792 # foo' or something and the inevitable confusion resulting from 'm68k'
793 # being treated as an argument is undesirable.
795 def split_args (s, dwim=1):
796 if s.find(",") == -1:
799 if s[-1:] == "," and dwim:
800 fubar("split_args: found trailing comma, spurious space maybe?")
803 ################################################################################
805 def Dict(**dict): return dict
807 ########################################
809 # Our very own version of commands.getouputstatus(), hacked to support
811 def gpgv_get_status_output(cmd, status_read, status_write):
812 cmd = ['/bin/sh', '-c', cmd]
813 p2cread, p2cwrite = os.pipe()
814 c2pread, c2pwrite = os.pipe()
815 errout, errin = os.pipe()
825 for i in range(3, 256):
826 if i != status_write:
832 os.execvp(cmd[0], cmd)
838 os.dup2(c2pread, c2pwrite)
839 os.dup2(errout, errin)
843 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
846 r = os.read(fd, 8196)
849 if fd == c2pwrite or fd == errin:
851 elif fd == status_read:
854 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
856 pid, exit_status = os.waitpid(pid, 0)
858 os.close(status_write)
859 os.close(status_read)
869 return output, status, exit_status
871 ################################################################################
873 def process_gpgv_output(status):
874 # Process the status-fd output
877 for line in status.split('\n'):
883 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
885 (gnupg, keyword) = split[:2]
886 if gnupg != "[GNUPG:]":
887 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
890 if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
891 internal_error += "found duplicate status token ('%s').\n" % (keyword)
894 keywords[keyword] = args
896 return (keywords, internal_error)
898 ################################################################################
900 def retrieve_key (filename, keyserver=None, keyring=None):
901 """Retrieve the key that signed 'filename' from 'keyserver' and
902 add it to 'keyring'. Returns nothing on success, or an error message
905 # Defaults for keyserver and keyring
907 keyserver = Cnf["Dinstall::KeyServer"]
909 keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
911 # Ensure the filename contains no shell meta-characters or other badness
912 if not re_taint_free.match(filename):
913 return "%s: tainted filename" % (filename)
915 # Invoke gpgv on the file
916 status_read, status_write = os.pipe();
917 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
918 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
920 # Process the status-fd output
921 (keywords, internal_error) = process_gpgv_output(status)
923 return internal_error
925 if not keywords.has_key("NO_PUBKEY"):
926 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
928 fingerprint = keywords["NO_PUBKEY"][0]
929 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
930 # it'll try to create a lockfile in /dev. A better solution might
931 # be a tempfile or something.
932 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
933 % (Cnf["Dinstall::SigningKeyring"])
934 cmd += " --keyring %s --keyserver %s --recv-key %s" \
935 % (keyring, keyserver, fingerprint)
936 (result, output) = commands.getstatusoutput(cmd)
938 return "'%s' failed with exit code %s" % (cmd, result)
942 ################################################################################
944 def gpg_keyring_args(keyrings=None):
946 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
948 return " ".join(["--keyring %s" % x for x in keyrings])
950 ################################################################################
952 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
953 """Check the signature of a file and return the fingerprint if the
954 signature is valid or 'None' if it's not. The first argument is the
955 filename whose signature should be checked. The second argument is a
956 reject function and is called when an error is found. The reject()
957 function must allow for two arguments: the first is the error message,
958 the second is an optional prefix string. It's possible for reject()
959 to be called more than once during an invocation of check_signature().
960 The third argument is optional and is the name of the files the
961 detached signature applies to. The fourth argument is optional and is
962 a *list* of keyrings to use. 'autofetch' can either be None, True or
963 False. If None, the default behaviour specified in the config will be
966 # Ensure the filename contains no shell meta-characters or other badness
967 if not re_taint_free.match(sig_filename):
968 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
971 if data_filename and not re_taint_free.match(data_filename):
972 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
976 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
978 # Autofetch the signing key if that's enabled
979 if autofetch == None:
980 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
982 error_msg = retrieve_key(sig_filename)
987 # Build the command line
988 status_read, status_write = os.pipe();
989 cmd = "gpgv --status-fd %s %s %s %s" % (
990 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
992 # Invoke gpgv on the file
993 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
995 # Process the status-fd output
996 (keywords, internal_error) = process_gpgv_output(status)
998 # If we failed to parse the status-fd output, let's just whine and bail now
1000 reject("internal error while performing signature check on %s." % (sig_filename))
1001 reject(internal_error, "")
1002 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1006 # Now check for obviously bad things in the processed output
1007 if keywords.has_key("KEYREVOKED"):
1008 reject("The key used to sign %s has been revoked." % (sig_filename))
1010 if keywords.has_key("BADSIG"):
1011 reject("bad signature on %s." % (sig_filename))
1013 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1014 reject("failed to check signature on %s." % (sig_filename))
1016 if keywords.has_key("NO_PUBKEY"):
1017 args = keywords["NO_PUBKEY"]
1020 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1022 if keywords.has_key("BADARMOR"):
1023 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1025 if keywords.has_key("NODATA"):
1026 reject("no signature found in %s." % (sig_filename))
1028 if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1029 args = keywords["KEYEXPIRED"]
1032 reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
1038 # Next check gpgv exited with a zero return code
1040 reject("gpgv failed while checking %s." % (sig_filename))
1042 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1044 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1047 # Sanity check the good stuff we expect
1048 if not keywords.has_key("VALIDSIG"):
1049 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1052 args = keywords["VALIDSIG"]
1054 reject("internal error while checking signature on %s." % (sig_filename))
1057 fingerprint = args[0]
1058 if not keywords.has_key("GOODSIG"):
1059 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1061 if not keywords.has_key("SIG_ID"):
1062 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1065 # Finally ensure there's not something we don't recognise
1066 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1067 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1068 NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1070 for keyword in keywords.keys():
1071 if not known_keywords.has_key(keyword):
1072 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1080 ################################################################################
1082 def gpg_get_key_addresses(fingerprint):
1083 """retreive email addresses from gpg key uids for a given fingerprint"""
1084 addresses = key_uid_email_cache.get(fingerprint)
1085 if addresses != None:
1088 cmd = "gpg --no-default-keyring %s --fingerprint %s" \
1089 % (gpg_keyring_args(), fingerprint)
1090 (result, output) = commands.getstatusoutput(cmd)
1092 for l in output.split('\n'):
1093 m = re_gpg_uid.match(l)
1095 addresses.add(m.group(1))
1096 key_uid_email_cache[fingerprint] = addresses
1099 ################################################################################
1101 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1103 def wrap(paragraph, max_length, prefix=""):
1107 words = paragraph.split()
1110 word_size = len(word)
1111 if word_size > max_length:
1113 s += line + '\n' + prefix
1114 s += word + '\n' + prefix
1117 new_length = len(line) + word_size + 1
1118 if new_length > max_length:
1119 s += line + '\n' + prefix
1132 ################################################################################
1134 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1135 # Returns fixed 'src'
1136 def clean_symlink (src, dest, root):
1137 src = src.replace(root, '', 1)
1138 dest = dest.replace(root, '', 1)
1139 dest = os.path.dirname(dest)
1140 new_src = '../' * len(dest.split('/'))
1141 return new_src + src
1143 ################################################################################
1145 def temp_filename(directory=None, dotprefix=None, perms=0700):
1146 """Return a secure and unique filename by pre-creating it.
1147 If 'directory' is non-null, it will be the directory the file is pre-created in.
1148 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1151 old_tempdir = tempfile.tempdir
1152 tempfile.tempdir = directory
1154 filename = tempfile.mktemp()
1157 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1158 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1162 tempfile.tempdir = old_tempdir
1166 ################################################################################
1168 # checks if the user part of the email is listed in the alias file
1170 def is_email_alias(email):
1172 if alias_cache == None:
1173 aliasfn = which_alias_file()
1176 for l in open(aliasfn):
1177 alias_cache.add(l.split(':')[0])
1178 uid = email.split('@')[0]
1179 return uid in alias_cache
1181 ################################################################################
1185 Cnf = apt_pkg.newConfiguration()
1186 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1188 if which_conf_file() != default_config:
1189 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1191 ################################################################################