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