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