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 changes_parse_error_exc = "Can't parse line in .changes file"
50 invalid_dsc_format_exc = "Invalid .dsc file"
51 nk_format_exc = "Unknown Format: in .changes file"
52 no_files_exc = "No Files: field in .dsc or .changes file."
53 cant_open_exc = "Can't open file"
54 unknown_hostname_exc = "Unknown hostname"
55 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
56 file_exists_exc = "Destination file exists"
57 sendmail_failed_exc = "Sendmail invocation failed"
58 tried_too_hard_exc = "Tried too hard to find a free filename."
60 default_config = "/etc/dak/dak.conf"
61 default_apt_config = "/etc/dak/apt.conf"
64 key_uid_email_cache = {}
66 ################################################################################
68 class Error(Exception):
69 """Base class for exceptions in this module."""
72 class ParseMaintError(Error):
73 """Exception raised for errors in parsing a maintainer field.
76 message -- explanation of the error
79 def __init__(self, message):
81 self.message = message
83 ################################################################################
85 def open_file(filename, mode='r'):
87 f = open(filename, mode)
89 raise cant_open_exc, filename
92 ################################################################################
94 def our_raw_input(prompt=""):
96 sys.stdout.write(prompt)
102 sys.stderr.write("\nUser interrupt (^D).\n")
105 ################################################################################
107 def extract_component_from_section(section):
110 if section.find('/') != -1:
111 component = section.split('/')[0]
113 # Expand default component
115 if Cnf.has_key("Component::%s" % section):
120 return (section, component)
122 ################################################################################
124 def parse_changes(filename, signing_rules=0):
125 """Parses a changes file and returns a dictionary where each field is a
126 key. The mandatory first argument is the filename of the .changes
129 signing_rules is an optional argument:
131 o If signing_rules == -1, no signature is required.
132 o If signing_rules == 0 (the default), a signature is required.
133 o If signing_rules == 1, it turns on the same strict format checking
136 The rules for (signing_rules == 1)-mode are:
138 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
139 followed by any PGP header data and must end with a blank line.
141 o The data section must end with a blank line and must be followed by
142 "-----BEGIN PGP SIGNATURE-----".
148 changes_in = open_file(filename)
149 lines = changes_in.readlines()
152 raise changes_parse_error_exc, "[Empty changes file]"
154 # Reindex by line number so we can easily verify the format of
160 indexed_lines[index] = line[:-1]
164 num_of_lines = len(indexed_lines.keys())
167 while index < num_of_lines:
169 line = indexed_lines[index]
171 if signing_rules == 1:
173 if index > num_of_lines:
174 raise invalid_dsc_format_exc, index
175 line = indexed_lines[index]
176 if not line.startswith("-----BEGIN PGP SIGNATURE"):
177 raise invalid_dsc_format_exc, index
182 if line.startswith("-----BEGIN PGP SIGNATURE"):
184 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
186 if signing_rules == 1:
187 while index < num_of_lines and line != "":
189 line = indexed_lines[index]
191 # If we're not inside the signed data, don't process anything
192 if signing_rules >= 0 and not inside_signature:
194 slf = re_single_line_field.match(line)
196 field = slf.groups()[0].lower()
197 changes[field] = slf.groups()[1]
201 changes[field] += '\n'
203 mlf = re_multi_line_field.match(line)
206 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
207 if first == 1 and changes[field] != "":
208 changes[field] += '\n'
210 changes[field] += mlf.groups()[0] + '\n'
214 if signing_rules == 1 and inside_signature:
215 raise invalid_dsc_format_exc, index
218 changes["filecontents"] = "".join(lines)
220 if changes.has_key("source"):
221 # Strip the source version in brackets from the source field,
222 # put it in the "source-version" field instead.
223 srcver = re_srchasver.search(changes["source"])
225 changes["source"] = srcver.group(1)
226 changes["source-version"] = srcver.group(2)
229 raise changes_parse_error_exc, error
233 ################################################################################
235 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
237 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
240 # Make sure we have a Files: field to parse...
241 if not changes.has_key(field):
244 # Make sure we recognise the format of the Files: field
245 format = re_verwithext.search(changes.get("format", "0.0"))
247 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
249 format = format.groups()
250 if format[1] == None:
251 format = int(float(format[0])), 0, format[2]
253 format = int(format[0]), int(format[1]), format[2]
254 if format[2] == None:
259 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
261 if (format < (1,5) or format > (1,8)):
262 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
263 if field != "files" and format < (1,8):
264 raise nk_format_exc, "%s" % (changes.get("format","0.0"))
266 includes_section = (not is_a_dsc) and field == "files"
268 # Parse each entry/line:
269 for i in changes[field].split('\n'):
273 section = priority = ""
276 (md5, size, section, priority, name) = s
278 (md5, size, name) = s
280 raise changes_parse_error_exc, i
287 (section, component) = extract_component_from_section(section)
289 files[name] = Dict(size=size, section=section,
290 priority=priority, component=component)
291 files[name][hashname] = md5
295 ################################################################################
297 def force_to_utf8(s):
298 """Forces a string to UTF-8. If the string isn't already UTF-8,
299 it's assumed to be ISO-8859-1."""
304 latin1_s = unicode(s,'iso8859-1')
305 return latin1_s.encode('utf-8')
307 def rfc2047_encode(s):
308 """Encodes a (header) string per RFC2047 if necessary. If the
309 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
311 codecs.lookup('ascii')[1](s)
316 codecs.lookup('utf-8')[1](s)
317 h = email.Header.Header(s, 'utf-8', 998)
320 h = email.Header.Header(s, 'iso-8859-1', 998)
323 ################################################################################
325 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
326 # with it. I know - I'll fix the suckage and make things
329 def fix_maintainer (maintainer):
330 """Parses a Maintainer or Changed-By field and returns:
331 (1) an RFC822 compatible version,
332 (2) an RFC2047 compatible version,
336 The name is forced to UTF-8 for both (1) and (3). If the name field
337 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
338 switched to 'email (name)' format."""
339 maintainer = maintainer.strip()
341 return ('', '', '', '')
343 if maintainer.find("<") == -1:
346 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
347 email = maintainer[1:-1]
350 m = re_parse_maintainer.match(maintainer)
352 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
356 # Get an RFC2047 compliant version of the name
357 rfc2047_name = rfc2047_encode(name)
359 # Force the name to be UTF-8
360 name = force_to_utf8(name)
362 if name.find(',') != -1 or name.find('.') != -1:
363 rfc822_maint = "%s (%s)" % (email, name)
364 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
366 rfc822_maint = "%s <%s>" % (name, email)
367 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
369 if email.find("@") == -1 and email.find("buildd_") != 0:
370 raise ParseMaintError, "No @ found in email address part."
372 return (rfc822_maint, rfc2047_maint, name, email)
374 ################################################################################
376 # sendmail wrapper, takes _either_ a message string or a file as arguments
377 def send_mail (message, filename=""):
378 # If we've been passed a string dump it into a temporary file
380 filename = tempfile.mktemp()
381 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
382 os.write (fd, message)
386 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
388 raise sendmail_failed_exc, output
390 # Clean up any temporary files
394 ################################################################################
396 def poolify (source, component):
399 if source[:3] == "lib":
400 return component + source[:4] + '/' + source + '/'
402 return component + source[:1] + '/' + source + '/'
404 ################################################################################
406 def move (src, dest, overwrite = 0, perms = 0664):
407 if os.path.exists(dest) and os.path.isdir(dest):
410 dest_dir = os.path.dirname(dest)
411 if not os.path.exists(dest_dir):
412 umask = os.umask(00000)
413 os.makedirs(dest_dir, 02775)
415 #print "Moving %s to %s..." % (src, dest)
416 if os.path.exists(dest) and os.path.isdir(dest):
417 dest += '/' + os.path.basename(src)
418 # Don't overwrite unless forced to
419 if os.path.exists(dest):
421 fubar("Can't move %s to %s - file already exists." % (src, dest))
423 if not os.access(dest, os.W_OK):
424 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
425 shutil.copy2(src, dest)
426 os.chmod(dest, perms)
429 def copy (src, dest, overwrite = 0, perms = 0664):
430 if os.path.exists(dest) and os.path.isdir(dest):
433 dest_dir = os.path.dirname(dest)
434 if not os.path.exists(dest_dir):
435 umask = os.umask(00000)
436 os.makedirs(dest_dir, 02775)
438 #print "Copying %s to %s..." % (src, dest)
439 if os.path.exists(dest) and os.path.isdir(dest):
440 dest += '/' + os.path.basename(src)
441 # Don't overwrite unless forced to
442 if os.path.exists(dest):
444 raise file_exists_exc
446 if not os.access(dest, os.W_OK):
447 raise cant_overwrite_exc
448 shutil.copy2(src, dest)
449 os.chmod(dest, perms)
451 ################################################################################
454 res = socket.gethostbyaddr(socket.gethostname())
455 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
456 if database_hostname:
457 return database_hostname
461 def which_conf_file ():
462 res = socket.gethostbyaddr(socket.gethostname())
463 if Cnf.get("Config::" + res[0] + "::DakConfig"):
464 return Cnf["Config::" + res[0] + "::DakConfig"]
466 return default_config
468 def which_apt_conf_file ():
469 res = socket.gethostbyaddr(socket.gethostname())
470 if Cnf.get("Config::" + res[0] + "::AptConfig"):
471 return Cnf["Config::" + res[0] + "::AptConfig"]
473 return default_apt_config
475 def which_alias_file():
476 hostname = socket.gethostbyaddr(socket.gethostname())[0]
477 aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
478 if os.path.exists(aliasfn):
483 ################################################################################
485 # Escape characters which have meaning to SQL's regex comparison operator ('~')
486 # (woefully incomplete)
489 s = s.replace('+', '\\\\+')
490 s = s.replace('.', '\\\\.')
493 ################################################################################
495 # Perform a substition of template
496 def TemplateSubst(map, filename):
497 file = open_file(filename)
498 template = file.read()
500 template = template.replace(x,map[x])
504 ################################################################################
506 def fubar(msg, exit_code=1):
507 sys.stderr.write("E: %s\n" % (msg))
511 sys.stderr.write("W: %s\n" % (msg))
513 ################################################################################
515 # Returns the user name with a laughable attempt at rfc822 conformancy
516 # (read: removing stray periods).
518 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
520 ################################################################################
530 return ("%d%s" % (c, t))
532 ################################################################################
534 def cc_fix_changes (changes):
535 o = changes.get("architecture", "")
537 del changes["architecture"]
538 changes["architecture"] = {}
540 changes["architecture"][j] = 1
542 # Sort by source name, source version, 'have source', and then by filename
543 def changes_compare (a, b):
545 a_changes = parse_changes(a)
550 b_changes = parse_changes(b)
554 cc_fix_changes (a_changes)
555 cc_fix_changes (b_changes)
557 # Sort by source name
558 a_source = a_changes.get("source")
559 b_source = b_changes.get("source")
560 q = cmp (a_source, b_source)
564 # Sort by source version
565 a_version = a_changes.get("version", "0")
566 b_version = b_changes.get("version", "0")
567 q = apt_pkg.VersionCompare(a_version, b_version)
571 # Sort by 'have source'
572 a_has_source = a_changes["architecture"].get("source")
573 b_has_source = b_changes["architecture"].get("source")
574 if a_has_source and not b_has_source:
576 elif b_has_source and not a_has_source:
579 # Fall back to sort by filename
582 ################################################################################
584 def find_next_free (dest, too_many=100):
587 while os.path.exists(dest) and extra < too_many:
588 dest = orig_dest + '.' + repr(extra)
590 if extra >= too_many:
591 raise tried_too_hard_exc
594 ################################################################################
596 def result_join (original, sep = '\t'):
598 for i in xrange(len(original)):
599 if original[i] == None:
602 list.append(original[i])
603 return sep.join(list)
605 ################################################################################
607 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
609 for line in str.split('\n'):
611 if line or include_blank_lines:
612 out += "%s%s\n" % (prefix, line)
613 # Strip trailing new line
618 ################################################################################
620 def validate_changes_file_arg(filename, require_changes=1):
621 """'filename' is either a .changes or .dak file. If 'filename' is a
622 .dak file, it's changed to be the corresponding .changes file. The
623 function then checks if the .changes file a) exists and b) is
624 readable and returns the .changes filename if so. If there's a
625 problem, the next action depends on the option 'require_changes'
628 o If 'require_changes' == -1, errors are ignored and the .changes
629 filename is returned.
630 o If 'require_changes' == 0, a warning is given and 'None' is returned.
631 o If 'require_changes' == 1, a fatal error is raised.
635 orig_filename = filename
636 if filename.endswith(".dak"):
637 filename = filename[:-4]+".changes"
639 if not filename.endswith(".changes"):
640 error = "invalid file type; not a changes file"
642 if not os.access(filename,os.R_OK):
643 if os.path.exists(filename):
644 error = "permission denied"
646 error = "file not found"
649 if require_changes == 1:
650 fubar("%s: %s." % (orig_filename, error))
651 elif require_changes == 0:
652 warn("Skipping %s - %s" % (orig_filename, error))
654 else: # We only care about the .dak file
659 ################################################################################
662 return (arch != "source" and arch != "all")
664 ################################################################################
666 def join_with_commas_and(list):
667 if len(list) == 0: return "nothing"
668 if len(list) == 1: return list[0]
669 return ", ".join(list[:-1]) + " and " + list[-1]
671 ################################################################################
676 (pkg, version, constraint) = atom
678 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
681 pp_deps.append(pp_dep)
682 return " |".join(pp_deps)
684 ################################################################################
689 ################################################################################
691 # Handle -a, -c and -s arguments; returns them as SQL constraints
692 def parse_args(Options):
696 for suite in split_args(Options["Suite"]):
697 suite_id = database.get_suite_id(suite)
699 warn("suite '%s' not recognised." % (suite))
701 suite_ids_list.append(suite_id)
703 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
705 fubar("No valid suite given.")
710 if Options["Component"]:
711 component_ids_list = []
712 for component in split_args(Options["Component"]):
713 component_id = database.get_component_id(component)
714 if component_id == -1:
715 warn("component '%s' not recognised." % (component))
717 component_ids_list.append(component_id)
718 if component_ids_list:
719 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
721 fubar("No valid component given.")
725 # Process architecture
726 con_architectures = ""
727 if Options["Architecture"]:
730 for architecture in split_args(Options["Architecture"]):
731 if architecture == "source":
734 architecture_id = database.get_architecture_id(architecture)
735 if architecture_id == -1:
736 warn("architecture '%s' not recognised." % (architecture))
738 arch_ids_list.append(architecture_id)
740 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
743 fubar("No valid architecture given.")
747 return (con_suites, con_architectures, con_components, check_source)
749 ################################################################################
751 # Inspired(tm) by Bryn Keller's print_exc_plus (See
752 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
755 tb = sys.exc_info()[2]
764 traceback.print_exc()
766 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
767 frame.f_code.co_filename,
769 for key, value in frame.f_locals.items():
770 print "\t%20s = " % key,
774 print "<unable to print>"
776 ################################################################################
778 def try_with_debug(function):
786 ################################################################################
788 # Function for use in sorting lists of architectures.
789 # Sorts normally except that 'source' dominates all others.
791 def arch_compare_sw (a, b):
792 if a == "source" and b == "source":
801 ################################################################################
803 # Split command line arguments which can be separated by either commas
804 # or whitespace. If dwim is set, it will complain about string ending
805 # in comma since this usually means someone did 'dak ls -a i386, m68k
806 # foo' or something and the inevitable confusion resulting from 'm68k'
807 # being treated as an argument is undesirable.
809 def split_args (s, dwim=1):
810 if s.find(",") == -1:
813 if s[-1:] == "," and dwim:
814 fubar("split_args: found trailing comma, spurious space maybe?")
817 ################################################################################
819 def Dict(**dict): return dict
821 ########################################
823 # Our very own version of commands.getouputstatus(), hacked to support
825 def gpgv_get_status_output(cmd, status_read, status_write):
826 cmd = ['/bin/sh', '-c', cmd]
827 p2cread, p2cwrite = os.pipe()
828 c2pread, c2pwrite = os.pipe()
829 errout, errin = os.pipe()
839 for i in range(3, 256):
840 if i != status_write:
846 os.execvp(cmd[0], cmd)
852 os.dup2(c2pread, c2pwrite)
853 os.dup2(errout, errin)
857 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
860 r = os.read(fd, 8196)
863 if fd == c2pwrite or fd == errin:
865 elif fd == status_read:
868 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
870 pid, exit_status = os.waitpid(pid, 0)
872 os.close(status_write)
873 os.close(status_read)
883 return output, status, exit_status
885 ################################################################################
887 def process_gpgv_output(status):
888 # Process the status-fd output
891 for line in status.split('\n'):
897 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
899 (gnupg, keyword) = split[:2]
900 if gnupg != "[GNUPG:]":
901 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
904 if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
905 internal_error += "found duplicate status token ('%s').\n" % (keyword)
908 keywords[keyword] = args
910 return (keywords, internal_error)
912 ################################################################################
914 def retrieve_key (filename, keyserver=None, keyring=None):
915 """Retrieve the key that signed 'filename' from 'keyserver' and
916 add it to 'keyring'. Returns nothing on success, or an error message
919 # Defaults for keyserver and keyring
921 keyserver = Cnf["Dinstall::KeyServer"]
923 keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
925 # Ensure the filename contains no shell meta-characters or other badness
926 if not re_taint_free.match(filename):
927 return "%s: tainted filename" % (filename)
929 # Invoke gpgv on the file
930 status_read, status_write = os.pipe();
931 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
932 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
934 # Process the status-fd output
935 (keywords, internal_error) = process_gpgv_output(status)
937 return internal_error
939 if not keywords.has_key("NO_PUBKEY"):
940 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
942 fingerprint = keywords["NO_PUBKEY"][0]
943 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
944 # it'll try to create a lockfile in /dev. A better solution might
945 # be a tempfile or something.
946 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
947 % (Cnf["Dinstall::SigningKeyring"])
948 cmd += " --keyring %s --keyserver %s --recv-key %s" \
949 % (keyring, keyserver, fingerprint)
950 (result, output) = commands.getstatusoutput(cmd)
952 return "'%s' failed with exit code %s" % (cmd, result)
956 ################################################################################
958 def gpg_keyring_args(keyrings=None):
960 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
962 return " ".join(["--keyring %s" % x for x in keyrings])
964 ################################################################################
966 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
967 """Check the signature of a file and return the fingerprint if the
968 signature is valid or 'None' if it's not. The first argument is the
969 filename whose signature should be checked. The second argument is a
970 reject function and is called when an error is found. The reject()
971 function must allow for two arguments: the first is the error message,
972 the second is an optional prefix string. It's possible for reject()
973 to be called more than once during an invocation of check_signature().
974 The third argument is optional and is the name of the files the
975 detached signature applies to. The fourth argument is optional and is
976 a *list* of keyrings to use. 'autofetch' can either be None, True or
977 False. If None, the default behaviour specified in the config will be
980 # Ensure the filename contains no shell meta-characters or other badness
981 if not re_taint_free.match(sig_filename):
982 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
985 if data_filename and not re_taint_free.match(data_filename):
986 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
990 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
992 # Autofetch the signing key if that's enabled
993 if autofetch == None:
994 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
996 error_msg = retrieve_key(sig_filename)
1001 # Build the command line
1002 status_read, status_write = os.pipe();
1003 cmd = "gpgv --status-fd %s %s %s %s" % (
1004 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
1006 # Invoke gpgv on the file
1007 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1009 # Process the status-fd output
1010 (keywords, internal_error) = process_gpgv_output(status)
1012 # If we failed to parse the status-fd output, let's just whine and bail now
1014 reject("internal error while performing signature check on %s." % (sig_filename))
1015 reject(internal_error, "")
1016 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1020 # Now check for obviously bad things in the processed output
1021 if keywords.has_key("KEYREVOKED"):
1022 reject("The key used to sign %s has been revoked." % (sig_filename))
1024 if keywords.has_key("BADSIG"):
1025 reject("bad signature on %s." % (sig_filename))
1027 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1028 reject("failed to check signature on %s." % (sig_filename))
1030 if keywords.has_key("NO_PUBKEY"):
1031 args = keywords["NO_PUBKEY"]
1034 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1036 if keywords.has_key("BADARMOR"):
1037 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1039 if keywords.has_key("NODATA"):
1040 reject("no signature found in %s." % (sig_filename))
1042 if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1043 args = keywords["KEYEXPIRED"]
1046 reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
1052 # Next check gpgv exited with a zero return code
1054 reject("gpgv failed while checking %s." % (sig_filename))
1056 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1058 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1061 # Sanity check the good stuff we expect
1062 if not keywords.has_key("VALIDSIG"):
1063 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1066 args = keywords["VALIDSIG"]
1068 reject("internal error while checking signature on %s." % (sig_filename))
1071 fingerprint = args[0]
1072 if not keywords.has_key("GOODSIG"):
1073 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1075 if not keywords.has_key("SIG_ID"):
1076 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1079 # Finally ensure there's not something we don't recognise
1080 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1081 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1082 NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1084 for keyword in keywords.keys():
1085 if not known_keywords.has_key(keyword):
1086 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1094 ################################################################################
1096 def gpg_get_key_addresses(fingerprint):
1097 """retreive email addresses from gpg key uids for a given fingerprint"""
1098 addresses = key_uid_email_cache.get(fingerprint)
1099 if addresses != None:
1102 cmd = "gpg --no-default-keyring %s --fingerprint %s" \
1103 % (gpg_keyring_args(), fingerprint)
1104 (result, output) = commands.getstatusoutput(cmd)
1106 for l in output.split('\n'):
1107 m = re_gpg_uid.match(l)
1109 addresses.add(m.group(1))
1110 key_uid_email_cache[fingerprint] = addresses
1113 ################################################################################
1115 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1117 def wrap(paragraph, max_length, prefix=""):
1121 words = paragraph.split()
1124 word_size = len(word)
1125 if word_size > max_length:
1127 s += line + '\n' + prefix
1128 s += word + '\n' + prefix
1131 new_length = len(line) + word_size + 1
1132 if new_length > max_length:
1133 s += line + '\n' + prefix
1146 ################################################################################
1148 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1149 # Returns fixed 'src'
1150 def clean_symlink (src, dest, root):
1151 src = src.replace(root, '', 1)
1152 dest = dest.replace(root, '', 1)
1153 dest = os.path.dirname(dest)
1154 new_src = '../' * len(dest.split('/'))
1155 return new_src + src
1157 ################################################################################
1159 def temp_filename(directory=None, dotprefix=None, perms=0700):
1160 """Return a secure and unique filename by pre-creating it.
1161 If 'directory' is non-null, it will be the directory the file is pre-created in.
1162 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1165 old_tempdir = tempfile.tempdir
1166 tempfile.tempdir = directory
1168 filename = tempfile.mktemp()
1171 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1172 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1176 tempfile.tempdir = old_tempdir
1180 ################################################################################
1182 # checks if the user part of the email is listed in the alias file
1184 def is_email_alias(email):
1186 if alias_cache == None:
1187 aliasfn = which_alias_file()
1190 for l in open(aliasfn):
1191 alias_cache.add(l.split(':')[0])
1192 uid = email.split('@')[0]
1193 return uid in alias_cache
1195 ################################################################################
1199 Cnf = apt_pkg.newConfiguration()
1200 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1202 if which_conf_file() != default_config:
1203 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1205 ################################################################################