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