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