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*\<([^\>]+)\>")
44 re_gpg_uid = re.compile('^uid.*<([^>]*)>')
46 re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
47 re_verwithext = re.compile(r"^(\d+)(?:\.(\d+))(?:\s+\((\S+)\))?$")
49 re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
51 changes_parse_error_exc = "Can't parse line in .changes file"
52 invalid_dsc_format_exc = "Invalid .dsc file"
53 nk_format_exc = "Unknown Format: in .changes file"
54 no_files_exc = "No Files: field in .dsc or .changes file."
55 cant_open_exc = "Can't open file"
56 unknown_hostname_exc = "Unknown hostname"
57 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
58 file_exists_exc = "Destination file exists"
59 sendmail_failed_exc = "Sendmail invocation failed"
60 tried_too_hard_exc = "Tried too hard to find a free filename."
62 default_config = "/etc/dak/dak.conf"
63 default_apt_config = "/etc/dak/apt.conf"
66 key_uid_email_cache = {}
68 ################################################################################
70 class Error(Exception):
71 """Base class for exceptions in this module."""
74 class ParseMaintError(Error):
75 """Exception raised for errors in parsing a maintainer field.
78 message -- explanation of the error
81 def __init__(self, message):
83 self.message = message
85 ################################################################################
87 def open_file(filename, mode='r'):
89 f = open(filename, mode)
91 raise cant_open_exc, filename
94 ################################################################################
96 def our_raw_input(prompt=""):
98 sys.stdout.write(prompt)
104 sys.stderr.write("\nUser interrupt (^D).\n")
107 ################################################################################
109 def extract_component_from_section(section):
112 if section.find('/') != -1:
113 component = section.split('/')[0]
115 # Expand default component
117 if Cnf.has_key("Component::%s" % section):
122 return (section, component)
124 ################################################################################
126 def parse_changes(filename, signing_rules=0):
127 """Parses a changes file and returns a dictionary where each field is a
128 key. The mandatory first argument is the filename of the .changes
131 signing_rules is an optional argument:
133 o If signing_rules == -1, no signature is required.
134 o If signing_rules == 0 (the default), a signature is required.
135 o If signing_rules == 1, it turns on the same strict format checking
138 The rules for (signing_rules == 1)-mode are:
140 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
141 followed by any PGP header data and must end with a blank line.
143 o The data section must end with a blank line and must be followed by
144 "-----BEGIN PGP SIGNATURE-----".
150 changes_in = open_file(filename)
151 lines = changes_in.readlines()
154 raise changes_parse_error_exc, "[Empty changes file]"
156 # Reindex by line number so we can easily verify the format of
162 indexed_lines[index] = line[:-1]
166 num_of_lines = len(indexed_lines.keys())
169 while index < num_of_lines:
171 line = indexed_lines[index]
173 if signing_rules == 1:
175 if index > num_of_lines:
176 raise invalid_dsc_format_exc, index
177 line = indexed_lines[index]
178 if not line.startswith("-----BEGIN PGP SIGNATURE"):
179 raise invalid_dsc_format_exc, index
184 if line.startswith("-----BEGIN PGP SIGNATURE"):
186 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
188 if signing_rules == 1:
189 while index < num_of_lines and line != "":
191 line = indexed_lines[index]
193 # If we're not inside the signed data, don't process anything
194 if signing_rules >= 0 and not inside_signature:
196 slf = re_single_line_field.match(line)
198 field = slf.groups()[0].lower()
199 changes[field] = slf.groups()[1]
203 changes[field] += '\n'
205 mlf = re_multi_line_field.match(line)
208 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
209 if first == 1 and changes[field] != "":
210 changes[field] += '\n'
212 changes[field] += mlf.groups()[0] + '\n'
216 if signing_rules == 1 and inside_signature:
217 raise invalid_dsc_format_exc, index
220 changes["filecontents"] = "".join(lines)
222 if changes.has_key("source"):
223 # Strip the source version in brackets from the source field,
224 # put it in the "source-version" field instead.
225 srcver = re_srchasver.search(changes["source"])
227 changes["source"] = srcver.group(1)
228 changes["source-version"] = srcver.group(2)
231 raise changes_parse_error_exc, error
235 ################################################################################
237 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
239 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
242 # Make sure we have a Files: field to parse...
243 if not changes.has_key(field):
246 # Make sure we recognise the format of the Files: field
247 format = re_verwithext.search(changes.get("format", "0.0"))
249 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
251 format = format.groups()
252 if format[1] == None:
253 format = int(float(format[0])), 0, format[2]
255 format = int(format[0]), int(format[1]), format[2]
256 if format[2] == None:
261 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
263 if (format < (1,5) or format > (1,8)):
264 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
265 if field != "files" and format < (1,8):
266 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
268 includes_section = (not is_a_dsc) and field == "files"
270 # Parse each entry/line:
271 for i in changes[field].split('\n'):
275 section = priority = ""
278 (md5, size, section, priority, name) = s
280 (md5, size, name) = s
282 raise changes_parse_error_exc, i
289 (section, component) = extract_component_from_section(section)
291 files[name] = Dict(size=size, section=section,
292 priority=priority, component=component)
293 files[name][hashname] = md5
297 ################################################################################
299 def force_to_utf8(s):
300 """Forces a string to UTF-8. If the string isn't already UTF-8,
301 it's assumed to be ISO-8859-1."""
306 latin1_s = unicode(s,'iso8859-1')
307 return latin1_s.encode('utf-8')
309 def rfc2047_encode(s):
310 """Encodes a (header) string per RFC2047 if necessary. If the
311 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
313 codecs.lookup('ascii')[1](s)
318 codecs.lookup('utf-8')[1](s)
319 h = email.Header.Header(s, 'utf-8', 998)
322 h = email.Header.Header(s, 'iso-8859-1', 998)
325 ################################################################################
327 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
328 # with it. I know - I'll fix the suckage and make things
331 def fix_maintainer (maintainer):
332 """Parses a Maintainer or Changed-By field and returns:
333 (1) an RFC822 compatible version,
334 (2) an RFC2047 compatible version,
338 The name is forced to UTF-8 for both (1) and (3). If the name field
339 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
340 switched to 'email (name)' format."""
341 maintainer = maintainer.strip()
343 return ('', '', '', '')
345 if maintainer.find("<") == -1:
348 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
349 email = maintainer[1:-1]
352 m = re_parse_maintainer.match(maintainer)
354 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
358 # Get an RFC2047 compliant version of the name
359 rfc2047_name = rfc2047_encode(name)
361 # Force the name to be UTF-8
362 name = force_to_utf8(name)
364 if name.find(',') != -1 or name.find('.') != -1:
365 rfc822_maint = "%s (%s)" % (email, name)
366 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
368 rfc822_maint = "%s <%s>" % (name, email)
369 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
371 if email.find("@") == -1 and email.find("buildd_") != 0:
372 raise ParseMaintError, "No @ found in email address part."
374 return (rfc822_maint, rfc2047_maint, name, email)
376 ################################################################################
378 # sendmail wrapper, takes _either_ a message string or a file as arguments
379 def send_mail (message, filename=""):
380 # If we've been passed a string dump it into a temporary file
382 filename = tempfile.mktemp()
383 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
384 os.write (fd, message)
388 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
390 raise sendmail_failed_exc, output
392 # Clean up any temporary files
396 ################################################################################
398 def poolify (source, component):
401 if source[:3] == "lib":
402 return component + source[:4] + '/' + source + '/'
404 return component + source[:1] + '/' + source + '/'
406 ################################################################################
408 def move (src, dest, overwrite = 0, perms = 0664):
409 if os.path.exists(dest) and os.path.isdir(dest):
412 dest_dir = os.path.dirname(dest)
413 if not os.path.exists(dest_dir):
414 umask = os.umask(00000)
415 os.makedirs(dest_dir, 02775)
417 #print "Moving %s to %s..." % (src, dest)
418 if os.path.exists(dest) and os.path.isdir(dest):
419 dest += '/' + os.path.basename(src)
420 # Don't overwrite unless forced to
421 if os.path.exists(dest):
423 fubar("Can't move %s to %s - file already exists." % (src, dest))
425 if not os.access(dest, os.W_OK):
426 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
427 shutil.copy2(src, dest)
428 os.chmod(dest, perms)
431 def copy (src, dest, overwrite = 0, perms = 0664):
432 if os.path.exists(dest) and os.path.isdir(dest):
435 dest_dir = os.path.dirname(dest)
436 if not os.path.exists(dest_dir):
437 umask = os.umask(00000)
438 os.makedirs(dest_dir, 02775)
440 #print "Copying %s to %s..." % (src, dest)
441 if os.path.exists(dest) and os.path.isdir(dest):
442 dest += '/' + os.path.basename(src)
443 # Don't overwrite unless forced to
444 if os.path.exists(dest):
446 raise file_exists_exc
448 if not os.access(dest, os.W_OK):
449 raise cant_overwrite_exc
450 shutil.copy2(src, dest)
451 os.chmod(dest, perms)
453 ################################################################################
456 res = socket.gethostbyaddr(socket.gethostname())
457 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
458 if database_hostname:
459 return database_hostname
463 def which_conf_file ():
464 res = socket.gethostbyaddr(socket.gethostname())
465 if Cnf.get("Config::" + res[0] + "::DakConfig"):
466 return Cnf["Config::" + res[0] + "::DakConfig"]
468 return default_config
470 def which_apt_conf_file ():
471 res = socket.gethostbyaddr(socket.gethostname())
472 if Cnf.get("Config::" + res[0] + "::AptConfig"):
473 return Cnf["Config::" + res[0] + "::AptConfig"]
475 return default_apt_config
477 def which_alias_file():
478 hostname = socket.gethostbyaddr(socket.gethostname())[0]
479 aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
480 if os.path.exists(aliasfn):
485 ################################################################################
487 # Escape characters which have meaning to SQL's regex comparison operator ('~')
488 # (woefully incomplete)
491 s = s.replace('+', '\\\\+')
492 s = s.replace('.', '\\\\.')
495 ################################################################################
497 # Perform a substition of template
498 def TemplateSubst(map, filename):
499 file = open_file(filename)
500 template = file.read()
502 template = template.replace(x,map[x])
506 ################################################################################
508 def fubar(msg, exit_code=1):
509 sys.stderr.write("E: %s\n" % (msg))
513 sys.stderr.write("W: %s\n" % (msg))
515 ################################################################################
517 # Returns the user name with a laughable attempt at rfc822 conformancy
518 # (read: removing stray periods).
520 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
522 ################################################################################
532 return ("%d%s" % (c, t))
534 ################################################################################
536 def cc_fix_changes (changes):
537 o = changes.get("architecture", "")
539 del changes["architecture"]
540 changes["architecture"] = {}
542 changes["architecture"][j] = 1
544 # Sort by source name, source version, 'have source', and then by filename
545 def changes_compare (a, b):
547 a_changes = parse_changes(a)
552 b_changes = parse_changes(b)
556 cc_fix_changes (a_changes)
557 cc_fix_changes (b_changes)
559 # Sort by source name
560 a_source = a_changes.get("source")
561 b_source = b_changes.get("source")
562 q = cmp (a_source, b_source)
566 # Sort by source version
567 a_version = a_changes.get("version", "0")
568 b_version = b_changes.get("version", "0")
569 q = apt_pkg.VersionCompare(a_version, b_version)
573 # Sort by 'have source'
574 a_has_source = a_changes["architecture"].get("source")
575 b_has_source = b_changes["architecture"].get("source")
576 if a_has_source and not b_has_source:
578 elif b_has_source and not a_has_source:
581 # Fall back to sort by filename
584 ################################################################################
586 def find_next_free (dest, too_many=100):
589 while os.path.exists(dest) and extra < too_many:
590 dest = orig_dest + '.' + repr(extra)
592 if extra >= too_many:
593 raise tried_too_hard_exc
596 ################################################################################
598 def result_join (original, sep = '\t'):
600 for i in xrange(len(original)):
601 if original[i] == None:
604 list.append(original[i])
605 return sep.join(list)
607 ################################################################################
609 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
611 for line in str.split('\n'):
613 if line or include_blank_lines:
614 out += "%s%s\n" % (prefix, line)
615 # Strip trailing new line
620 ################################################################################
622 def validate_changes_file_arg(filename, require_changes=1):
623 """'filename' is either a .changes or .dak file. If 'filename' is a
624 .dak file, it's changed to be the corresponding .changes file. The
625 function then checks if the .changes file a) exists and b) is
626 readable and returns the .changes filename if so. If there's a
627 problem, the next action depends on the option 'require_changes'
630 o If 'require_changes' == -1, errors are ignored and the .changes
631 filename is returned.
632 o If 'require_changes' == 0, a warning is given and 'None' is returned.
633 o If 'require_changes' == 1, a fatal error is raised.
637 orig_filename = filename
638 if filename.endswith(".dak"):
639 filename = filename[:-4]+".changes"
641 if not filename.endswith(".changes"):
642 error = "invalid file type; not a changes file"
644 if not os.access(filename,os.R_OK):
645 if os.path.exists(filename):
646 error = "permission denied"
648 error = "file not found"
651 if require_changes == 1:
652 fubar("%s: %s." % (orig_filename, error))
653 elif require_changes == 0:
654 warn("Skipping %s - %s" % (orig_filename, error))
656 else: # We only care about the .dak file
661 ################################################################################
664 return (arch != "source" and arch != "all")
666 ################################################################################
668 def join_with_commas_and(list):
669 if len(list) == 0: return "nothing"
670 if len(list) == 1: return list[0]
671 return ", ".join(list[:-1]) + " and " + list[-1]
673 ################################################################################
678 (pkg, version, constraint) = atom
680 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
683 pp_deps.append(pp_dep)
684 return " |".join(pp_deps)
686 ################################################################################
691 ################################################################################
693 # Handle -a, -c and -s arguments; returns them as SQL constraints
694 def parse_args(Options):
698 for suite in split_args(Options["Suite"]):
699 suite_id = database.get_suite_id(suite)
701 warn("suite '%s' not recognised." % (suite))
703 suite_ids_list.append(suite_id)
705 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
707 fubar("No valid suite given.")
712 if Options["Component"]:
713 component_ids_list = []
714 for component in split_args(Options["Component"]):
715 component_id = database.get_component_id(component)
716 if component_id == -1:
717 warn("component '%s' not recognised." % (component))
719 component_ids_list.append(component_id)
720 if component_ids_list:
721 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
723 fubar("No valid component given.")
727 # Process architecture
728 con_architectures = ""
729 if Options["Architecture"]:
732 for architecture in split_args(Options["Architecture"]):
733 if architecture == "source":
736 architecture_id = database.get_architecture_id(architecture)
737 if architecture_id == -1:
738 warn("architecture '%s' not recognised." % (architecture))
740 arch_ids_list.append(architecture_id)
742 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
745 fubar("No valid architecture given.")
749 return (con_suites, con_architectures, con_components, check_source)
751 ################################################################################
753 # Inspired(tm) by Bryn Keller's print_exc_plus (See
754 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
757 tb = sys.exc_info()[2]
766 traceback.print_exc()
768 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
769 frame.f_code.co_filename,
771 for key, value in frame.f_locals.items():
772 print "\t%20s = " % key,
776 print "<unable to print>"
778 ################################################################################
780 def try_with_debug(function):
788 ################################################################################
790 # Function for use in sorting lists of architectures.
791 # Sorts normally except that 'source' dominates all others.
793 def arch_compare_sw (a, b):
794 if a == "source" and b == "source":
803 ################################################################################
805 # Split command line arguments which can be separated by either commas
806 # or whitespace. If dwim is set, it will complain about string ending
807 # in comma since this usually means someone did 'dak ls -a i386, m68k
808 # foo' or something and the inevitable confusion resulting from 'm68k'
809 # being treated as an argument is undesirable.
811 def split_args (s, dwim=1):
812 if s.find(",") == -1:
815 if s[-1:] == "," and dwim:
816 fubar("split_args: found trailing comma, spurious space maybe?")
819 ################################################################################
821 def Dict(**dict): return dict
823 ########################################
825 # Our very own version of commands.getouputstatus(), hacked to support
827 def gpgv_get_status_output(cmd, status_read, status_write):
828 cmd = ['/bin/sh', '-c', cmd]
829 p2cread, p2cwrite = os.pipe()
830 c2pread, c2pwrite = os.pipe()
831 errout, errin = os.pipe()
841 for i in range(3, 256):
842 if i != status_write:
848 os.execvp(cmd[0], cmd)
854 os.dup2(c2pread, c2pwrite)
855 os.dup2(errout, errin)
859 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
862 r = os.read(fd, 8196)
865 if fd == c2pwrite or fd == errin:
867 elif fd == status_read:
870 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
872 pid, exit_status = os.waitpid(pid, 0)
874 os.close(status_write)
875 os.close(status_read)
885 return output, status, exit_status
887 ################################################################################
889 def process_gpgv_output(status):
890 # Process the status-fd output
893 for line in status.split('\n'):
899 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
901 (gnupg, keyword) = split[:2]
902 if gnupg != "[GNUPG:]":
903 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
906 if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
907 internal_error += "found duplicate status token ('%s').\n" % (keyword)
910 keywords[keyword] = args
912 return (keywords, internal_error)
914 ################################################################################
916 def retrieve_key (filename, keyserver=None, keyring=None):
917 """Retrieve the key that signed 'filename' from 'keyserver' and
918 add it to 'keyring'. Returns nothing on success, or an error message
921 # Defaults for keyserver and keyring
923 keyserver = Cnf["Dinstall::KeyServer"]
925 keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
927 # Ensure the filename contains no shell meta-characters or other badness
928 if not re_taint_free.match(filename):
929 return "%s: tainted filename" % (filename)
931 # Invoke gpgv on the file
932 status_read, status_write = os.pipe();
933 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
934 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
936 # Process the status-fd output
937 (keywords, internal_error) = process_gpgv_output(status)
939 return internal_error
941 if not keywords.has_key("NO_PUBKEY"):
942 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
944 fingerprint = keywords["NO_PUBKEY"][0]
945 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
946 # it'll try to create a lockfile in /dev. A better solution might
947 # be a tempfile or something.
948 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
949 % (Cnf["Dinstall::SigningKeyring"])
950 cmd += " --keyring %s --keyserver %s --recv-key %s" \
951 % (keyring, keyserver, fingerprint)
952 (result, output) = commands.getstatusoutput(cmd)
954 return "'%s' failed with exit code %s" % (cmd, result)
958 ################################################################################
960 def gpg_keyring_args(keyrings=None):
962 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
964 return " ".join(["--keyring %s" % x for x in keyrings])
966 ################################################################################
968 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
969 """Check the signature of a file and return the fingerprint if the
970 signature is valid or 'None' if it's not. The first argument is the
971 filename whose signature should be checked. The second argument is a
972 reject function and is called when an error is found. The reject()
973 function must allow for two arguments: the first is the error message,
974 the second is an optional prefix string. It's possible for reject()
975 to be called more than once during an invocation of check_signature().
976 The third argument is optional and is the name of the files the
977 detached signature applies to. The fourth argument is optional and is
978 a *list* of keyrings to use. 'autofetch' can either be None, True or
979 False. If None, the default behaviour specified in the config will be
982 # Ensure the filename contains no shell meta-characters or other badness
983 if not re_taint_free.match(sig_filename):
984 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
987 if data_filename and not re_taint_free.match(data_filename):
988 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
992 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
994 # Autofetch the signing key if that's enabled
995 if autofetch == None:
996 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
998 error_msg = retrieve_key(sig_filename)
1003 # Build the command line
1004 status_read, status_write = os.pipe();
1005 cmd = "gpgv --status-fd %s %s %s %s" % (
1006 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
1008 # Invoke gpgv on the file
1009 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1011 # Process the status-fd output
1012 (keywords, internal_error) = process_gpgv_output(status)
1014 # If we failed to parse the status-fd output, let's just whine and bail now
1016 reject("internal error while performing signature check on %s." % (sig_filename))
1017 reject(internal_error, "")
1018 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1022 # Now check for obviously bad things in the processed output
1023 if keywords.has_key("KEYREVOKED"):
1024 reject("The key used to sign %s has been revoked." % (sig_filename))
1026 if keywords.has_key("BADSIG"):
1027 reject("bad signature on %s." % (sig_filename))
1029 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1030 reject("failed to check signature on %s." % (sig_filename))
1032 if keywords.has_key("NO_PUBKEY"):
1033 args = keywords["NO_PUBKEY"]
1036 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1038 if keywords.has_key("BADARMOR"):
1039 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1041 if keywords.has_key("NODATA"):
1042 reject("no signature found in %s." % (sig_filename))
1044 if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1045 args = keywords["KEYEXPIRED"]
1048 reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
1054 # Next check gpgv exited with a zero return code
1056 reject("gpgv failed while checking %s." % (sig_filename))
1058 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1060 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1063 # Sanity check the good stuff we expect
1064 if not keywords.has_key("VALIDSIG"):
1065 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1068 args = keywords["VALIDSIG"]
1070 reject("internal error while checking signature on %s." % (sig_filename))
1073 fingerprint = args[0]
1074 if not keywords.has_key("GOODSIG"):
1075 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1077 if not keywords.has_key("SIG_ID"):
1078 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1081 # Finally ensure there's not something we don't recognise
1082 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1083 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1084 NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1086 for keyword in keywords.keys():
1087 if not known_keywords.has_key(keyword):
1088 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1096 ################################################################################
1098 def gpg_get_key_addresses(fingerprint):
1099 """retreive email addresses from gpg key uids for a given fingerprint"""
1100 addresses = key_uid_email_cache.get(fingerprint)
1101 if addresses != None:
1104 cmd = "gpg --no-default-keyring %s --fingerprint %s" \
1105 % (gpg_keyring_args(), fingerprint)
1106 (result, output) = commands.getstatusoutput(cmd)
1108 for l in output.split('\n'):
1109 m = re_gpg_uid.match(l)
1111 addresses.add(m.group(1))
1112 key_uid_email_cache[fingerprint] = addresses
1115 ################################################################################
1117 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1119 def wrap(paragraph, max_length, prefix=""):
1123 words = paragraph.split()
1126 word_size = len(word)
1127 if word_size > max_length:
1129 s += line + '\n' + prefix
1130 s += word + '\n' + prefix
1133 new_length = len(line) + word_size + 1
1134 if new_length > max_length:
1135 s += line + '\n' + prefix
1148 ################################################################################
1150 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1151 # Returns fixed 'src'
1152 def clean_symlink (src, dest, root):
1153 src = src.replace(root, '', 1)
1154 dest = dest.replace(root, '', 1)
1155 dest = os.path.dirname(dest)
1156 new_src = '../' * len(dest.split('/'))
1157 return new_src + src
1159 ################################################################################
1161 def temp_filename(directory=None, dotprefix=None, perms=0700):
1162 """Return a secure and unique filename by pre-creating it.
1163 If 'directory' is non-null, it will be the directory the file is pre-created in.
1164 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1167 old_tempdir = tempfile.tempdir
1168 tempfile.tempdir = directory
1170 filename = tempfile.mktemp()
1173 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1174 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1178 tempfile.tempdir = old_tempdir
1182 ################################################################################
1184 # checks if the user part of the email is listed in the alias file
1186 def is_email_alias(email):
1188 if alias_cache == None:
1189 aliasfn = which_alias_file()
1192 for l in open(aliasfn):
1193 alias_cache.add(l.split(':')[0])
1194 uid = email.split('@')[0]
1195 return uid in alias_cache
1197 ################################################################################
1201 Cnf = apt_pkg.newConfiguration()
1202 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1204 if which_conf_file() != default_config:
1205 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1207 ################################################################################