]> git.decadent.org.uk Git - dak.git/blob - utils.py
* katie.py (source_exists): expand the list of distributionsthe source may exist...
[dak.git] / utils.py
1 #!/usr/bin/env python
2
3 # Utility functions
4 # Copyright (C) 2000, 2001, 2002, 2003  James Troup <james@nocrew.org>
5 # $Id: utils.py,v 1.57 2003-03-14 19:05:13 troup Exp $
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 ################################################################################
22
23 import commands, os, pwd, re, select, socket, shutil, string, sys, tempfile, traceback;
24 import apt_pkg;
25 import db_access;
26
27 ################################################################################
28
29 re_comments = re.compile(r"\#.*")
30 re_no_epoch = re.compile(r"^\d*\:")
31 re_no_revision = re.compile(r"\-[^-]*$")
32 re_arch_from_filename = re.compile(r"/binary-[^/]+/")
33 re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
34 re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$");
35 re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$");
36
37 re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)");
38 re_multi_line_field = re.compile(r"^\s(.*)");
39 re_taint_free = re.compile(r"^[-+~\.\w]+$");
40
41 re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\> \t]+)\>");
42
43 changes_parse_error_exc = "Can't parse line in .changes file";
44 invalid_dsc_format_exc = "Invalid .dsc file";
45 nk_format_exc = "Unknown Format: in .changes file";
46 no_files_exc = "No Files: field in .dsc file.";
47 cant_open_exc = "Can't read file.";
48 unknown_hostname_exc = "Unknown hostname";
49 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
50 file_exists_exc = "Destination file exists";
51 send_mail_invalid_args_exc = "Both arguments are non-null.";
52 sendmail_failed_exc = "Sendmail invocation failed";
53 tried_too_hard_exc = "Tried too hard to find a free filename.";
54
55 default_config = "/etc/katie/katie.conf";
56 default_apt_config = "/etc/katie/apt.conf";
57
58 ######################################################################################
59
60 def open_file(filename, mode='r'):
61     try:
62         f = open(filename, mode);
63     except IOError:
64         raise cant_open_exc, filename
65     return f
66
67 ######################################################################################
68
69 def our_raw_input(prompt=""):
70     if prompt:
71         sys.stdout.write(prompt);
72     sys.stdout.flush();
73     try:
74         ret = raw_input();
75         return ret
76     except EOFError:
77         sys.stderr.write('\nUser interrupt (^D).\n');
78         raise SystemExit;
79
80 ######################################################################################
81
82 def str_isnum (s):
83     for c in s:
84         if c not in string.digits:
85             return 0;
86     return 1;
87
88 ######################################################################################
89
90 def extract_component_from_section(section):
91     component = "";
92
93     if section.find('/') != -1:
94         component = section.split('/')[0];
95     if component.lower() == "non-us" and section.count('/') > 0:
96         s = component + '/' + section.split('/')[1];
97         if Cnf.has_key("Component::%s" % s): # Avoid e.g. non-US/libs
98             component = s;
99
100     if section.lower() == "non-us":
101         component = "non-US/main";
102
103     # non-US prefix is case insensitive
104     if component.lower()[:6] == "non-us":
105         component = "non-US"+component[6:];
106
107     # Expand default component
108     if component == "":
109         if Cnf.has_key("Component::%s" % section):
110             component = section;
111         else:
112             component = "main";
113     elif component == "non-US":
114         component = "non-US/main";
115
116     return (section, component);
117
118 ######################################################################################
119
120 # dsc_whitespace_rules turns on strict format checking to avoid
121 # allowing in source packages which are unextracable by the
122 # inappropriately fragile dpkg-source.
123 #
124 # The rules are:
125 #
126 #
127 # o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
128 #   followed by any PGP header data and must end with a blank line.
129 #
130 # o The data section must end with a blank line and must be followed by
131 #   "-----BEGIN PGP SIGNATURE-----".
132
133 def parse_changes(filename, dsc_whitespace_rules=0):
134     changes_in = open_file(filename);
135     error = "";
136     changes = {};
137     lines = changes_in.readlines();
138
139     if not lines:
140         raise changes_parse_error_exc, "[Empty changes file]";
141
142     # Reindex by line number so we can easily verify the format of
143     # .dsc files...
144     index = 0;
145     indexed_lines = {};
146     for line in lines:
147         index += 1;
148         indexed_lines[index] = line[:-1];
149
150     inside_signature = 0;
151
152     indices = indexed_lines.keys()
153     index = 0;
154     first = -1;
155     while index < max(indices):
156         index += 1;
157         line = indexed_lines[index];
158         if line == "":
159             if dsc_whitespace_rules:
160                 index += 1;
161                 if index > max(indices):
162                     raise invalid_dsc_format_exc, index;
163                 line = indexed_lines[index];
164                 if not line.startswith("-----BEGIN PGP SIGNATURE"):
165                     raise invalid_dsc_format_exc, index;
166                 inside_signature = 0;
167                 break;
168         if line.startswith("-----BEGIN PGP SIGNATURE"):
169             break;
170         if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
171             if dsc_whitespace_rules:
172                 inside_signature = 1;
173                 while index < max(indices) and line != "":
174                     index += 1;
175                     line = indexed_lines[index];
176             continue;
177         slf = re_single_line_field.match(line);
178         if slf:
179             field = slf.groups()[0].lower();
180             changes[field] = slf.groups()[1];
181             first = 1;
182             continue;
183         if line == " .":
184             changes[field] += '\n';
185             continue;
186         mlf = re_multi_line_field.match(line);
187         if mlf:
188             if first == -1:
189                 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line);
190             if first == 1 and changes[field] != "":
191                 changes[field] += '\n';
192             first = 0;
193             changes[field] += mlf.groups()[0] + '\n';
194             continue;
195         error += line;
196
197     if dsc_whitespace_rules and inside_signature:
198         raise invalid_dsc_format_exc, index;
199
200     changes_in.close();
201     changes["filecontents"] = "".join(lines);
202
203     if error != "":
204         raise changes_parse_error_exc, error;
205
206     return changes;
207
208 ######################################################################################
209
210 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
211
212 def build_file_list(changes, is_a_dsc=0):
213     files = {}
214     format = changes.get("format", "")
215     if format != "":
216         format = float(format)
217     if not is_a_dsc and (format < 1.5 or format > 2.0):
218         raise nk_format_exc, format;
219
220     # No really, this has happened.  Think 0 length .dsc file.
221     if not changes.has_key("files"):
222         raise no_files_exc
223
224     for i in changes["files"].split("\n"):
225         if i == "":
226             break
227         s = i.split();
228         section = priority = "";
229         try:
230             if is_a_dsc:
231                 (md5, size, name) = s
232             else:
233                 (md5, size, section, priority, name) = s
234         except ValueError:
235             raise changes_parse_error_exc, i
236
237         if section == "": section = "-"
238         if priority == "": priority = "-"
239
240         (section, component) = extract_component_from_section(section);
241
242         files[name] = { "md5sum" : md5,
243                         "size" : size,
244                         "section": section,
245                         "priority": priority,
246                         "component": component }
247
248     return files
249
250 ######################################################################################
251
252 # Fix the `Maintainer:' field to be an RFC822 compatible address.
253 # cf. Debian Policy Manual (D.2.4)
254 #
255 # 06:28|<Culus> 'The standard sucks, but my tool is supposed to
256 #                interoperate with it. I know - I'll fix the suckage
257 #                and make things incompatible!'
258
259 def fix_maintainer (maintainer):
260     m = re_parse_maintainer.match(maintainer);
261     rfc822 = maintainer;
262     name = "";
263     email = "";
264     if m != None and len(m.groups()) == 2:
265         name = m.group(1);
266         email = m.group(2);
267         if name.find(',') != -1 or name.find('.') != -1:
268             rfc822 = "%s (%s)" % (email, name);
269     return (rfc822, name, email)
270
271 ######################################################################################
272
273 # sendmail wrapper, takes _either_ a message string or a file as arguments
274 def send_mail (message, filename=""):
275         # Sanity check arguments
276         if message != "" and filename != "":
277             raise send_mail_invalid_args_exc;
278
279         # If we've been passed a string dump it into a temporary file
280         if message != "":
281             filename = tempfile.mktemp();
282             fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
283             os.write (fd, message);
284             os.close (fd);
285
286         # Invoke sendmail
287         (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename));
288         if (result != 0):
289             raise sendmail_failed_exc, output;
290
291         # Clean up any temporary files
292         if message !="":
293             os.unlink (filename);
294
295 ######################################################################################
296
297 def poolify (source, component):
298     if component != "":
299         component += '/';
300     # FIXME: this is nasty
301     component = component.lower().replace('non-us/', 'non-US/');
302     if source[:3] == "lib":
303         return component + source[:4] + '/' + source + '/'
304     else:
305         return component + source[:1] + '/' + source + '/'
306
307 ######################################################################################
308
309 def move (src, dest, overwrite = 0, perms = 0664):
310     if os.path.exists(dest) and os.path.isdir(dest):
311         dest_dir = dest;
312     else:
313         dest_dir = os.path.dirname(dest);
314     if not os.path.exists(dest_dir):
315         umask = os.umask(00000);
316         os.makedirs(dest_dir, 02775);
317         os.umask(umask);
318     #print "Moving %s to %s..." % (src, dest);
319     if os.path.exists(dest) and os.path.isdir(dest):
320         dest += '/' + os.path.basename(src);
321     # Don't overwrite unless forced to
322     if os.path.exists(dest):
323         if not overwrite:
324             raise file_exists_exc;
325         else:
326             if not os.access(dest, os.W_OK):
327                 raise cant_overwrite_exc
328     shutil.copy2(src, dest);
329     os.chmod(dest, perms);
330     os.unlink(src);
331
332 def copy (src, dest, overwrite = 0, perms = 0664):
333     if os.path.exists(dest) and os.path.isdir(dest):
334         dest_dir = dest;
335     else:
336         dest_dir = os.path.dirname(dest);
337     if not os.path.exists(dest_dir):
338         umask = os.umask(00000);
339         os.makedirs(dest_dir, 02775);
340         os.umask(umask);
341     #print "Copying %s to %s..." % (src, dest);
342     if os.path.exists(dest) and os.path.isdir(dest):
343         dest += '/' + os.path.basename(src);
344     # Don't overwrite unless forced to
345     if os.path.exists(dest):
346         if not overwrite:
347             raise file_exists_exc
348         else:
349             if not os.access(dest, os.W_OK):
350                 raise cant_overwrite_exc
351     shutil.copy2(src, dest);
352     os.chmod(dest, perms);
353
354 ######################################################################################
355
356 def where_am_i ():
357     res = socket.gethostbyaddr(socket.gethostname());
358     database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname");
359     if database_hostname:
360         return database_hostname;
361     else:
362         return res[0];
363
364 def which_conf_file ():
365     res = socket.gethostbyaddr(socket.gethostname());
366     if Cnf.get("Config::" + res[0] + "::KatieConfig"):
367         return Cnf["Config::" + res[0] + "::KatieConfig"]
368     else:
369         return default_config;
370
371 def which_apt_conf_file ():
372     res = socket.gethostbyaddr(socket.gethostname());
373     if Cnf.get("Config::" + res[0] + "::AptConfig"):
374         return Cnf["Config::" + res[0] + "::AptConfig"]
375     else:
376         return default_apt_config;
377
378 ######################################################################################
379
380 # Escape characters which have meaning to SQL's regex comparison operator ('~')
381 # (woefully incomplete)
382
383 def regex_safe (s):
384     s = s.replace('+', '\\\\+');
385     s = s.replace('.', '\\\\.');
386     return s
387
388 ######################################################################################
389
390 # Perform a substition of template
391 def TemplateSubst(map, filename):
392     file = open_file(filename);
393     template = file.read();
394     for x in map.keys():
395         template = template.replace(x,map[x]);
396     file.close();
397     return template;
398
399 ######################################################################################
400
401 def fubar(msg, exit_code=1):
402     sys.stderr.write("E: %s\n" % (msg));
403     sys.exit(exit_code);
404
405 def warn(msg):
406     sys.stderr.write("W: %s\n" % (msg));
407
408 ######################################################################################
409
410 # Returns the user name with a laughable attempt at rfc822 conformancy
411 # (read: removing stray periods).
412 def whoami ():
413     return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '');
414
415 ######################################################################################
416
417 def size_type (c):
418     t  = " b";
419     if c > 10000:
420         c = c / 1000;
421         t = " Kb";
422     if c > 10000:
423         c = c / 1000;
424         t = " Mb";
425     return ("%d%s" % (c, t))
426
427 ################################################################################
428
429 def cc_fix_changes (changes):
430     o = changes.get("architecture", "")
431     if o != "":
432         del changes["architecture"]
433     changes["architecture"] = {}
434     for j in o.split():
435         changes["architecture"][j] = 1
436
437 # Sort by source name, source version, 'have source', and then by filename
438 def changes_compare (a, b):
439     try:
440         a_changes = parse_changes(a);
441     except:
442         return -1;
443
444     try:
445         b_changes = parse_changes(b);
446     except:
447         return 1;
448
449     cc_fix_changes (a_changes);
450     cc_fix_changes (b_changes);
451
452     # Sort by source name
453     a_source = a_changes.get("source");
454     b_source = b_changes.get("source");
455     q = cmp (a_source, b_source);
456     if q:
457         return q;
458
459     # Sort by source version
460     a_version = a_changes.get("version");
461     b_version = b_changes.get("version");
462     q = apt_pkg.VersionCompare(a_version, b_version);
463     if q:
464         return q;
465
466     # Sort by 'have source'
467     a_has_source = a_changes["architecture"].get("source");
468     b_has_source = b_changes["architecture"].get("source");
469     if a_has_source and not b_has_source:
470         return -1;
471     elif b_has_source and not a_has_source:
472         return 1;
473
474     # Fall back to sort by filename
475     return cmp(a, b);
476
477 ################################################################################
478
479 def find_next_free (dest, too_many=100):
480     extra = 0;
481     orig_dest = dest;
482     while os.path.exists(dest) and extra < too_many:
483         dest = orig_dest + '.' + repr(extra);
484         extra += 1;
485     if extra >= too_many:
486         raise tried_too_hard_exc;
487     return dest;
488
489 ################################################################################
490
491 def result_join (original, sep = '\t'):
492     list = [];
493     for i in xrange(len(original)):
494         if original[i] == None:
495             list.append("");
496         else:
497             list.append(original[i]);
498     return sep.join(list);
499
500 ################################################################################
501
502 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
503     out = "";
504     for line in str.split('\n'):
505         line = line.strip();
506         if line or include_blank_lines:
507             out += "%s%s\n" % (prefix, line);
508     # Strip trailing new line
509     if out:
510         out = out[:-1];
511     return out;
512
513 ################################################################################
514
515 def validate_changes_file_arg(file, fatal=1):
516     error = None;
517
518     orig_filename = file
519     if file.endswith(".katie"):
520         file = file[:-6]+".changes";
521
522     if not file.endswith(".changes"):
523         error = "invalid file type; not a changes file";
524     else:
525         if not os.access(file,os.R_OK):
526             if os.path.exists(file):
527                 error = "permission denied";
528             else:
529                 error = "file not found";
530
531     if error:
532         if fatal:
533             fubar("%s: %s." % (orig_filename, error));
534         else:
535             warn("Skipping %s - %s" % (orig_filename, error));
536             return None;
537     else:
538         return file;
539
540 ################################################################################
541
542 def real_arch(arch):
543     return (arch != "source" and arch != "all");
544
545 ################################################################################
546
547 def join_with_commas_and(list):
548         if len(list) == 0: return "nothing";
549         if len(list) == 1: return list[0];
550         return ", ".join(list[:-1]) + " and " + list[-1];
551
552 ################################################################################
553
554 def get_conf():
555         return Cnf;
556
557 ################################################################################
558
559 # Handle -a, -c and -s arguments; returns them as SQL constraints
560 def parse_args(Options):
561     # Process suite
562     if Options["Suite"]:
563         suite_ids_list = [];
564         for suite in split_args(Options["Suite"]):
565             suite_id = db_access.get_suite_id(suite);
566             if suite_id == -1:
567                 warn("suite '%s' not recognised." % (suite));
568             else:
569                 suite_ids_list.append(suite_id);
570         if suite_ids_list:
571             con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
572         else:
573             fubar("No valid suite given.");
574     else:
575         con_suites = "";
576
577     # Process component
578     if Options["Component"]:
579         component_ids_list = [];
580         for component in split_args(Options["Component"]):
581             component_id = db_access.get_component_id(component);
582             if component_id == -1:
583                 warn("component '%s' not recognised." % (component));
584             else:
585                 component_ids_list.append(component_id);
586         if component_ids_list:
587             con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
588         else:
589             fubar("No valid component given.");
590     else:
591         con_components = "";
592
593     # Process architecture
594     con_architectures = "";
595     if Options["Architecture"]:
596         arch_ids_list = [];
597         check_source = 0;
598         for architecture in split_args(Options["Architecture"]):
599             if architecture == "source":
600                 check_source = 1;
601             else:
602                 architecture_id = db_access.get_architecture_id(architecture);
603                 if architecture_id == -1:
604                     warn("architecture '%s' not recognised." % (architecture));
605                 else:
606                     arch_ids_list.append(architecture_id);
607         if arch_ids_list:
608             con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
609         else:
610             if not check_source:
611                 fubar("No valid architecture given.");
612     else:
613         check_source = 1;
614
615     return (con_suites, con_architectures, con_components, check_source);
616
617 ################################################################################
618
619 # Inspired(tm) by Bryn Keller's print_exc_plus (See
620 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
621
622 def print_exc():
623     tb = sys.exc_info()[2];
624     while tb.tb_next:
625         tb = tb.tb_next;
626     stack = [];
627     frame = tb.tb_frame;
628     while frame:
629         stack.append(frame);
630         frame = frame.f_back;
631     stack.reverse();
632     traceback.print_exc();
633     for frame in stack:
634         print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
635                                              frame.f_code.co_filename,
636                                              frame.f_lineno);
637         for key, value in frame.f_locals.items():
638             print "\t%20s = " % key,;
639             try:
640                 print value;
641             except:
642                 print "<unable to print>";
643
644 ################################################################################
645
646 def try_with_debug(function):
647     try:
648         function();
649     except SystemExit:
650         raise;
651     except:
652         print_exc();
653
654 ################################################################################
655
656 # Function for use in sorting lists of architectures.
657 # Sorts normally except that 'source' dominates all others.
658
659 def arch_compare_sw (a, b):
660     if a == "source" and b == "source":
661         return 0;
662     elif a == "source":
663         return -1;
664     elif b == "source":
665         return 1;
666
667     return cmp (a, b);
668
669 ################################################################################
670
671 # Split command line arguments which can be separated by either commas
672 # or whitespace.  If dwim is set, it will complain about string ending
673 # in comma since this usually means someone did 'madison -a i386, m68k
674 # foo' or something and the inevitable confusion resulting from 'm68k'
675 # being treated as an argument is undesirable.
676
677 def split_args (s, dwim=1):
678     if s.find(",") == -1:
679         return s.split();
680     else:
681         if s[-1:] == "," and dwim:
682             fubar("split_args: found trailing comma, spurious space maybe?");
683         return s.split(",");
684
685 ################################################################################
686
687 def Dict(**dict): return dict
688
689 ########################################
690
691 # Our very own version of commands.getouputstatus(), hacked to support
692 # gpgv's status fd.
693 def gpgv_get_status_output(cmd, status_read, status_write):
694     cmd = ['/bin/sh', '-c', cmd];
695     p2cread, p2cwrite = os.pipe();
696     c2pread, c2pwrite = os.pipe();
697     errout, errin = os.pipe();
698     pid = os.fork();
699     if pid == 0:
700         # Child
701         os.close(0);
702         os.close(1);
703         os.dup(p2cread);
704         os.dup(c2pwrite);
705         os.close(2);
706         os.dup(errin);
707         for i in range(3, 256):
708             if i != status_write:
709                 try:
710                     os.close(i);
711                 except:
712                     pass;
713         try:
714             os.execvp(cmd[0], cmd);
715         finally:
716             os._exit(1);
717
718     # parent
719     os.close(p2cread)
720     os.dup2(c2pread, c2pwrite);
721     os.dup2(errout, errin);
722
723     output = status = "";
724     while 1:
725         i, o, e = select.select([c2pwrite, errin, status_read], [], []);
726         more_data = [];
727         for fd in i:
728             r = os.read(fd, 8196);
729             if len(r) > 0:
730                 more_data.append(fd);
731                 if fd == c2pwrite or fd == errin:
732                     output += r;
733                 elif fd == status_read:
734                     status += r;
735                 else:
736                     fubar("Unexpected file descriptor [%s] returned from select\n" % (fd));
737         if not more_data:
738             pid, exit_status = os.waitpid(pid, 0)
739             try:
740                 os.close(status_write);
741                 os.close(status_read);
742                 os.close(c2pread);
743                 os.close(c2pwrite);
744                 os.close(p2cwrite);
745                 os.close(errin);
746                 os.close(errout);
747             except:
748                 pass;
749             break;
750
751     return output, status, exit_status;
752
753 ############################################################
754
755
756 def check_signature (filename, reject):
757     """Check the signature of a file and return the fingerprint if the
758 signature is valid or 'None' if it's not.  The first argument is the
759 filename whose signature should be checked.  The second argument is a
760 reject function and is called when an error is found.  The reject()
761 function must allow for two arguments: the first is the error message,
762 the second is an optional prefix string.  It is possible that reject()
763 is called more than once during an invocation of check_signature()."""
764
765     # Ensure the filename contains no shell meta-characters or other badness
766     if not re_taint_free.match(os.path.basename(filename)):
767         reject("!!WARNING!! tainted filename: '%s'." % (filename));
768         return 0;
769
770     # Invoke gpgv on the file
771     status_read, status_write = os.pipe();
772     cmd = "gpgv --status-fd %s --keyring %s --keyring %s %s" \
773           % (status_write, Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"], filename);
774     (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write);
775
776     # Process the status-fd output
777     keywords = {};
778     bad = internal_error = "";
779     for line in status.split('\n'):
780         line = line.strip();
781         if line == "":
782             continue;
783         split = line.split();
784         if len(split) < 2:
785             internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line);
786             continue;
787         (gnupg, keyword) = split[:2];
788         if gnupg != "[GNUPG:]":
789             internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg);
790             continue;
791         args = split[2:];
792         if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
793             internal_error += "found duplicate status token ('%s').\n" % (keyword);
794             continue;
795         else:
796             keywords[keyword] = args;
797
798     # If we failed to parse the status-fd output, let's just whine and bail now
799     if internal_error:
800         reject("internal error while performing signature check on %s." % (filename));
801         reject(internal_error, "");
802         reject("Please report the above errors to the Archive maintainers by replying to this mail.", "");
803         return None;
804
805     # Now check for obviously bad things in the processed output
806     if keywords.has_key("SIGEXPIRED"):
807         reject("key used to sign %s has expired." % (filename));
808         bad = 1;
809     if keywords.has_key("KEYREVOKED"):
810         reject("key used to sign %s has been revoked." % (filename));
811         bad = 1;
812     if keywords.has_key("BADSIG"):
813         reject("bad signature on %s." % (filename));
814         bad = 1;
815     if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
816         reject("failed to check signature on %s." % (filename));
817         bad = 1;
818     if keywords.has_key("NO_PUBKEY"):
819         reject("key used to sign %s not found in keyring." % (filename));
820         bad = 1;
821     if keywords.has_key("BADARMOR"):
822         reject("ascii armour of signature was corrupt in %s." % (filename));
823         bad = 1;
824     if keywords.has_key("NODATA"):
825         reject("no signature found in %s." % (filename));
826         bad = 1;
827
828     if bad:
829         return None;
830
831     # Next check gpgv exited with a zero return code
832     if exit_status:
833         reject("gpgv failed while checking %s." % (filename));
834         if status.strip():
835             reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "");
836         else:
837             reject(prefix_multi_line_string(output, " [GPG output:] "), "");
838         return None;
839
840     # Sanity check the good stuff we expect
841     if not keywords.has_key("VALIDSIG"):
842         reject("signature on %s does not appear to be valid [No VALIDSIG]." % (filename));
843         bad = 1;
844     else:
845         args = keywords["VALIDSIG"];
846         if len(args) < 1:
847             reject("internal error while checking signature on %s." % (filename));
848             bad = 1;
849         else:
850             fingerprint = args[0];
851     if not keywords.has_key("GOODSIG"):
852         reject("signature on %s does not appear to be valid [No GOODSIG]." % (filename));
853         bad = 1;
854     if not keywords.has_key("SIG_ID"):
855         reject("signature on %s does not appear to be valid [No SIG_ID]." % (filename));
856         bad = 1;
857
858     # Finally ensure there's not something we don't recognise
859     known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
860                           SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
861                           NODATA="");
862
863     for keyword in keywords.keys():
864         if not known_keywords.has_key(keyword):
865             reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], filename));
866             bad = 1;
867
868     if bad:
869         return None;
870     else:
871         return fingerprint;
872
873 ################################################################################
874
875 apt_pkg.init()
876
877 Cnf = apt_pkg.newConfiguration();
878 apt_pkg.ReadConfigFileISC(Cnf,default_config);
879
880 if which_conf_file() != default_config:
881         apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
882
883 ################################################################################