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