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