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 default_config = "/etc/dak/dak.conf"
53 default_apt_config = "/etc/dak/apt.conf"
56 key_uid_email_cache = {}
58 ################################################################################
60 def open_file(filename, mode='r'):
62 f = open(filename, mode)
64 raise CantOpenError, filename
67 ################################################################################
69 def our_raw_input(prompt=""):
71 sys.stdout.write(prompt)
77 sys.stderr.write("\nUser interrupt (^D).\n")
80 ################################################################################
82 def extract_component_from_section(section):
85 if section.find('/') != -1:
86 component = section.split('/')[0]
88 # Expand default component
90 if Cnf.has_key("Component::%s" % section):
95 return (section, component)
97 ################################################################################
99 def parse_changes(filename, signing_rules=0):
100 """Parses a changes file and returns a dictionary where each field is a
101 key. The mandatory first argument is the filename of the .changes
104 signing_rules is an optional argument:
106 o If signing_rules == -1, no signature is required.
107 o If signing_rules == 0 (the default), a signature is required.
108 o If signing_rules == 1, it turns on the same strict format checking
111 The rules for (signing_rules == 1)-mode are:
113 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
114 followed by any PGP header data and must end with a blank line.
116 o The data section must end with a blank line and must be followed by
117 "-----BEGIN PGP SIGNATURE-----".
123 changes_in = open_file(filename)
124 lines = changes_in.readlines()
127 raise ParseChangesError, "[Empty changes file]"
129 # Reindex by line number so we can easily verify the format of
135 indexed_lines[index] = line[:-1]
139 num_of_lines = len(indexed_lines.keys())
142 while index < num_of_lines:
144 line = indexed_lines[index]
146 if signing_rules == 1:
148 if index > num_of_lines:
149 raise InvalidDscError, index
150 line = indexed_lines[index]
151 if not line.startswith("-----BEGIN PGP SIGNATURE"):
152 raise InvalidDscError, index
157 if line.startswith("-----BEGIN PGP SIGNATURE"):
159 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
161 if signing_rules == 1:
162 while index < num_of_lines and line != "":
164 line = indexed_lines[index]
166 # If we're not inside the signed data, don't process anything
167 if signing_rules >= 0 and not inside_signature:
169 slf = re_single_line_field.match(line)
171 field = slf.groups()[0].lower()
172 changes[field] = slf.groups()[1]
176 changes[field] += '\n'
178 mlf = re_multi_line_field.match(line)
181 raise ParseChangesError, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
182 if first == 1 and changes[field] != "":
183 changes[field] += '\n'
185 changes[field] += mlf.groups()[0] + '\n'
189 if signing_rules == 1 and inside_signature:
190 raise InvalidDscError, index
193 changes["filecontents"] = "".join(lines)
195 if changes.has_key("source"):
196 # Strip the source version in brackets from the source field,
197 # put it in the "source-version" field instead.
198 srcver = re_srchasver.search(changes["source"])
200 changes["source"] = srcver.group(1)
201 changes["source-version"] = srcver.group(2)
204 raise ParseChangesError, error
208 ################################################################################
210 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
212 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
215 # Make sure we have a Files: field to parse...
216 if not changes.has_key(field):
217 raise NoFilesFieldError
219 # Make sure we recognise the format of the Files: field
220 format = re_verwithext.search(changes.get("format", "0.0"))
222 raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
224 format = format.groups()
225 if format[1] == None:
226 format = int(float(format[0])), 0, format[2]
228 format = int(format[0]), int(format[1]), format[2]
229 if format[2] == None:
234 raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
236 if (format < (1,5) or format > (1,8)):
237 raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
238 if field != "files" and format < (1,8):
239 raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
241 includes_section = (not is_a_dsc) and field == "files"
243 # Parse each entry/line:
244 for i in changes[field].split('\n'):
248 section = priority = ""
251 (md5, size, section, priority, name) = s
253 (md5, size, name) = s
255 raise ParseChangesError, i
262 (section, component) = extract_component_from_section(section)
264 files[name] = Dict(size=size, section=section,
265 priority=priority, component=component)
266 files[name][hashname] = md5
270 ################################################################################
272 def force_to_utf8(s):
273 """Forces a string to UTF-8. If the string isn't already UTF-8,
274 it's assumed to be ISO-8859-1."""
279 latin1_s = unicode(s,'iso8859-1')
280 return latin1_s.encode('utf-8')
282 def rfc2047_encode(s):
283 """Encodes a (header) string per RFC2047 if necessary. If the
284 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
286 codecs.lookup('ascii')[1](s)
291 codecs.lookup('utf-8')[1](s)
292 h = email.Header.Header(s, 'utf-8', 998)
295 h = email.Header.Header(s, 'iso-8859-1', 998)
298 ################################################################################
300 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
301 # with it. I know - I'll fix the suckage and make things
304 def fix_maintainer (maintainer):
305 """Parses a Maintainer or Changed-By field and returns:
306 (1) an RFC822 compatible version,
307 (2) an RFC2047 compatible version,
311 The name is forced to UTF-8 for both (1) and (3). If the name field
312 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
313 switched to 'email (name)' format."""
314 maintainer = maintainer.strip()
316 return ('', '', '', '')
318 if maintainer.find("<") == -1:
321 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
322 email = maintainer[1:-1]
325 m = re_parse_maintainer.match(maintainer)
327 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
331 # Get an RFC2047 compliant version of the name
332 rfc2047_name = rfc2047_encode(name)
334 # Force the name to be UTF-8
335 name = force_to_utf8(name)
337 if name.find(',') != -1 or name.find('.') != -1:
338 rfc822_maint = "%s (%s)" % (email, name)
339 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
341 rfc822_maint = "%s <%s>" % (name, email)
342 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
344 if email.find("@") == -1 and email.find("buildd_") != 0:
345 raise ParseMaintError, "No @ found in email address part."
347 return (rfc822_maint, rfc2047_maint, name, email)
349 ################################################################################
351 # sendmail wrapper, takes _either_ a message string or a file as arguments
352 def send_mail (message, filename=""):
353 # If we've been passed a string dump it into a temporary file
355 filename = tempfile.mktemp()
356 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
357 os.write (fd, message)
361 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
363 raise SendmailFailedError, output
365 # Clean up any temporary files
369 ################################################################################
371 def poolify (source, component):
374 if source[:3] == "lib":
375 return component + source[:4] + '/' + source + '/'
377 return component + source[:1] + '/' + source + '/'
379 ################################################################################
381 def move (src, dest, overwrite = 0, perms = 0664):
382 if os.path.exists(dest) and os.path.isdir(dest):
385 dest_dir = os.path.dirname(dest)
386 if not os.path.exists(dest_dir):
387 umask = os.umask(00000)
388 os.makedirs(dest_dir, 02775)
390 #print "Moving %s to %s..." % (src, dest)
391 if os.path.exists(dest) and os.path.isdir(dest):
392 dest += '/' + os.path.basename(src)
393 # Don't overwrite unless forced to
394 if os.path.exists(dest):
396 fubar("Can't move %s to %s - file already exists." % (src, dest))
398 if not os.access(dest, os.W_OK):
399 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
400 shutil.copy2(src, dest)
401 os.chmod(dest, perms)
404 def copy (src, dest, overwrite = 0, perms = 0664):
405 if os.path.exists(dest) and os.path.isdir(dest):
408 dest_dir = os.path.dirname(dest)
409 if not os.path.exists(dest_dir):
410 umask = os.umask(00000)
411 os.makedirs(dest_dir, 02775)
413 #print "Copying %s to %s..." % (src, dest)
414 if os.path.exists(dest) and os.path.isdir(dest):
415 dest += '/' + os.path.basename(src)
416 # Don't overwrite unless forced to
417 if os.path.exists(dest):
419 raise FileExistsError
421 if not os.access(dest, os.W_OK):
422 raise CantOverwriteError
423 shutil.copy2(src, dest)
424 os.chmod(dest, perms)
426 ################################################################################
429 res = socket.gethostbyaddr(socket.gethostname())
430 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
431 if database_hostname:
432 return database_hostname
436 def which_conf_file ():
437 res = socket.gethostbyaddr(socket.gethostname())
438 if Cnf.get("Config::" + res[0] + "::DakConfig"):
439 return Cnf["Config::" + res[0] + "::DakConfig"]
441 return default_config
443 def which_apt_conf_file ():
444 res = socket.gethostbyaddr(socket.gethostname())
445 if Cnf.get("Config::" + res[0] + "::AptConfig"):
446 return Cnf["Config::" + res[0] + "::AptConfig"]
448 return default_apt_config
450 def which_alias_file():
451 hostname = socket.gethostbyaddr(socket.gethostname())[0]
452 aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
453 if os.path.exists(aliasfn):
458 ################################################################################
460 # Escape characters which have meaning to SQL's regex comparison operator ('~')
461 # (woefully incomplete)
464 s = s.replace('+', '\\\\+')
465 s = s.replace('.', '\\\\.')
468 ################################################################################
470 # Perform a substition of template
471 def TemplateSubst(map, filename):
472 file = open_file(filename)
473 template = file.read()
475 template = template.replace(x,map[x])
479 ################################################################################
481 def fubar(msg, exit_code=1):
482 sys.stderr.write("E: %s\n" % (msg))
486 sys.stderr.write("W: %s\n" % (msg))
488 ################################################################################
490 # Returns the user name with a laughable attempt at rfc822 conformancy
491 # (read: removing stray periods).
493 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
495 ################################################################################
505 return ("%d%s" % (c, t))
507 ################################################################################
509 def cc_fix_changes (changes):
510 o = changes.get("architecture", "")
512 del changes["architecture"]
513 changes["architecture"] = {}
515 changes["architecture"][j] = 1
517 # Sort by source name, source version, 'have source', and then by filename
518 def changes_compare (a, b):
520 a_changes = parse_changes(a)
525 b_changes = parse_changes(b)
529 cc_fix_changes (a_changes)
530 cc_fix_changes (b_changes)
532 # Sort by source name
533 a_source = a_changes.get("source")
534 b_source = b_changes.get("source")
535 q = cmp (a_source, b_source)
539 # Sort by source version
540 a_version = a_changes.get("version", "0")
541 b_version = b_changes.get("version", "0")
542 q = apt_pkg.VersionCompare(a_version, b_version)
546 # Sort by 'have source'
547 a_has_source = a_changes["architecture"].get("source")
548 b_has_source = b_changes["architecture"].get("source")
549 if a_has_source and not b_has_source:
551 elif b_has_source and not a_has_source:
554 # Fall back to sort by filename
557 ################################################################################
559 def find_next_free (dest, too_many=100):
562 while os.path.exists(dest) and extra < too_many:
563 dest = orig_dest + '.' + repr(extra)
565 if extra >= too_many:
566 raise NoFreeFilenameError
569 ################################################################################
571 def result_join (original, sep = '\t'):
573 for i in xrange(len(original)):
574 if original[i] == None:
577 list.append(original[i])
578 return sep.join(list)
580 ################################################################################
582 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
584 for line in str.split('\n'):
586 if line or include_blank_lines:
587 out += "%s%s\n" % (prefix, line)
588 # Strip trailing new line
593 ################################################################################
595 def validate_changes_file_arg(filename, require_changes=1):
596 """'filename' is either a .changes or .dak file. If 'filename' is a
597 .dak file, it's changed to be the corresponding .changes file. The
598 function then checks if the .changes file a) exists and b) is
599 readable and returns the .changes filename if so. If there's a
600 problem, the next action depends on the option 'require_changes'
603 o If 'require_changes' == -1, errors are ignored and the .changes
604 filename is returned.
605 o If 'require_changes' == 0, a warning is given and 'None' is returned.
606 o If 'require_changes' == 1, a fatal error is raised.
610 orig_filename = filename
611 if filename.endswith(".dak"):
612 filename = filename[:-4]+".changes"
614 if not filename.endswith(".changes"):
615 error = "invalid file type; not a changes file"
617 if not os.access(filename,os.R_OK):
618 if os.path.exists(filename):
619 error = "permission denied"
621 error = "file not found"
624 if require_changes == 1:
625 fubar("%s: %s." % (orig_filename, error))
626 elif require_changes == 0:
627 warn("Skipping %s - %s" % (orig_filename, error))
629 else: # We only care about the .dak file
634 ################################################################################
637 return (arch != "source" and arch != "all")
639 ################################################################################
641 def join_with_commas_and(list):
642 if len(list) == 0: return "nothing"
643 if len(list) == 1: return list[0]
644 return ", ".join(list[:-1]) + " and " + list[-1]
646 ################################################################################
651 (pkg, version, constraint) = atom
653 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
656 pp_deps.append(pp_dep)
657 return " |".join(pp_deps)
659 ################################################################################
664 ################################################################################
666 # Handle -a, -c and -s arguments; returns them as SQL constraints
667 def parse_args(Options):
671 for suite in split_args(Options["Suite"]):
672 suite_id = database.get_suite_id(suite)
674 warn("suite '%s' not recognised." % (suite))
676 suite_ids_list.append(suite_id)
678 con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
680 fubar("No valid suite given.")
685 if Options["Component"]:
686 component_ids_list = []
687 for component in split_args(Options["Component"]):
688 component_id = database.get_component_id(component)
689 if component_id == -1:
690 warn("component '%s' not recognised." % (component))
692 component_ids_list.append(component_id)
693 if component_ids_list:
694 con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
696 fubar("No valid component given.")
700 # Process architecture
701 con_architectures = ""
702 if Options["Architecture"]:
705 for architecture in split_args(Options["Architecture"]):
706 if architecture == "source":
709 architecture_id = database.get_architecture_id(architecture)
710 if architecture_id == -1:
711 warn("architecture '%s' not recognised." % (architecture))
713 arch_ids_list.append(architecture_id)
715 con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
718 fubar("No valid architecture given.")
722 return (con_suites, con_architectures, con_components, check_source)
724 ################################################################################
726 # Inspired(tm) by Bryn Keller's print_exc_plus (See
727 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
730 tb = sys.exc_info()[2]
739 traceback.print_exc()
741 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
742 frame.f_code.co_filename,
744 for key, value in frame.f_locals.items():
745 print "\t%20s = " % key,
749 print "<unable to print>"
751 ################################################################################
753 def try_with_debug(function):
761 ################################################################################
763 # Function for use in sorting lists of architectures.
764 # Sorts normally except that 'source' dominates all others.
766 def arch_compare_sw (a, b):
767 if a == "source" and b == "source":
776 ################################################################################
778 # Split command line arguments which can be separated by either commas
779 # or whitespace. If dwim is set, it will complain about string ending
780 # in comma since this usually means someone did 'dak ls -a i386, m68k
781 # foo' or something and the inevitable confusion resulting from 'm68k'
782 # being treated as an argument is undesirable.
784 def split_args (s, dwim=1):
785 if s.find(",") == -1:
788 if s[-1:] == "," and dwim:
789 fubar("split_args: found trailing comma, spurious space maybe?")
792 ################################################################################
794 def Dict(**dict): return dict
796 ########################################
798 # Our very own version of commands.getouputstatus(), hacked to support
800 def gpgv_get_status_output(cmd, status_read, status_write):
801 cmd = ['/bin/sh', '-c', cmd]
802 p2cread, p2cwrite = os.pipe()
803 c2pread, c2pwrite = os.pipe()
804 errout, errin = os.pipe()
814 for i in range(3, 256):
815 if i != status_write:
821 os.execvp(cmd[0], cmd)
827 os.dup2(c2pread, c2pwrite)
828 os.dup2(errout, errin)
832 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
835 r = os.read(fd, 8196)
838 if fd == c2pwrite or fd == errin:
840 elif fd == status_read:
843 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
845 pid, exit_status = os.waitpid(pid, 0)
847 os.close(status_write)
848 os.close(status_read)
858 return output, status, exit_status
860 ################################################################################
862 def process_gpgv_output(status):
863 # Process the status-fd output
866 for line in status.split('\n'):
872 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
874 (gnupg, keyword) = split[:2]
875 if gnupg != "[GNUPG:]":
876 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
879 if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
880 internal_error += "found duplicate status token ('%s').\n" % (keyword)
883 keywords[keyword] = args
885 return (keywords, internal_error)
887 ################################################################################
889 def retrieve_key (filename, keyserver=None, keyring=None):
890 """Retrieve the key that signed 'filename' from 'keyserver' and
891 add it to 'keyring'. Returns nothing on success, or an error message
894 # Defaults for keyserver and keyring
896 keyserver = Cnf["Dinstall::KeyServer"]
898 keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
900 # Ensure the filename contains no shell meta-characters or other badness
901 if not re_taint_free.match(filename):
902 return "%s: tainted filename" % (filename)
904 # Invoke gpgv on the file
905 status_read, status_write = os.pipe();
906 cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
907 (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
909 # Process the status-fd output
910 (keywords, internal_error) = process_gpgv_output(status)
912 return internal_error
914 if not keywords.has_key("NO_PUBKEY"):
915 return "didn't find expected NO_PUBKEY in gpgv status-fd output"
917 fingerprint = keywords["NO_PUBKEY"][0]
918 # XXX - gpg sucks. You can't use --secret-keyring=/dev/null as
919 # it'll try to create a lockfile in /dev. A better solution might
920 # be a tempfile or something.
921 cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
922 % (Cnf["Dinstall::SigningKeyring"])
923 cmd += " --keyring %s --keyserver %s --recv-key %s" \
924 % (keyring, keyserver, fingerprint)
925 (result, output) = commands.getstatusoutput(cmd)
927 return "'%s' failed with exit code %s" % (cmd, result)
931 ################################################################################
933 def gpg_keyring_args(keyrings=None):
935 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
937 return " ".join(["--keyring %s" % x for x in keyrings])
939 ################################################################################
941 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
942 """Check the signature of a file and return the fingerprint if the
943 signature is valid or 'None' if it's not. The first argument is the
944 filename whose signature should be checked. The second argument is a
945 reject function and is called when an error is found. The reject()
946 function must allow for two arguments: the first is the error message,
947 the second is an optional prefix string. It's possible for reject()
948 to be called more than once during an invocation of check_signature().
949 The third argument is optional and is the name of the files the
950 detached signature applies to. The fourth argument is optional and is
951 a *list* of keyrings to use. 'autofetch' can either be None, True or
952 False. If None, the default behaviour specified in the config will be
955 # Ensure the filename contains no shell meta-characters or other badness
956 if not re_taint_free.match(sig_filename):
957 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
960 if data_filename and not re_taint_free.match(data_filename):
961 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
965 keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
967 # Autofetch the signing key if that's enabled
968 if autofetch == None:
969 autofetch = Cnf.get("Dinstall::KeyAutoFetch")
971 error_msg = retrieve_key(sig_filename)
976 # Build the command line
977 status_read, status_write = os.pipe();
978 cmd = "gpgv --status-fd %s %s %s %s" % (
979 status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
981 # Invoke gpgv on the file
982 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
984 # Process the status-fd output
985 (keywords, internal_error) = process_gpgv_output(status)
987 # If we failed to parse the status-fd output, let's just whine and bail now
989 reject("internal error while performing signature check on %s." % (sig_filename))
990 reject(internal_error, "")
991 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
995 # Now check for obviously bad things in the processed output
996 if keywords.has_key("KEYREVOKED"):
997 reject("The key used to sign %s has been revoked." % (sig_filename))
999 if keywords.has_key("BADSIG"):
1000 reject("bad signature on %s." % (sig_filename))
1002 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1003 reject("failed to check signature on %s." % (sig_filename))
1005 if keywords.has_key("NO_PUBKEY"):
1006 args = keywords["NO_PUBKEY"]
1009 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1011 if keywords.has_key("BADARMOR"):
1012 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1014 if keywords.has_key("NODATA"):
1015 reject("no signature found in %s." % (sig_filename))
1017 if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1018 args = keywords["KEYEXPIRED"]
1021 reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
1027 # Next check gpgv exited with a zero return code
1029 reject("gpgv failed while checking %s." % (sig_filename))
1031 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1033 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1036 # Sanity check the good stuff we expect
1037 if not keywords.has_key("VALIDSIG"):
1038 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1041 args = keywords["VALIDSIG"]
1043 reject("internal error while checking signature on %s." % (sig_filename))
1046 fingerprint = args[0]
1047 if not keywords.has_key("GOODSIG"):
1048 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1050 if not keywords.has_key("SIG_ID"):
1051 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1054 # Finally ensure there's not something we don't recognise
1055 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1056 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1057 NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1059 for keyword in keywords.keys():
1060 if not known_keywords.has_key(keyword):
1061 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1069 ################################################################################
1071 def gpg_get_key_addresses(fingerprint):
1072 """retreive email addresses from gpg key uids for a given fingerprint"""
1073 addresses = key_uid_email_cache.get(fingerprint)
1074 if addresses != None:
1077 cmd = "gpg --no-default-keyring %s --fingerprint %s" \
1078 % (gpg_keyring_args(), fingerprint)
1079 (result, output) = commands.getstatusoutput(cmd)
1081 for l in output.split('\n'):
1082 m = re_gpg_uid.match(l)
1084 addresses.add(m.group(1))
1085 key_uid_email_cache[fingerprint] = addresses
1088 ################################################################################
1090 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1092 def wrap(paragraph, max_length, prefix=""):
1096 words = paragraph.split()
1099 word_size = len(word)
1100 if word_size > max_length:
1102 s += line + '\n' + prefix
1103 s += word + '\n' + prefix
1106 new_length = len(line) + word_size + 1
1107 if new_length > max_length:
1108 s += line + '\n' + prefix
1121 ################################################################################
1123 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1124 # Returns fixed 'src'
1125 def clean_symlink (src, dest, root):
1126 src = src.replace(root, '', 1)
1127 dest = dest.replace(root, '', 1)
1128 dest = os.path.dirname(dest)
1129 new_src = '../' * len(dest.split('/'))
1130 return new_src + src
1132 ################################################################################
1134 def temp_filename(directory=None, dotprefix=None, perms=0700):
1135 """Return a secure and unique filename by pre-creating it.
1136 If 'directory' is non-null, it will be the directory the file is pre-created in.
1137 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1140 old_tempdir = tempfile.tempdir
1141 tempfile.tempdir = directory
1143 filename = tempfile.mktemp()
1146 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1147 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1151 tempfile.tempdir = old_tempdir
1155 ################################################################################
1157 # checks if the user part of the email is listed in the alias file
1159 def is_email_alias(email):
1161 if alias_cache == None:
1162 aliasfn = which_alias_file()
1165 for l in open(aliasfn):
1166 alias_cache.add(l.split(':')[0])
1167 uid = email.split('@')[0]
1168 return uid in alias_cache
1170 ################################################################################
1174 Cnf = apt_pkg.newConfiguration()
1175 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1177 if which_conf_file() != default_config:
1178 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1180 ################################################################################