]> git.decadent.org.uk Git - dak.git/blob - daklib/utils.py
Merge remote branch 'mario/master' into merge
[dak.git] / daklib / utils.py
1 #!/usr/bin/env python
2 # vim:set et ts=4 sw=4:
3
4 """Utility functions
5
6 @contact: Debian FTP Master <ftpmaster@debian.org>
7 @copyright: 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
8 @license: GNU General Public License version 2 or later
9 """
10
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
15
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
25 import commands
26 import email.Header
27 import os
28 import pwd
29 import select
30 import socket
31 import shutil
32 import sys
33 import tempfile
34 import traceback
35 import stat
36 import apt_pkg
37 import time
38 import re
39 import email as modemail
40 import subprocess
41
42 from dbconn import DBConn, get_architecture, get_component, get_suite
43 from dak_exceptions import *
44 from textutils import fix_maintainer
45 from regexes import re_html_escaping, html_escaping, re_single_line_field, \
46                     re_multi_line_field, re_srchasver, re_taint_free, \
47                     re_gpg_uid, re_re_mark, re_whitespace_comment, re_issource, \
48                     re_is_orig_source
49
50 from formats import parse_format, validate_changes_format
51 from srcformats import get_format_from_string
52 from collections import defaultdict
53
54 ################################################################################
55
56 default_config = "/etc/dak/dak.conf"     #: default dak config, defines host properties
57 default_apt_config = "/etc/dak/apt.conf" #: default apt config, not normally used
58
59 alias_cache = None        #: Cache for email alias checks
60 key_uid_email_cache = {}  #: Cache for email addresses from gpg key uids
61
62 # (hashname, function, earliest_changes_version)
63 known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
64                 ("sha256", apt_pkg.sha256sum, (1, 8))] #: hashes we accept for entries in .changes/.dsc
65
66 # Monkeypatch commands.getstatusoutput as it may not return the correct exit
67 # code in lenny's Python. This also affects commands.getoutput and
68 # commands.getstatus.
69 def dak_getstatusoutput(cmd):
70     pipe = subprocess.Popen(cmd, shell=True, universal_newlines=True,
71         stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
72
73     output = "".join(pipe.stdout.readlines())
74
75     if output[-1:] == '\n':
76         output = output[:-1]
77
78     ret = pipe.wait()
79     if ret is None:
80         ret = 0
81
82     return ret, output
83 commands.getstatusoutput = dak_getstatusoutput
84
85 ################################################################################
86
87 def html_escape(s):
88     """ Escape html chars """
89     return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)
90
91 ################################################################################
92
93 def open_file(filename, mode='r'):
94     """
95     Open C{file}, return fileobject.
96
97     @type filename: string
98     @param filename: path/filename to open
99
100     @type mode: string
101     @param mode: open mode
102
103     @rtype: fileobject
104     @return: open fileobject
105
106     @raise CantOpenError: If IOError is raised by open, reraise it as CantOpenError.
107
108     """
109     try:
110         f = open(filename, mode)
111     except IOError:
112         raise CantOpenError, filename
113     return f
114
115 ################################################################################
116
117 def our_raw_input(prompt=""):
118     if prompt:
119         while 1:
120             try:
121                 sys.stdout.write(prompt)
122                 break
123             except IOError:
124                 pass
125     sys.stdout.flush()
126     try:
127         ret = raw_input()
128         return ret
129     except EOFError:
130         sys.stderr.write("\nUser interrupt (^D).\n")
131         raise SystemExit
132
133 ################################################################################
134
135 def extract_component_from_section(section):
136     component = ""
137
138     if section.find('/') != -1:
139         component = section.split('/')[0]
140
141     # Expand default component
142     if component == "":
143         if Cnf.has_key("Component::%s" % section):
144             component = section
145         else:
146             component = "main"
147
148     return (section, component)
149
150 ################################################################################
151
152 def parse_deb822(contents, signing_rules=0):
153     error = ""
154     changes = {}
155
156     # Split the lines in the input, keeping the linebreaks.
157     lines = contents.splitlines(True)
158
159     if len(lines) == 0:
160         raise ParseChangesError, "[Empty changes file]"
161
162     # Reindex by line number so we can easily verify the format of
163     # .dsc files...
164     index = 0
165     indexed_lines = {}
166     for line in lines:
167         index += 1
168         indexed_lines[index] = line[:-1]
169
170     inside_signature = 0
171
172     num_of_lines = len(indexed_lines.keys())
173     index = 0
174     first = -1
175     while index < num_of_lines:
176         index += 1
177         line = indexed_lines[index]
178         if line == "":
179             if signing_rules == 1:
180                 index += 1
181                 if index > num_of_lines:
182                     raise InvalidDscError, index
183                 line = indexed_lines[index]
184                 if not line.startswith("-----BEGIN PGP SIGNATURE"):
185                     raise InvalidDscError, index
186                 inside_signature = 0
187                 break
188             else:
189                 continue
190         if line.startswith("-----BEGIN PGP SIGNATURE"):
191             break
192         if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
193             inside_signature = 1
194             if signing_rules == 1:
195                 while index < num_of_lines and line != "":
196                     index += 1
197                     line = indexed_lines[index]
198             continue
199         # If we're not inside the signed data, don't process anything
200         if signing_rules >= 0 and not inside_signature:
201             continue
202         slf = re_single_line_field.match(line)
203         if slf:
204             field = slf.groups()[0].lower()
205             changes[field] = slf.groups()[1]
206             first = 1
207             continue
208         if line == " .":
209             changes[field] += '\n'
210             continue
211         mlf = re_multi_line_field.match(line)
212         if mlf:
213             if first == -1:
214                 raise ParseChangesError, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
215             if first == 1 and changes[field] != "":
216                 changes[field] += '\n'
217             first = 0
218             changes[field] += mlf.groups()[0] + '\n'
219             continue
220         error += line
221
222     if signing_rules == 1 and inside_signature:
223         raise InvalidDscError, index
224
225     changes["filecontents"] = "".join(lines)
226
227     if changes.has_key("source"):
228         # Strip the source version in brackets from the source field,
229         # put it in the "source-version" field instead.
230         srcver = re_srchasver.search(changes["source"])
231         if srcver:
232             changes["source"] = srcver.group(1)
233             changes["source-version"] = srcver.group(2)
234
235     if error:
236         raise ParseChangesError, error
237
238     return changes
239
240 ################################################################################
241
242 def parse_changes(filename, signing_rules=0, dsc_file=0):
243     """
244     Parses a changes file and returns a dictionary where each field is a
245     key.  The mandatory first argument is the filename of the .changes
246     file.
247
248     signing_rules is an optional argument:
249
250       - If signing_rules == -1, no signature is required.
251       - If signing_rules == 0 (the default), a signature is required.
252       - If signing_rules == 1, it turns on the same strict format checking
253         as dpkg-source.
254
255     The rules for (signing_rules == 1)-mode are:
256
257       - The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
258         followed by any PGP header data and must end with a blank line.
259
260       - The data section must end with a blank line and must be followed by
261         "-----BEGIN PGP SIGNATURE-----".
262     """
263
264     changes_in = open_file(filename)
265     content = changes_in.read()
266     changes_in.close()
267     try:
268         unicode(content, 'utf-8')
269     except UnicodeError:
270         raise ChangesUnicodeError, "Changes file not proper utf-8"
271     changes = parse_deb822(content, signing_rules)
272
273
274     if not dsc_file:
275         # Finally ensure that everything needed for .changes is there
276         must_keywords = ('Format', 'Date', 'Source', 'Binary', 'Architecture', 'Version',
277                          'Distribution', 'Maintainer', 'Description', 'Changes', 'Files')
278
279         missingfields=[]
280         for keyword in must_keywords:
281             if not changes.has_key(keyword.lower()):
282                 missingfields.append(keyword)
283
284                 if len(missingfields):
285                     raise ParseChangesError, "Missing mandantory field(s) in changes file (policy 5.5): %s" % (missingfields)
286
287     return changes
288
289 ################################################################################
290
291 def hash_key(hashname):
292     return '%ssum' % hashname
293
294 ################################################################################
295
296 def create_hash(where, files, hashname, hashfunc):
297     """
298     create_hash extends the passed files dict with the given hash by
299     iterating over all files on disk and passing them to the hashing
300     function given.
301     """
302
303     rejmsg = []
304     for f in files.keys():
305         try:
306             file_handle = open_file(f)
307         except CantOpenError:
308             rejmsg.append("Could not open file %s for checksumming" % (f))
309             continue
310
311         files[f][hash_key(hashname)] = hashfunc(file_handle)
312
313         file_handle.close()
314     return rejmsg
315
316 ################################################################################
317
318 def check_hash(where, files, hashname, hashfunc):
319     """
320     check_hash checks the given hash in the files dict against the actual
321     files on disk.  The hash values need to be present consistently in
322     all file entries.  It does not modify its input in any way.
323     """
324
325     rejmsg = []
326     for f in files.keys():
327         file_handle = None
328         try:
329             try:
330                 file_handle = open_file(f)
331
332                 # Check for the hash entry, to not trigger a KeyError.
333                 if not files[f].has_key(hash_key(hashname)):
334                     rejmsg.append("%s: misses %s checksum in %s" % (f, hashname,
335                         where))
336                     continue
337
338                 # Actually check the hash for correctness.
339                 if hashfunc(file_handle) != files[f][hash_key(hashname)]:
340                     rejmsg.append("%s: %s check failed in %s" % (f, hashname,
341                         where))
342             except CantOpenError:
343                 # TODO: This happens when the file is in the pool.
344                 # warn("Cannot open file %s" % f)
345                 continue
346         finally:
347             if file_handle:
348                 file_handle.close()
349     return rejmsg
350
351 ################################################################################
352
353 def check_size(where, files):
354     """
355     check_size checks the file sizes in the passed files dict against the
356     files on disk.
357     """
358
359     rejmsg = []
360     for f in files.keys():
361         try:
362             entry = os.stat(f)
363         except OSError, exc:
364             if exc.errno == 2:
365                 # TODO: This happens when the file is in the pool.
366                 continue
367             raise
368
369         actual_size = entry[stat.ST_SIZE]
370         size = int(files[f]["size"])
371         if size != actual_size:
372             rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
373                    % (f, actual_size, size, where))
374     return rejmsg
375
376 ################################################################################
377
378 def check_dsc_files(dsc_filename, dsc=None, dsc_files=None):
379     """
380     Verify that the files listed in the Files field of the .dsc are
381     those expected given the announced Format.
382
383     @type dsc_filename: string
384     @param dsc_filename: path of .dsc file
385
386     @type dsc: dict
387     @param dsc: the content of the .dsc parsed by C{parse_changes()}
388
389     @type dsc_files: dict
390     @param dsc_files: the file list returned by C{build_file_list()}
391
392     @rtype: list
393     @return: all errors detected
394     """
395     rejmsg = []
396
397     # Parse the file if needed
398     if dsc is None:
399         dsc = parse_changes(dsc_filename, signing_rules=1, dsc_file=1);
400
401     if dsc_files is None:
402         dsc_files = build_file_list(dsc, is_a_dsc=1)
403
404     # Ensure .dsc lists proper set of source files according to the format
405     # announced
406     has = defaultdict(lambda: 0)
407
408     ftype_lookup = (
409         (r'orig.tar.gz',               ('orig_tar_gz', 'orig_tar')),
410         (r'diff.gz',                   ('debian_diff',)),
411         (r'tar.gz',                    ('native_tar_gz', 'native_tar')),
412         (r'debian\.tar\.(gz|bz2)',     ('debian_tar',)),
413         (r'orig\.tar\.(gz|bz2)',       ('orig_tar',)),
414         (r'tar\.(gz|bz2)',             ('native_tar',)),
415         (r'orig-.+\.tar\.(gz|bz2)',    ('more_orig_tar',)),
416     )
417
418     for f in dsc_files.keys():
419         m = re_issource.match(f)
420         if not m:
421             rejmsg.append("%s: %s in Files field not recognised as source."
422                           % (dsc_filename, f))
423             continue
424
425         # Populate 'has' dictionary by resolving keys in lookup table
426         matched = False
427         for regex, keys in ftype_lookup:
428             if re.match(regex, m.group(3)):
429                 matched = True
430                 for key in keys:
431                     has[key] += 1
432                 break
433
434         # File does not match anything in lookup table; reject
435         if not matched:
436             reject("%s: unexpected source file '%s'" % (dsc_filename, f))
437
438     # Check for multiple files
439     for file_type in ('orig_tar', 'native_tar', 'debian_tar', 'debian_diff'):
440         if has[file_type] > 1:
441             rejmsg.append("%s: lists multiple %s" % (dsc_filename, file_type))
442
443     # Source format specific tests
444     try:
445         format = get_format_from_string(dsc['format'])
446         rejmsg.extend([
447             '%s: %s' % (dsc_filename, x) for x in format.reject_msgs(has)
448         ])
449
450     except UnknownFormatError:
451         # Not an error here for now
452         pass
453
454     return rejmsg
455
456 ################################################################################
457
458 def check_hash_fields(what, manifest):
459     """
460     check_hash_fields ensures that there are no checksum fields in the
461     given dict that we do not know about.
462     """
463
464     rejmsg = []
465     hashes = map(lambda x: x[0], known_hashes)
466     for field in manifest:
467         if field.startswith("checksums-"):
468             hashname = field.split("-",1)[1]
469             if hashname not in hashes:
470                 rejmsg.append("Unsupported checksum field for %s "\
471                     "in %s" % (hashname, what))
472     return rejmsg
473
474 ################################################################################
475
476 def _ensure_changes_hash(changes, format, version, files, hashname, hashfunc):
477     if format >= version:
478         # The version should contain the specified hash.
479         func = check_hash
480
481         # Import hashes from the changes
482         rejmsg = parse_checksums(".changes", files, changes, hashname)
483         if len(rejmsg) > 0:
484             return rejmsg
485     else:
486         # We need to calculate the hash because it can't possibly
487         # be in the file.
488         func = create_hash
489     return func(".changes", files, hashname, hashfunc)
490
491 # We could add the orig which might be in the pool to the files dict to
492 # access the checksums easily.
493
494 def _ensure_dsc_hash(dsc, dsc_files, hashname, hashfunc):
495     """
496     ensure_dsc_hashes' task is to ensure that each and every *present* hash
497     in the dsc is correct, i.e. identical to the changes file and if necessary
498     the pool.  The latter task is delegated to check_hash.
499     """
500
501     rejmsg = []
502     if not dsc.has_key('Checksums-%s' % (hashname,)):
503         return rejmsg
504     # Import hashes from the dsc
505     parse_checksums(".dsc", dsc_files, dsc, hashname)
506     # And check it...
507     rejmsg.extend(check_hash(".dsc", dsc_files, hashname, hashfunc))
508     return rejmsg
509
510 ################################################################################
511
512 def parse_checksums(where, files, manifest, hashname):
513     rejmsg = []
514     field = 'checksums-%s' % hashname
515     if not field in manifest:
516         return rejmsg
517     for line in manifest[field].split('\n'):
518         if not line:
519             break
520         clist = line.strip().split(' ')
521         if len(clist) == 3:
522             checksum, size, checkfile = clist
523         else:
524             rejmsg.append("Cannot parse checksum line [%s]" % (line))
525             continue
526         if not files.has_key(checkfile):
527         # TODO: check for the file's entry in the original files dict, not
528         # the one modified by (auto)byhand and other weird stuff
529         #    rejmsg.append("%s: not present in files but in checksums-%s in %s" %
530         #        (file, hashname, where))
531             continue
532         if not files[checkfile]["size"] == size:
533             rejmsg.append("%s: size differs for files and checksums-%s entry "\
534                 "in %s" % (checkfile, hashname, where))
535             continue
536         files[checkfile][hash_key(hashname)] = checksum
537     for f in files.keys():
538         if not files[f].has_key(hash_key(hashname)):
539             rejmsg.append("%s: no entry in checksums-%s in %s" % (checkfile,
540                 hashname, where))
541     return rejmsg
542
543 ################################################################################
544
545 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
546
547 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
548     files = {}
549
550     # Make sure we have a Files: field to parse...
551     if not changes.has_key(field):
552         raise NoFilesFieldError
553
554     # Validate .changes Format: field
555     if not is_a_dsc:
556         validate_changes_format(parse_format(changes['format']), field)
557
558     includes_section = (not is_a_dsc) and field == "files"
559
560     # Parse each entry/line:
561     for i in changes[field].split('\n'):
562         if not i:
563             break
564         s = i.split()
565         section = priority = ""
566         try:
567             if includes_section:
568                 (md5, size, section, priority, name) = s
569             else:
570                 (md5, size, name) = s
571         except ValueError:
572             raise ParseChangesError, i
573
574         if section == "":
575             section = "-"
576         if priority == "":
577             priority = "-"
578
579         (section, component) = extract_component_from_section(section)
580
581         files[name] = dict(size=size, section=section,
582                            priority=priority, component=component)
583         files[name][hashname] = md5
584
585     return files
586
587 ################################################################################
588
589 def send_mail (message, filename=""):
590     """sendmail wrapper, takes _either_ a message string or a file as arguments"""
591
592     # Check whether we're supposed to be sending mail
593     if Cnf.has_key("Dinstall::Options::No-Mail") and Cnf["Dinstall::Options::No-Mail"]:
594         return
595
596     # If we've been passed a string dump it into a temporary file
597     if message:
598         (fd, filename) = tempfile.mkstemp()
599         os.write (fd, message)
600         os.close (fd)
601
602     if Cnf.has_key("Dinstall::MailWhiteList") and \
603            Cnf["Dinstall::MailWhiteList"] != "":
604         message_in = open_file(filename)
605         message_raw = modemail.message_from_file(message_in)
606         message_in.close();
607
608         whitelist = [];
609         whitelist_in = open_file(Cnf["Dinstall::MailWhiteList"])
610         try:
611             for line in whitelist_in:
612                 if not re_whitespace_comment.match(line):
613                     if re_re_mark.match(line):
614                         whitelist.append(re.compile(re_re_mark.sub("", line.strip(), 1)))
615                     else:
616                         whitelist.append(re.compile(re.escape(line.strip())))
617         finally:
618             whitelist_in.close()
619
620         # Fields to check.
621         fields = ["To", "Bcc", "Cc"]
622         for field in fields:
623             # Check each field
624             value = message_raw.get(field, None)
625             if value != None:
626                 match = [];
627                 for item in value.split(","):
628                     (rfc822_maint, rfc2047_maint, name, email) = fix_maintainer(item.strip())
629                     mail_whitelisted = 0
630                     for wr in whitelist:
631                         if wr.match(email):
632                             mail_whitelisted = 1
633                             break
634                     if not mail_whitelisted:
635                         print "Skipping %s since it's not in %s" % (item, Cnf["Dinstall::MailWhiteList"])
636                         continue
637                     match.append(item)
638
639                 # Doesn't have any mail in whitelist so remove the header
640                 if len(match) == 0:
641                     del message_raw[field]
642                 else:
643                     message_raw.replace_header(field, ', '.join(match))
644
645         # Change message fields in order if we don't have a To header
646         if not message_raw.has_key("To"):
647             fields.reverse()
648             for field in fields:
649                 if message_raw.has_key(field):
650                     message_raw[fields[-1]] = message_raw[field]
651                     del message_raw[field]
652                     break
653             else:
654                 # Clean up any temporary files
655                 # and return, as we removed all recipients.
656                 if message:
657                     os.unlink (filename);
658                 return;
659
660         fd = os.open(filename, os.O_RDWR|os.O_EXCL, 0700);
661         os.write (fd, message_raw.as_string(True));
662         os.close (fd);
663
664     # Invoke sendmail
665     (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
666     if (result != 0):
667         raise SendmailFailedError, output
668
669     # Clean up any temporary files
670     if message:
671         os.unlink (filename)
672
673 ################################################################################
674
675 def poolify (source, component):
676     if component:
677         component += '/'
678     if source[:3] == "lib":
679         return component + source[:4] + '/' + source + '/'
680     else:
681         return component + source[:1] + '/' + source + '/'
682
683 ################################################################################
684
685 def move (src, dest, overwrite = 0, perms = 0664):
686     if os.path.exists(dest) and os.path.isdir(dest):
687         dest_dir = dest
688     else:
689         dest_dir = os.path.dirname(dest)
690     if not os.path.exists(dest_dir):
691         umask = os.umask(00000)
692         os.makedirs(dest_dir, 02775)
693         os.umask(umask)
694     #print "Moving %s to %s..." % (src, dest)
695     if os.path.exists(dest) and os.path.isdir(dest):
696         dest += '/' + os.path.basename(src)
697     # Don't overwrite unless forced to
698     if os.path.exists(dest):
699         if not overwrite:
700             fubar("Can't move %s to %s - file already exists." % (src, dest))
701         else:
702             if not os.access(dest, os.W_OK):
703                 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
704     shutil.copy2(src, dest)
705     os.chmod(dest, perms)
706     os.unlink(src)
707
708 def copy (src, dest, overwrite = 0, perms = 0664):
709     if os.path.exists(dest) and os.path.isdir(dest):
710         dest_dir = dest
711     else:
712         dest_dir = os.path.dirname(dest)
713     if not os.path.exists(dest_dir):
714         umask = os.umask(00000)
715         os.makedirs(dest_dir, 02775)
716         os.umask(umask)
717     #print "Copying %s to %s..." % (src, dest)
718     if os.path.exists(dest) and os.path.isdir(dest):
719         dest += '/' + os.path.basename(src)
720     # Don't overwrite unless forced to
721     if os.path.exists(dest):
722         if not overwrite:
723             raise FileExistsError
724         else:
725             if not os.access(dest, os.W_OK):
726                 raise CantOverwriteError
727     shutil.copy2(src, dest)
728     os.chmod(dest, perms)
729
730 ################################################################################
731
732 def where_am_i ():
733     res = socket.getfqdn()
734     database_hostname = Cnf.get("Config::" + res + "::DatabaseHostname")
735     if database_hostname:
736         return database_hostname
737     else:
738         return res
739
740 def which_conf_file ():
741     if os.getenv('DAK_CONFIG'):
742         return os.getenv('DAK_CONFIG')
743
744     res = socket.getfqdn()
745     # In case we allow local config files per user, try if one exists
746     if Cnf.FindB("Config::" + res + "::AllowLocalConfig"):
747         homedir = os.getenv("HOME")
748         confpath = os.path.join(homedir, "/etc/dak.conf")
749         if os.path.exists(confpath):
750             apt_pkg.ReadConfigFileISC(Cnf,default_config)
751
752     # We are still in here, so there is no local config file or we do
753     # not allow local files. Do the normal stuff.
754     if Cnf.get("Config::" + res + "::DakConfig"):
755         return Cnf["Config::" + res + "::DakConfig"]
756
757     return default_config
758
759 def which_apt_conf_file ():
760     res = socket.getfqdn()
761     # In case we allow local config files per user, try if one exists
762     if Cnf.FindB("Config::" + res + "::AllowLocalConfig"):
763         homedir = os.getenv("HOME")
764         confpath = os.path.join(homedir, "/etc/dak.conf")
765         if os.path.exists(confpath):
766             apt_pkg.ReadConfigFileISC(Cnf,default_config)
767
768     if Cnf.get("Config::" + res + "::AptConfig"):
769         return Cnf["Config::" + res + "::AptConfig"]
770     else:
771         return default_apt_config
772
773 def which_alias_file():
774     hostname = socket.getfqdn()
775     aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
776     if os.path.exists(aliasfn):
777         return aliasfn
778     else:
779         return None
780
781 ################################################################################
782
783 def TemplateSubst(subst_map, filename):
784     """ Perform a substition of template """
785     templatefile = open_file(filename)
786     template = templatefile.read()
787     for k, v in subst_map.iteritems():
788         template = template.replace(k, str(v))
789     templatefile.close()
790     return template
791
792 ################################################################################
793
794 def fubar(msg, exit_code=1):
795     sys.stderr.write("E: %s\n" % (msg))
796     sys.exit(exit_code)
797
798 def warn(msg):
799     sys.stderr.write("W: %s\n" % (msg))
800
801 ################################################################################
802
803 # Returns the user name with a laughable attempt at rfc822 conformancy
804 # (read: removing stray periods).
805 def whoami ():
806     return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
807
808 def getusername ():
809     return pwd.getpwuid(os.getuid())[0]
810
811 ################################################################################
812
813 def size_type (c):
814     t  = " B"
815     if c > 10240:
816         c = c / 1024
817         t = " KB"
818     if c > 10240:
819         c = c / 1024
820         t = " MB"
821     return ("%d%s" % (c, t))
822
823 ################################################################################
824
825 def cc_fix_changes (changes):
826     o = changes.get("architecture", "")
827     if o:
828         del changes["architecture"]
829     changes["architecture"] = {}
830     for j in o.split():
831         changes["architecture"][j] = 1
832
833 def changes_compare (a, b):
834     """ Sort by source name, source version, 'have source', and then by filename """
835     try:
836         a_changes = parse_changes(a)
837     except:
838         return -1
839
840     try:
841         b_changes = parse_changes(b)
842     except:
843         return 1
844
845     cc_fix_changes (a_changes)
846     cc_fix_changes (b_changes)
847
848     # Sort by source name
849     a_source = a_changes.get("source")
850     b_source = b_changes.get("source")
851     q = cmp (a_source, b_source)
852     if q:
853         return q
854
855     # Sort by source version
856     a_version = a_changes.get("version", "0")
857     b_version = b_changes.get("version", "0")
858     q = apt_pkg.VersionCompare(a_version, b_version)
859     if q:
860         return q
861
862     # Sort by 'have source'
863     a_has_source = a_changes["architecture"].get("source")
864     b_has_source = b_changes["architecture"].get("source")
865     if a_has_source and not b_has_source:
866         return -1
867     elif b_has_source and not a_has_source:
868         return 1
869
870     # Fall back to sort by filename
871     return cmp(a, b)
872
873 ################################################################################
874
875 def find_next_free (dest, too_many=100):
876     extra = 0
877     orig_dest = dest
878     while os.path.exists(dest) and extra < too_many:
879         dest = orig_dest + '.' + repr(extra)
880         extra += 1
881     if extra >= too_many:
882         raise NoFreeFilenameError
883     return dest
884
885 ################################################################################
886
887 def result_join (original, sep = '\t'):
888     resultlist = []
889     for i in xrange(len(original)):
890         if original[i] == None:
891             resultlist.append("")
892         else:
893             resultlist.append(original[i])
894     return sep.join(resultlist)
895
896 ################################################################################
897
898 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
899     out = ""
900     for line in str.split('\n'):
901         line = line.strip()
902         if line or include_blank_lines:
903             out += "%s%s\n" % (prefix, line)
904     # Strip trailing new line
905     if out:
906         out = out[:-1]
907     return out
908
909 ################################################################################
910
911 def validate_changes_file_arg(filename, require_changes=1):
912     """
913     'filename' is either a .changes or .dak file.  If 'filename' is a
914     .dak file, it's changed to be the corresponding .changes file.  The
915     function then checks if the .changes file a) exists and b) is
916     readable and returns the .changes filename if so.  If there's a
917     problem, the next action depends on the option 'require_changes'
918     argument:
919
920       - If 'require_changes' == -1, errors are ignored and the .changes
921         filename is returned.
922       - If 'require_changes' == 0, a warning is given and 'None' is returned.
923       - If 'require_changes' == 1, a fatal error is raised.
924
925     """
926     error = None
927
928     orig_filename = filename
929     if filename.endswith(".dak"):
930         filename = filename[:-4]+".changes"
931
932     if not filename.endswith(".changes"):
933         error = "invalid file type; not a changes file"
934     else:
935         if not os.access(filename,os.R_OK):
936             if os.path.exists(filename):
937                 error = "permission denied"
938             else:
939                 error = "file not found"
940
941     if error:
942         if require_changes == 1:
943             fubar("%s: %s." % (orig_filename, error))
944         elif require_changes == 0:
945             warn("Skipping %s - %s" % (orig_filename, error))
946             return None
947         else: # We only care about the .dak file
948             return filename
949     else:
950         return filename
951
952 ################################################################################
953
954 def real_arch(arch):
955     return (arch != "source" and arch != "all")
956
957 ################################################################################
958
959 def join_with_commas_and(list):
960     if len(list) == 0: return "nothing"
961     if len(list) == 1: return list[0]
962     return ", ".join(list[:-1]) + " and " + list[-1]
963
964 ################################################################################
965
966 def pp_deps (deps):
967     pp_deps = []
968     for atom in deps:
969         (pkg, version, constraint) = atom
970         if constraint:
971             pp_dep = "%s (%s %s)" % (pkg, constraint, version)
972         else:
973             pp_dep = pkg
974         pp_deps.append(pp_dep)
975     return " |".join(pp_deps)
976
977 ################################################################################
978
979 def get_conf():
980     return Cnf
981
982 ################################################################################
983
984 def parse_args(Options):
985     """ Handle -a, -c and -s arguments; returns them as SQL constraints """
986     # XXX: This should go away and everything which calls it be converted
987     #      to use SQLA properly.  For now, we'll just fix it not to use
988     #      the old Pg interface though
989     session = DBConn().session()
990     # Process suite
991     if Options["Suite"]:
992         suite_ids_list = []
993         for suitename in split_args(Options["Suite"]):
994             suite = get_suite(suitename, session=session)
995             if suite.suite_id is None:
996                 warn("suite '%s' not recognised." % (suite.suite_name))
997             else:
998                 suite_ids_list.append(suite.suite_id)
999         if suite_ids_list:
1000             con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
1001         else:
1002             fubar("No valid suite given.")
1003     else:
1004         con_suites = ""
1005
1006     # Process component
1007     if Options["Component"]:
1008         component_ids_list = []
1009         for componentname in split_args(Options["Component"]):
1010             component = get_component(componentname, session=session)
1011             if component is None:
1012                 warn("component '%s' not recognised." % (componentname))
1013             else:
1014                 component_ids_list.append(component.component_id)
1015         if component_ids_list:
1016             con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
1017         else:
1018             fubar("No valid component given.")
1019     else:
1020         con_components = ""
1021
1022     # Process architecture
1023     con_architectures = ""
1024     check_source = 0
1025     if Options["Architecture"]:
1026         arch_ids_list = []
1027         for archname in split_args(Options["Architecture"]):
1028             if archname == "source":
1029                 check_source = 1
1030             else:
1031                 arch = get_architecture(archname, session=session)
1032                 if arch is None:
1033                     warn("architecture '%s' not recognised." % (archname))
1034                 else:
1035                     arch_ids_list.append(arch.arch_id)
1036         if arch_ids_list:
1037             con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
1038         else:
1039             if not check_source:
1040                 fubar("No valid architecture given.")
1041     else:
1042         check_source = 1
1043
1044     return (con_suites, con_architectures, con_components, check_source)
1045
1046 ################################################################################
1047
1048 # Inspired(tm) by Bryn Keller's print_exc_plus (See
1049 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
1050
1051 def print_exc():
1052     tb = sys.exc_info()[2]
1053     while tb.tb_next:
1054         tb = tb.tb_next
1055     stack = []
1056     frame = tb.tb_frame
1057     while frame:
1058         stack.append(frame)
1059         frame = frame.f_back
1060     stack.reverse()
1061     traceback.print_exc()
1062     for frame in stack:
1063         print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
1064                                              frame.f_code.co_filename,
1065                                              frame.f_lineno)
1066         for key, value in frame.f_locals.items():
1067             print "\t%20s = " % key,
1068             try:
1069                 print value
1070             except:
1071                 print "<unable to print>"
1072
1073 ################################################################################
1074
1075 def try_with_debug(function):
1076     try:
1077         function()
1078     except SystemExit:
1079         raise
1080     except:
1081         print_exc()
1082
1083 ################################################################################
1084
1085 def arch_compare_sw (a, b):
1086     """
1087     Function for use in sorting lists of architectures.
1088
1089     Sorts normally except that 'source' dominates all others.
1090     """
1091
1092     if a == "source" and b == "source":
1093         return 0
1094     elif a == "source":
1095         return -1
1096     elif b == "source":
1097         return 1
1098
1099     return cmp (a, b)
1100
1101 ################################################################################
1102
1103 def split_args (s, dwim=1):
1104     """
1105     Split command line arguments which can be separated by either commas
1106     or whitespace.  If dwim is set, it will complain about string ending
1107     in comma since this usually means someone did 'dak ls -a i386, m68k
1108     foo' or something and the inevitable confusion resulting from 'm68k'
1109     being treated as an argument is undesirable.
1110     """
1111
1112     if s.find(",") == -1:
1113         return s.split()
1114     else:
1115         if s[-1:] == "," and dwim:
1116             fubar("split_args: found trailing comma, spurious space maybe?")
1117         return s.split(",")
1118
1119 ################################################################################
1120
1121 def gpgv_get_status_output(cmd, status_read, status_write):
1122     """
1123     Our very own version of commands.getouputstatus(), hacked to support
1124     gpgv's status fd.
1125     """
1126
1127     cmd = ['/bin/sh', '-c', cmd]
1128     p2cread, p2cwrite = os.pipe()
1129     c2pread, c2pwrite = os.pipe()
1130     errout, errin = os.pipe()
1131     pid = os.fork()
1132     if pid == 0:
1133         # Child
1134         os.close(0)
1135         os.close(1)
1136         os.dup(p2cread)
1137         os.dup(c2pwrite)
1138         os.close(2)
1139         os.dup(errin)
1140         for i in range(3, 256):
1141             if i != status_write:
1142                 try:
1143                     os.close(i)
1144                 except:
1145                     pass
1146         try:
1147             os.execvp(cmd[0], cmd)
1148         finally:
1149             os._exit(1)
1150
1151     # Parent
1152     os.close(p2cread)
1153     os.dup2(c2pread, c2pwrite)
1154     os.dup2(errout, errin)
1155
1156     output = status = ""
1157     while 1:
1158         i, o, e = select.select([c2pwrite, errin, status_read], [], [])
1159         more_data = []
1160         for fd in i:
1161             r = os.read(fd, 8196)
1162             if len(r) > 0:
1163                 more_data.append(fd)
1164                 if fd == c2pwrite or fd == errin:
1165                     output += r
1166                 elif fd == status_read:
1167                     status += r
1168                 else:
1169                     fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
1170         if not more_data:
1171             pid, exit_status = os.waitpid(pid, 0)
1172             try:
1173                 os.close(status_write)
1174                 os.close(status_read)
1175                 os.close(c2pread)
1176                 os.close(c2pwrite)
1177                 os.close(p2cwrite)
1178                 os.close(errin)
1179                 os.close(errout)
1180             except:
1181                 pass
1182             break
1183
1184     return output, status, exit_status
1185
1186 ################################################################################
1187
1188 def process_gpgv_output(status):
1189     # Process the status-fd output
1190     keywords = {}
1191     internal_error = ""
1192     for line in status.split('\n'):
1193         line = line.strip()
1194         if line == "":
1195             continue
1196         split = line.split()
1197         if len(split) < 2:
1198             internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
1199             continue
1200         (gnupg, keyword) = split[:2]
1201         if gnupg != "[GNUPG:]":
1202             internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
1203             continue
1204         args = split[2:]
1205         if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
1206             internal_error += "found duplicate status token ('%s').\n" % (keyword)
1207             continue
1208         else:
1209             keywords[keyword] = args
1210
1211     return (keywords, internal_error)
1212
1213 ################################################################################
1214
1215 def retrieve_key (filename, keyserver=None, keyring=None):
1216     """
1217     Retrieve the key that signed 'filename' from 'keyserver' and
1218     add it to 'keyring'.  Returns nothing on success, or an error message
1219     on error.
1220     """
1221
1222     # Defaults for keyserver and keyring
1223     if not keyserver:
1224         keyserver = Cnf["Dinstall::KeyServer"]
1225     if not keyring:
1226         keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
1227
1228     # Ensure the filename contains no shell meta-characters or other badness
1229     if not re_taint_free.match(filename):
1230         return "%s: tainted filename" % (filename)
1231
1232     # Invoke gpgv on the file
1233     status_read, status_write = os.pipe()
1234     cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
1235     (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
1236
1237     # Process the status-fd output
1238     (keywords, internal_error) = process_gpgv_output(status)
1239     if internal_error:
1240         return internal_error
1241
1242     if not keywords.has_key("NO_PUBKEY"):
1243         return "didn't find expected NO_PUBKEY in gpgv status-fd output"
1244
1245     fingerprint = keywords["NO_PUBKEY"][0]
1246     # XXX - gpg sucks.  You can't use --secret-keyring=/dev/null as
1247     # it'll try to create a lockfile in /dev.  A better solution might
1248     # be a tempfile or something.
1249     cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
1250           % (Cnf["Dinstall::SigningKeyring"])
1251     cmd += " --keyring %s --keyserver %s --recv-key %s" \
1252            % (keyring, keyserver, fingerprint)
1253     (result, output) = commands.getstatusoutput(cmd)
1254     if (result != 0):
1255         return "'%s' failed with exit code %s" % (cmd, result)
1256
1257     return ""
1258
1259 ################################################################################
1260
1261 def gpg_keyring_args(keyrings=None):
1262     if not keyrings:
1263         keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1264
1265     return " ".join(["--keyring %s" % x for x in keyrings])
1266
1267 ################################################################################
1268
1269 def check_signature (sig_filename, data_filename="", keyrings=None, autofetch=None):
1270     """
1271     Check the signature of a file and return the fingerprint if the
1272     signature is valid or 'None' if it's not.  The first argument is the
1273     filename whose signature should be checked.  The second argument is a
1274     reject function and is called when an error is found.  The reject()
1275     function must allow for two arguments: the first is the error message,
1276     the second is an optional prefix string.  It's possible for reject()
1277     to be called more than once during an invocation of check_signature().
1278     The third argument is optional and is the name of the files the
1279     detached signature applies to.  The fourth argument is optional and is
1280     a *list* of keyrings to use.  'autofetch' can either be None, True or
1281     False.  If None, the default behaviour specified in the config will be
1282     used.
1283     """
1284
1285     rejects = []
1286
1287     # Ensure the filename contains no shell meta-characters or other badness
1288     if not re_taint_free.match(sig_filename):
1289         rejects.append("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
1290         return (None, rejects)
1291
1292     if data_filename and not re_taint_free.match(data_filename):
1293         rejects.append("!!WARNING!! tainted data filename: '%s'." % (data_filename))
1294         return (None, rejects)
1295
1296     if not keyrings:
1297         keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1298
1299     # Autofetch the signing key if that's enabled
1300     if autofetch == None:
1301         autofetch = Cnf.get("Dinstall::KeyAutoFetch")
1302     if autofetch:
1303         error_msg = retrieve_key(sig_filename)
1304         if error_msg:
1305             rejects.append(error_msg)
1306             return (None, rejects)
1307
1308     # Build the command line
1309     status_read, status_write = os.pipe()
1310     cmd = "gpgv --status-fd %s %s %s %s" % (
1311         status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
1312
1313     # Invoke gpgv on the file
1314     (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1315
1316     # Process the status-fd output
1317     (keywords, internal_error) = process_gpgv_output(status)
1318
1319     # If we failed to parse the status-fd output, let's just whine and bail now
1320     if internal_error:
1321         rejects.append("internal error while performing signature check on %s." % (sig_filename))
1322         rejects.append(internal_error, "")
1323         rejects.append("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1324         return (None, rejects)
1325
1326     # Now check for obviously bad things in the processed output
1327     if keywords.has_key("KEYREVOKED"):
1328         rejects.append("The key used to sign %s has been revoked." % (sig_filename))
1329     if keywords.has_key("BADSIG"):
1330         rejects.append("bad signature on %s." % (sig_filename))
1331     if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1332         rejects.append("failed to check signature on %s." % (sig_filename))
1333     if keywords.has_key("NO_PUBKEY"):
1334         args = keywords["NO_PUBKEY"]
1335         if len(args) >= 1:
1336             key = args[0]
1337         rejects.append("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1338     if keywords.has_key("BADARMOR"):
1339         rejects.append("ASCII armour of signature was corrupt in %s." % (sig_filename))
1340     if keywords.has_key("NODATA"):
1341         rejects.append("no signature found in %s." % (sig_filename))
1342     if keywords.has_key("EXPKEYSIG"):
1343         args = keywords["EXPKEYSIG"]
1344         if len(args) >= 1:
1345             key = args[0]
1346         rejects.append("Signature made by expired key 0x%s" % (key))
1347     if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1348         args = keywords["KEYEXPIRED"]
1349         expiredate=""
1350         if len(args) >= 1:
1351             timestamp = args[0]
1352             if timestamp.count("T") == 0:
1353                 try:
1354                     expiredate = time.strftime("%Y-%m-%d", time.gmtime(float(timestamp)))
1355                 except ValueError:
1356                     expiredate = "unknown (%s)" % (timestamp)
1357             else:
1358                 expiredate = timestamp
1359         rejects.append("The key used to sign %s has expired on %s" % (sig_filename, expiredate))
1360
1361     if len(rejects) > 0:
1362         return (None, rejects)
1363
1364     # Next check gpgv exited with a zero return code
1365     if exit_status:
1366         rejects.append("gpgv failed while checking %s." % (sig_filename))
1367         if status.strip():
1368             rejects.append(prefix_multi_line_string(status, " [GPG status-fd output:] "))
1369         else:
1370             rejects.append(prefix_multi_line_string(output, " [GPG output:] "))
1371         return (None, rejects)
1372
1373     # Sanity check the good stuff we expect
1374     if not keywords.has_key("VALIDSIG"):
1375         rejects.append("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1376     else:
1377         args = keywords["VALIDSIG"]
1378         if len(args) < 1:
1379             rejects.append("internal error while checking signature on %s." % (sig_filename))
1380         else:
1381             fingerprint = args[0]
1382     if not keywords.has_key("GOODSIG"):
1383         rejects.append("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1384     if not keywords.has_key("SIG_ID"):
1385         rejects.append("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1386
1387     # Finally ensure there's not something we don't recognise
1388     known_keywords = dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1389                           SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1390                           NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="",POLICY_URL="")
1391
1392     for keyword in keywords.keys():
1393         if not known_keywords.has_key(keyword):
1394             rejects.append("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1395
1396     if len(rejects) > 0:
1397         return (None, rejects)
1398     else:
1399         return (fingerprint, [])
1400
1401 ################################################################################
1402
1403 def gpg_get_key_addresses(fingerprint):
1404     """retreive email addresses from gpg key uids for a given fingerprint"""
1405     addresses = key_uid_email_cache.get(fingerprint)
1406     if addresses != None:
1407         return addresses
1408     addresses = set()
1409     cmd = "gpg --no-default-keyring %s --fingerprint %s" \
1410                 % (gpg_keyring_args(), fingerprint)
1411     (result, output) = commands.getstatusoutput(cmd)
1412     if result == 0:
1413         for l in output.split('\n'):
1414             m = re_gpg_uid.match(l)
1415             if m:
1416                 addresses.add(m.group(1))
1417     key_uid_email_cache[fingerprint] = addresses
1418     return addresses
1419
1420 ################################################################################
1421
1422 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1423
1424 def wrap(paragraph, max_length, prefix=""):
1425     line = ""
1426     s = ""
1427     have_started = 0
1428     words = paragraph.split()
1429
1430     for word in words:
1431         word_size = len(word)
1432         if word_size > max_length:
1433             if have_started:
1434                 s += line + '\n' + prefix
1435             s += word + '\n' + prefix
1436         else:
1437             if have_started:
1438                 new_length = len(line) + word_size + 1
1439                 if new_length > max_length:
1440                     s += line + '\n' + prefix
1441                     line = word
1442                 else:
1443                     line += ' ' + word
1444             else:
1445                 line = word
1446         have_started = 1
1447
1448     if have_started:
1449         s += line
1450
1451     return s
1452
1453 ################################################################################
1454
1455 def clean_symlink (src, dest, root):
1456     """
1457     Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1458     Returns fixed 'src'
1459     """
1460     src = src.replace(root, '', 1)
1461     dest = dest.replace(root, '', 1)
1462     dest = os.path.dirname(dest)
1463     new_src = '../' * len(dest.split('/'))
1464     return new_src + src
1465
1466 ################################################################################
1467
1468 def temp_filename(directory=None, prefix="dak", suffix=""):
1469     """
1470     Return a secure and unique filename by pre-creating it.
1471     If 'directory' is non-null, it will be the directory the file is pre-created in.
1472     If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
1473     If 'suffix' is non-null, the filename will end with it.
1474
1475     Returns a pair (fd, name).
1476     """
1477
1478     return tempfile.mkstemp(suffix, prefix, directory)
1479
1480 ################################################################################
1481
1482 def temp_dirname(parent=None, prefix="dak", suffix=""):
1483     """
1484     Return a secure and unique directory by pre-creating it.
1485     If 'parent' is non-null, it will be the directory the directory is pre-created in.
1486     If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
1487     If 'suffix' is non-null, the filename will end with it.
1488
1489     Returns a pathname to the new directory
1490     """
1491
1492     return tempfile.mkdtemp(suffix, prefix, parent)
1493
1494 ################################################################################
1495
1496 def is_email_alias(email):
1497     """ checks if the user part of the email is listed in the alias file """
1498     global alias_cache
1499     if alias_cache == None:
1500         aliasfn = which_alias_file()
1501         alias_cache = set()
1502         if aliasfn:
1503             for l in open(aliasfn):
1504                 alias_cache.add(l.split(':')[0])
1505     uid = email.split('@')[0]
1506     return uid in alias_cache
1507
1508 ################################################################################
1509
1510 def get_changes_files(from_dir):
1511     """
1512     Takes a directory and lists all .changes files in it (as well as chdir'ing
1513     to the directory; this is due to broken behaviour on the part of p-u/p-a
1514     when you're not in the right place)
1515
1516     Returns a list of filenames
1517     """
1518     try:
1519         # Much of the rest of p-u/p-a depends on being in the right place
1520         os.chdir(from_dir)
1521         changes_files = [x for x in os.listdir(from_dir) if x.endswith('.changes')]
1522     except OSError, e:
1523         fubar("Failed to read list from directory %s (%s)" % (from_dir, e))
1524
1525     return changes_files
1526
1527 ################################################################################
1528
1529 apt_pkg.init()
1530
1531 Cnf = apt_pkg.newConfiguration()
1532 if not os.getenv("DAK_TEST"):
1533     apt_pkg.ReadConfigFileISC(Cnf,default_config)
1534
1535 if which_conf_file() != default_config:
1536     apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())