]> git.decadent.org.uk Git - dak.git/blob - daklib/utils.py
override
[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 def getusername ():
824     return pwd.getpwuid(os.getuid())[0]
825
826 ################################################################################
827
828 def size_type (c):
829     t  = " B"
830     if c > 10240:
831         c = c / 1024
832         t = " KB"
833     if c > 10240:
834         c = c / 1024
835         t = " MB"
836     return ("%d%s" % (c, t))
837
838 ################################################################################
839
840 def cc_fix_changes (changes):
841     o = changes.get("architecture", "")
842     if o:
843         del changes["architecture"]
844     changes["architecture"] = {}
845     for j in o.split():
846         changes["architecture"][j] = 1
847
848 def changes_compare (a, b):
849     """ Sort by source name, source version, 'have source', and then by filename """
850     try:
851         a_changes = parse_changes(a)
852     except:
853         return -1
854
855     try:
856         b_changes = parse_changes(b)
857     except:
858         return 1
859
860     cc_fix_changes (a_changes)
861     cc_fix_changes (b_changes)
862
863     # Sort by source name
864     a_source = a_changes.get("source")
865     b_source = b_changes.get("source")
866     q = cmp (a_source, b_source)
867     if q:
868         return q
869
870     # Sort by source version
871     a_version = a_changes.get("version", "0")
872     b_version = b_changes.get("version", "0")
873     q = apt_pkg.VersionCompare(a_version, b_version)
874     if q:
875         return q
876
877     # Sort by 'have source'
878     a_has_source = a_changes["architecture"].get("source")
879     b_has_source = b_changes["architecture"].get("source")
880     if a_has_source and not b_has_source:
881         return -1
882     elif b_has_source and not a_has_source:
883         return 1
884
885     # Fall back to sort by filename
886     return cmp(a, b)
887
888 ################################################################################
889
890 def find_next_free (dest, too_many=100):
891     extra = 0
892     orig_dest = dest
893     while os.path.exists(dest) and extra < too_many:
894         dest = orig_dest + '.' + repr(extra)
895         extra += 1
896     if extra >= too_many:
897         raise NoFreeFilenameError
898     return dest
899
900 ################################################################################
901
902 def result_join (original, sep = '\t'):
903     resultlist = []
904     for i in xrange(len(original)):
905         if original[i] == None:
906             resultlist.append("")
907         else:
908             resultlist.append(original[i])
909     return sep.join(resultlist)
910
911 ################################################################################
912
913 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
914     out = ""
915     for line in str.split('\n'):
916         line = line.strip()
917         if line or include_blank_lines:
918             out += "%s%s\n" % (prefix, line)
919     # Strip trailing new line
920     if out:
921         out = out[:-1]
922     return out
923
924 ################################################################################
925
926 def validate_changes_file_arg(filename, require_changes=1):
927     """
928     'filename' is either a .changes or .dak file.  If 'filename' is a
929     .dak file, it's changed to be the corresponding .changes file.  The
930     function then checks if the .changes file a) exists and b) is
931     readable and returns the .changes filename if so.  If there's a
932     problem, the next action depends on the option 'require_changes'
933     argument:
934
935       - If 'require_changes' == -1, errors are ignored and the .changes
936         filename is returned.
937       - If 'require_changes' == 0, a warning is given and 'None' is returned.
938       - If 'require_changes' == 1, a fatal error is raised.
939
940     """
941     error = None
942
943     orig_filename = filename
944     if filename.endswith(".dak"):
945         filename = filename[:-4]+".changes"
946
947     if not filename.endswith(".changes"):
948         error = "invalid file type; not a changes file"
949     else:
950         if not os.access(filename,os.R_OK):
951             if os.path.exists(filename):
952                 error = "permission denied"
953             else:
954                 error = "file not found"
955
956     if error:
957         if require_changes == 1:
958             fubar("%s: %s." % (orig_filename, error))
959         elif require_changes == 0:
960             warn("Skipping %s - %s" % (orig_filename, error))
961             return None
962         else: # We only care about the .dak file
963             return filename
964     else:
965         return filename
966
967 ################################################################################
968
969 def real_arch(arch):
970     return (arch != "source" and arch != "all")
971
972 ################################################################################
973
974 def join_with_commas_and(list):
975     if len(list) == 0: return "nothing"
976     if len(list) == 1: return list[0]
977     return ", ".join(list[:-1]) + " and " + list[-1]
978
979 ################################################################################
980
981 def pp_deps (deps):
982     pp_deps = []
983     for atom in deps:
984         (pkg, version, constraint) = atom
985         if constraint:
986             pp_dep = "%s (%s %s)" % (pkg, constraint, version)
987         else:
988             pp_dep = pkg
989         pp_deps.append(pp_dep)
990     return " |".join(pp_deps)
991
992 ################################################################################
993
994 def get_conf():
995     return Cnf
996
997 ################################################################################
998
999 def parse_args(Options):
1000     """ Handle -a, -c and -s arguments; returns them as SQL constraints """
1001     # Process suite
1002     if Options["Suite"]:
1003         suite_ids_list = []
1004         for suite in split_args(Options["Suite"]):
1005             suite_id = database.get_suite_id(suite)
1006             if suite_id == -1:
1007                 warn("suite '%s' not recognised." % (suite))
1008             else:
1009                 suite_ids_list.append(suite_id)
1010         if suite_ids_list:
1011             con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
1012         else:
1013             fubar("No valid suite given.")
1014     else:
1015         con_suites = ""
1016
1017     # Process component
1018     if Options["Component"]:
1019         component_ids_list = []
1020         for component in split_args(Options["Component"]):
1021             component_id = database.get_component_id(component)
1022             if component_id == -1:
1023                 warn("component '%s' not recognised." % (component))
1024             else:
1025                 component_ids_list.append(component_id)
1026         if component_ids_list:
1027             con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
1028         else:
1029             fubar("No valid component given.")
1030     else:
1031         con_components = ""
1032
1033     # Process architecture
1034     con_architectures = ""
1035     if Options["Architecture"]:
1036         arch_ids_list = []
1037         check_source = 0
1038         for architecture in split_args(Options["Architecture"]):
1039             if architecture == "source":
1040                 check_source = 1
1041             else:
1042                 architecture_id = database.get_architecture_id(architecture)
1043                 if architecture_id == -1:
1044                     warn("architecture '%s' not recognised." % (architecture))
1045                 else:
1046                     arch_ids_list.append(architecture_id)
1047         if arch_ids_list:
1048             con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
1049         else:
1050             if not check_source:
1051                 fubar("No valid architecture given.")
1052     else:
1053         check_source = 1
1054
1055     return (con_suites, con_architectures, con_components, check_source)
1056
1057 ################################################################################
1058
1059 # Inspired(tm) by Bryn Keller's print_exc_plus (See
1060 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
1061
1062 def print_exc():
1063     tb = sys.exc_info()[2]
1064     while tb.tb_next:
1065         tb = tb.tb_next
1066     stack = []
1067     frame = tb.tb_frame
1068     while frame:
1069         stack.append(frame)
1070         frame = frame.f_back
1071     stack.reverse()
1072     traceback.print_exc()
1073     for frame in stack:
1074         print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
1075                                              frame.f_code.co_filename,
1076                                              frame.f_lineno)
1077         for key, value in frame.f_locals.items():
1078             print "\t%20s = " % key,
1079             try:
1080                 print value
1081             except:
1082                 print "<unable to print>"
1083
1084 ################################################################################
1085
1086 def try_with_debug(function):
1087     try:
1088         function()
1089     except SystemExit:
1090         raise
1091     except:
1092         print_exc()
1093
1094 ################################################################################
1095
1096 def arch_compare_sw (a, b):
1097     """
1098     Function for use in sorting lists of architectures.
1099
1100     Sorts normally except that 'source' dominates all others.
1101     """
1102
1103     if a == "source" and b == "source":
1104         return 0
1105     elif a == "source":
1106         return -1
1107     elif b == "source":
1108         return 1
1109
1110     return cmp (a, b)
1111
1112 ################################################################################
1113
1114 def split_args (s, dwim=1):
1115     """
1116     Split command line arguments which can be separated by either commas
1117     or whitespace.  If dwim is set, it will complain about string ending
1118     in comma since this usually means someone did 'dak ls -a i386, m68k
1119     foo' or something and the inevitable confusion resulting from 'm68k'
1120     being treated as an argument is undesirable.
1121     """
1122
1123     if s.find(",") == -1:
1124         return s.split()
1125     else:
1126         if s[-1:] == "," and dwim:
1127             fubar("split_args: found trailing comma, spurious space maybe?")
1128         return s.split(",")
1129
1130 ################################################################################
1131
1132 def Dict(**dict): return dict
1133
1134 ########################################
1135
1136 def gpgv_get_status_output(cmd, status_read, status_write):
1137     """
1138     Our very own version of commands.getouputstatus(), hacked to support
1139     gpgv's status fd.
1140     """
1141
1142     cmd = ['/bin/sh', '-c', cmd]
1143     p2cread, p2cwrite = os.pipe()
1144     c2pread, c2pwrite = os.pipe()
1145     errout, errin = os.pipe()
1146     pid = os.fork()
1147     if pid == 0:
1148         # Child
1149         os.close(0)
1150         os.close(1)
1151         os.dup(p2cread)
1152         os.dup(c2pwrite)
1153         os.close(2)
1154         os.dup(errin)
1155         for i in range(3, 256):
1156             if i != status_write:
1157                 try:
1158                     os.close(i)
1159                 except:
1160                     pass
1161         try:
1162             os.execvp(cmd[0], cmd)
1163         finally:
1164             os._exit(1)
1165
1166     # Parent
1167     os.close(p2cread)
1168     os.dup2(c2pread, c2pwrite)
1169     os.dup2(errout, errin)
1170
1171     output = status = ""
1172     while 1:
1173         i, o, e = select.select([c2pwrite, errin, status_read], [], [])
1174         more_data = []
1175         for fd in i:
1176             r = os.read(fd, 8196)
1177             if len(r) > 0:
1178                 more_data.append(fd)
1179                 if fd == c2pwrite or fd == errin:
1180                     output += r
1181                 elif fd == status_read:
1182                     status += r
1183                 else:
1184                     fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
1185         if not more_data:
1186             pid, exit_status = os.waitpid(pid, 0)
1187             try:
1188                 os.close(status_write)
1189                 os.close(status_read)
1190                 os.close(c2pread)
1191                 os.close(c2pwrite)
1192                 os.close(p2cwrite)
1193                 os.close(errin)
1194                 os.close(errout)
1195             except:
1196                 pass
1197             break
1198
1199     return output, status, exit_status
1200
1201 ################################################################################
1202
1203 def process_gpgv_output(status):
1204     # Process the status-fd output
1205     keywords = {}
1206     internal_error = ""
1207     for line in status.split('\n'):
1208         line = line.strip()
1209         if line == "":
1210             continue
1211         split = line.split()
1212         if len(split) < 2:
1213             internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
1214             continue
1215         (gnupg, keyword) = split[:2]
1216         if gnupg != "[GNUPG:]":
1217             internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
1218             continue
1219         args = split[2:]
1220         if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
1221             internal_error += "found duplicate status token ('%s').\n" % (keyword)
1222             continue
1223         else:
1224             keywords[keyword] = args
1225
1226     return (keywords, internal_error)
1227
1228 ################################################################################
1229
1230 def retrieve_key (filename, keyserver=None, keyring=None):
1231     """
1232     Retrieve the key that signed 'filename' from 'keyserver' and
1233     add it to 'keyring'.  Returns nothing on success, or an error message
1234     on error.
1235     """
1236
1237     # Defaults for keyserver and keyring
1238     if not keyserver:
1239         keyserver = Cnf["Dinstall::KeyServer"]
1240     if not keyring:
1241         keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
1242
1243     # Ensure the filename contains no shell meta-characters or other badness
1244     if not re_taint_free.match(filename):
1245         return "%s: tainted filename" % (filename)
1246
1247     # Invoke gpgv on the file
1248     status_read, status_write = os.pipe()
1249     cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
1250     (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
1251
1252     # Process the status-fd output
1253     (keywords, internal_error) = process_gpgv_output(status)
1254     if internal_error:
1255         return internal_error
1256
1257     if not keywords.has_key("NO_PUBKEY"):
1258         return "didn't find expected NO_PUBKEY in gpgv status-fd output"
1259
1260     fingerprint = keywords["NO_PUBKEY"][0]
1261     # XXX - gpg sucks.  You can't use --secret-keyring=/dev/null as
1262     # it'll try to create a lockfile in /dev.  A better solution might
1263     # be a tempfile or something.
1264     cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
1265           % (Cnf["Dinstall::SigningKeyring"])
1266     cmd += " --keyring %s --keyserver %s --recv-key %s" \
1267            % (keyring, keyserver, fingerprint)
1268     (result, output) = commands.getstatusoutput(cmd)
1269     if (result != 0):
1270         return "'%s' failed with exit code %s" % (cmd, result)
1271
1272     return ""
1273
1274 ################################################################################
1275
1276 def gpg_keyring_args(keyrings=None):
1277     if not keyrings:
1278         keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1279
1280     return " ".join(["--keyring %s" % x for x in keyrings])
1281
1282 ################################################################################
1283
1284 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
1285     """
1286     Check the signature of a file and return the fingerprint if the
1287     signature is valid or 'None' if it's not.  The first argument is the
1288     filename whose signature should be checked.  The second argument is a
1289     reject function and is called when an error is found.  The reject()
1290     function must allow for two arguments: the first is the error message,
1291     the second is an optional prefix string.  It's possible for reject()
1292     to be called more than once during an invocation of check_signature().
1293     The third argument is optional and is the name of the files the
1294     detached signature applies to.  The fourth argument is optional and is
1295     a *list* of keyrings to use.  'autofetch' can either be None, True or
1296     False.  If None, the default behaviour specified in the config will be
1297     used.
1298     """
1299
1300     # Ensure the filename contains no shell meta-characters or other badness
1301     if not re_taint_free.match(sig_filename):
1302         reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
1303         return None
1304
1305     if data_filename and not re_taint_free.match(data_filename):
1306         reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
1307         return None
1308
1309     if not keyrings:
1310         keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1311
1312     # Autofetch the signing key if that's enabled
1313     if autofetch == None:
1314         autofetch = Cnf.get("Dinstall::KeyAutoFetch")
1315     if autofetch:
1316         error_msg = retrieve_key(sig_filename)
1317         if error_msg:
1318             reject(error_msg)
1319             return None
1320
1321     # Build the command line
1322     status_read, status_write = os.pipe()
1323     cmd = "gpgv --status-fd %s %s %s %s" % (
1324         status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
1325
1326     # Invoke gpgv on the file
1327     (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1328
1329     # Process the status-fd output
1330     (keywords, internal_error) = process_gpgv_output(status)
1331
1332     # If we failed to parse the status-fd output, let's just whine and bail now
1333     if internal_error:
1334         reject("internal error while performing signature check on %s." % (sig_filename))
1335         reject(internal_error, "")
1336         reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1337         return None
1338
1339     bad = ""
1340     # Now check for obviously bad things in the processed output
1341     if keywords.has_key("KEYREVOKED"):
1342         reject("The key used to sign %s has been revoked." % (sig_filename))
1343         bad = 1
1344     if keywords.has_key("BADSIG"):
1345         reject("bad signature on %s." % (sig_filename))
1346         bad = 1
1347     if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1348         reject("failed to check signature on %s." % (sig_filename))
1349         bad = 1
1350     if keywords.has_key("NO_PUBKEY"):
1351         args = keywords["NO_PUBKEY"]
1352         if len(args) >= 1:
1353             key = args[0]
1354         reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1355         bad = 1
1356     if keywords.has_key("BADARMOR"):
1357         reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
1358         bad = 1
1359     if keywords.has_key("NODATA"):
1360         reject("no signature found in %s." % (sig_filename))
1361         bad = 1
1362     if keywords.has_key("EXPKEYSIG"):
1363         args = keywords["EXPKEYSIG"]
1364         if len(args) >= 1:
1365             key = args[0]
1366         reject("Signature made by expired key 0x%s" % (key))
1367         bad = 1
1368     if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1369         args = keywords["KEYEXPIRED"]
1370         expiredate=""
1371         if len(args) >= 1:
1372             timestamp = args[0]
1373             if timestamp.count("T") == 0:
1374                 try:
1375                     expiredate = time.strftime("%Y-%m-%d", time.gmtime(float(timestamp)))
1376                 except ValueError:
1377                     expiredate = "unknown (%s)" % (timestamp)
1378             else:
1379                 expiredate = timestamp
1380         reject("The key used to sign %s has expired on %s" % (sig_filename, expiredate))
1381         bad = 1
1382
1383     if bad:
1384         return None
1385
1386     # Next check gpgv exited with a zero return code
1387     if exit_status:
1388         reject("gpgv failed while checking %s." % (sig_filename))
1389         if status.strip():
1390             reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1391         else:
1392             reject(prefix_multi_line_string(output, " [GPG output:] "), "")
1393         return None
1394
1395     # Sanity check the good stuff we expect
1396     if not keywords.has_key("VALIDSIG"):
1397         reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1398         bad = 1
1399     else:
1400         args = keywords["VALIDSIG"]
1401         if len(args) < 1:
1402             reject("internal error while checking signature on %s." % (sig_filename))
1403             bad = 1
1404         else:
1405             fingerprint = args[0]
1406     if not keywords.has_key("GOODSIG"):
1407         reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1408         bad = 1
1409     if not keywords.has_key("SIG_ID"):
1410         reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1411         bad = 1
1412
1413     # Finally ensure there's not something we don't recognise
1414     known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1415                           SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1416                           NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1417
1418     for keyword in keywords.keys():
1419         if not known_keywords.has_key(keyword):
1420             reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1421             bad = 1
1422
1423     if bad:
1424         return None
1425     else:
1426         return fingerprint
1427
1428 ################################################################################
1429
1430 def gpg_get_key_addresses(fingerprint):
1431     """retreive email addresses from gpg key uids for a given fingerprint"""
1432     addresses = key_uid_email_cache.get(fingerprint)
1433     if addresses != None:
1434         return addresses
1435     addresses = set()
1436     cmd = "gpg --no-default-keyring %s --fingerprint %s" \
1437                 % (gpg_keyring_args(), fingerprint)
1438     (result, output) = commands.getstatusoutput(cmd)
1439     if result == 0:
1440         for l in output.split('\n'):
1441             m = re_gpg_uid.match(l)
1442             if m:
1443                 addresses.add(m.group(1))
1444     key_uid_email_cache[fingerprint] = addresses
1445     return addresses
1446
1447 ################################################################################
1448
1449 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1450
1451 def wrap(paragraph, max_length, prefix=""):
1452     line = ""
1453     s = ""
1454     have_started = 0
1455     words = paragraph.split()
1456
1457     for word in words:
1458         word_size = len(word)
1459         if word_size > max_length:
1460             if have_started:
1461                 s += line + '\n' + prefix
1462             s += word + '\n' + prefix
1463         else:
1464             if have_started:
1465                 new_length = len(line) + word_size + 1
1466                 if new_length > max_length:
1467                     s += line + '\n' + prefix
1468                     line = word
1469                 else:
1470                     line += ' ' + word
1471             else:
1472                 line = word
1473         have_started = 1
1474
1475     if have_started:
1476         s += line
1477
1478     return s
1479
1480 ################################################################################
1481
1482 def clean_symlink (src, dest, root):
1483     """
1484     Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1485     Returns fixed 'src'
1486     """
1487     src = src.replace(root, '', 1)
1488     dest = dest.replace(root, '', 1)
1489     dest = os.path.dirname(dest)
1490     new_src = '../' * len(dest.split('/'))
1491     return new_src + src
1492
1493 ################################################################################
1494
1495 def temp_filename(directory=None, prefix="dak", suffix=""):
1496     """
1497     Return a secure and unique filename by pre-creating it.
1498     If 'directory' is non-null, it will be the directory the file is pre-created in.
1499     If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
1500     If 'suffix' is non-null, the filename will end with it.
1501
1502     Returns a pair (fd, name).
1503     """
1504
1505     return tempfile.mkstemp(suffix, prefix, directory)
1506
1507 ################################################################################
1508
1509 def temp_dirname(parent=None, prefix="dak", suffix=""):
1510     """
1511     Return a secure and unique directory by pre-creating it.
1512     If 'parent' is non-null, it will be the directory the directory is pre-created in.
1513     If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
1514     If 'suffix' is non-null, the filename will end with it.
1515
1516     Returns a pathname to the new directory
1517     """
1518
1519     return tempfile.mkdtemp(suffix, prefix, parent)
1520
1521 ################################################################################
1522
1523 def is_email_alias(email):
1524     """ checks if the user part of the email is listed in the alias file """
1525     global alias_cache
1526     if alias_cache == None:
1527         aliasfn = which_alias_file()
1528         alias_cache = set()
1529         if aliasfn:
1530             for l in open(aliasfn):
1531                 alias_cache.add(l.split(':')[0])
1532     uid = email.split('@')[0]
1533     return uid in alias_cache
1534
1535 ################################################################################
1536
1537 def get_changes_files(dir):
1538     """
1539     Takes a directory and lists all .changes files in it (as well as chdir'ing
1540     to the directory; this is due to broken behaviour on the part of p-u/p-a
1541     when you're not in the right place)
1542
1543     Returns a list of filenames
1544     """
1545     try:
1546         # Much of the rest of p-u/p-a depends on being in the right place
1547         os.chdir(dir)
1548         changes_files = [x for x in os.listdir(dir) if x.endswith('.changes')]
1549     except OSError, e:
1550         fubar("Failed to read list from directory %s (%s)" % (dir, e))
1551
1552     return changes_files
1553
1554 ################################################################################
1555
1556 apt_pkg.init()
1557
1558 Cnf = apt_pkg.newConfiguration()
1559 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1560
1561 if which_conf_file() != default_config:
1562     apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1563
1564 ###############################################################################