]> git.decadent.org.uk Git - dak.git/blob - daklib/utils.py
Make build_file_list to use get_format_from_string and friends.
[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, 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 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     # Get SourceFormat object for this Format and validate it
527     format = get_format_from_string(changes.get['format'])
528     format.validate_format(is_a_dsc=is_a_dsc, field=field)
529
530     includes_section = (not is_a_dsc) and field == "files"
531
532     # Parse each entry/line:
533     for i in changes[field].split('\n'):
534         if not i:
535             break
536         s = i.split()
537         section = priority = ""
538         try:
539             if includes_section:
540                 (md5, size, section, priority, name) = s
541             else:
542                 (md5, size, name) = s
543         except ValueError:
544             raise ParseChangesError, i
545
546         if section == "":
547             section = "-"
548         if priority == "":
549             priority = "-"
550
551         (section, component) = extract_component_from_section(section)
552
553         files[name] = Dict(size=size, section=section,
554                            priority=priority, component=component)
555         files[name][hashname] = md5
556
557     return files
558
559 ################################################################################
560
561 def send_mail (message, filename=""):
562     """sendmail wrapper, takes _either_ a message string or a file as arguments"""
563
564     # If we've been passed a string dump it into a temporary file
565     if message:
566         (fd, filename) = tempfile.mkstemp()
567         os.write (fd, message)
568         os.close (fd)
569
570     if Cnf.has_key("Dinstall::MailWhiteList") and \
571            Cnf["Dinstall::MailWhiteList"] != "":
572         message_in = open_file(filename)
573         message_raw = modemail.message_from_file(message_in)
574         message_in.close();
575
576         whitelist = [];
577         whitelist_in = open_file(Cnf["Dinstall::MailWhiteList"])
578         try:
579             for line in whitelist_in:
580                 if not re_whitespace_comment.match(line):
581                     if re_re_mark.match(line):
582                         whitelist.append(re.compile(re_re_mark.sub("", line.strip(), 1)))
583                     else:
584                         whitelist.append(re.compile(re.escape(line.strip())))
585         finally:
586             whitelist_in.close()
587
588         # Fields to check.
589         fields = ["To", "Bcc", "Cc"]
590         for field in fields:
591             # Check each field
592             value = message_raw.get(field, None)
593             if value != None:
594                 match = [];
595                 for item in value.split(","):
596                     (rfc822_maint, rfc2047_maint, name, email) = fix_maintainer(item.strip())
597                     mail_whitelisted = 0
598                     for wr in whitelist:
599                         if wr.match(email):
600                             mail_whitelisted = 1
601                             break
602                     if not mail_whitelisted:
603                         print "Skipping %s since it's not in %s" % (item, Cnf["Dinstall::MailWhiteList"])
604                         continue
605                     match.append(item)
606
607                 # Doesn't have any mail in whitelist so remove the header
608                 if len(match) == 0:
609                     del message_raw[field]
610                 else:
611                     message_raw.replace_header(field, string.join(match, ", "))
612
613         # Change message fields in order if we don't have a To header
614         if not message_raw.has_key("To"):
615             fields.reverse()
616             for field in fields:
617                 if message_raw.has_key(field):
618                     message_raw[fields[-1]] = message_raw[field]
619                     del message_raw[field]
620                     break
621             else:
622                 # Clean up any temporary files
623                 # and return, as we removed all recipients.
624                 if message:
625                     os.unlink (filename);
626                 return;
627
628         fd = os.open(filename, os.O_RDWR|os.O_EXCL, 0700);
629         os.write (fd, message_raw.as_string(True));
630         os.close (fd);
631
632     # Invoke sendmail
633     (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
634     if (result != 0):
635         raise SendmailFailedError, output
636
637     # Clean up any temporary files
638     if message:
639         os.unlink (filename)
640
641 ################################################################################
642
643 def poolify (source, component):
644     if component:
645         component += '/'
646     if source[:3] == "lib":
647         return component + source[:4] + '/' + source + '/'
648     else:
649         return component + source[:1] + '/' + source + '/'
650
651 ################################################################################
652
653 def move (src, dest, overwrite = 0, perms = 0664):
654     if os.path.exists(dest) and os.path.isdir(dest):
655         dest_dir = dest
656     else:
657         dest_dir = os.path.dirname(dest)
658     if not os.path.exists(dest_dir):
659         umask = os.umask(00000)
660         os.makedirs(dest_dir, 02775)
661         os.umask(umask)
662     #print "Moving %s to %s..." % (src, dest)
663     if os.path.exists(dest) and os.path.isdir(dest):
664         dest += '/' + os.path.basename(src)
665     # Don't overwrite unless forced to
666     if os.path.exists(dest):
667         if not overwrite:
668             fubar("Can't move %s to %s - file already exists." % (src, dest))
669         else:
670             if not os.access(dest, os.W_OK):
671                 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
672     shutil.copy2(src, dest)
673     os.chmod(dest, perms)
674     os.unlink(src)
675
676 def copy (src, dest, overwrite = 0, perms = 0664):
677     if os.path.exists(dest) and os.path.isdir(dest):
678         dest_dir = dest
679     else:
680         dest_dir = os.path.dirname(dest)
681     if not os.path.exists(dest_dir):
682         umask = os.umask(00000)
683         os.makedirs(dest_dir, 02775)
684         os.umask(umask)
685     #print "Copying %s to %s..." % (src, dest)
686     if os.path.exists(dest) and os.path.isdir(dest):
687         dest += '/' + os.path.basename(src)
688     # Don't overwrite unless forced to
689     if os.path.exists(dest):
690         if not overwrite:
691             raise FileExistsError
692         else:
693             if not os.access(dest, os.W_OK):
694                 raise CantOverwriteError
695     shutil.copy2(src, dest)
696     os.chmod(dest, perms)
697
698 ################################################################################
699
700 def where_am_i ():
701     res = socket.gethostbyaddr(socket.gethostname())
702     database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
703     if database_hostname:
704         return database_hostname
705     else:
706         return res[0]
707
708 def which_conf_file ():
709     res = socket.gethostbyaddr(socket.gethostname())
710     # In case we allow local config files per user, try if one exists
711     if Cnf.FindB("Config::" + res[0] + "::AllowLocalConfig"):
712         homedir = os.getenv("HOME")
713         confpath = os.path.join(homedir, "/etc/dak.conf")
714         if os.path.exists(confpath):
715             apt_pkg.ReadConfigFileISC(Cnf,default_config)
716
717     # We are still in here, so there is no local config file or we do
718     # not allow local files. Do the normal stuff.
719     if Cnf.get("Config::" + res[0] + "::DakConfig"):
720         return Cnf["Config::" + res[0] + "::DakConfig"]
721     else:
722         return default_config
723
724 def which_apt_conf_file ():
725     res = socket.gethostbyaddr(socket.gethostname())
726     # In case we allow local config files per user, try if one exists
727     if Cnf.FindB("Config::" + res[0] + "::AllowLocalConfig"):
728         homedir = os.getenv("HOME")
729         confpath = os.path.join(homedir, "/etc/dak.conf")
730         if os.path.exists(confpath):
731             apt_pkg.ReadConfigFileISC(Cnf,default_config)
732
733     if Cnf.get("Config::" + res[0] + "::AptConfig"):
734         return Cnf["Config::" + res[0] + "::AptConfig"]
735     else:
736         return default_apt_config
737
738 def which_alias_file():
739     hostname = socket.gethostbyaddr(socket.gethostname())[0]
740     aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
741     if os.path.exists(aliasfn):
742         return aliasfn
743     else:
744         return None
745
746 ################################################################################
747
748 def TemplateSubst(map, filename):
749     """ Perform a substition of template """
750     templatefile = open_file(filename)
751     template = templatefile.read()
752     for x in map.keys():
753         template = template.replace(x, str(map[x]))
754     templatefile.close()
755     return template
756
757 ################################################################################
758
759 def fubar(msg, exit_code=1):
760     sys.stderr.write("E: %s\n" % (msg))
761     sys.exit(exit_code)
762
763 def warn(msg):
764     sys.stderr.write("W: %s\n" % (msg))
765
766 ################################################################################
767
768 # Returns the user name with a laughable attempt at rfc822 conformancy
769 # (read: removing stray periods).
770 def whoami ():
771     return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
772
773 def getusername ():
774     return pwd.getpwuid(os.getuid())[0]
775
776 ################################################################################
777
778 def size_type (c):
779     t  = " B"
780     if c > 10240:
781         c = c / 1024
782         t = " KB"
783     if c > 10240:
784         c = c / 1024
785         t = " MB"
786     return ("%d%s" % (c, t))
787
788 ################################################################################
789
790 def cc_fix_changes (changes):
791     o = changes.get("architecture", "")
792     if o:
793         del changes["architecture"]
794     changes["architecture"] = {}
795     for j in o.split():
796         changes["architecture"][j] = 1
797
798 def changes_compare (a, b):
799     """ Sort by source name, source version, 'have source', and then by filename """
800     try:
801         a_changes = parse_changes(a)
802     except:
803         return -1
804
805     try:
806         b_changes = parse_changes(b)
807     except:
808         return 1
809
810     cc_fix_changes (a_changes)
811     cc_fix_changes (b_changes)
812
813     # Sort by source name
814     a_source = a_changes.get("source")
815     b_source = b_changes.get("source")
816     q = cmp (a_source, b_source)
817     if q:
818         return q
819
820     # Sort by source version
821     a_version = a_changes.get("version", "0")
822     b_version = b_changes.get("version", "0")
823     q = apt_pkg.VersionCompare(a_version, b_version)
824     if q:
825         return q
826
827     # Sort by 'have source'
828     a_has_source = a_changes["architecture"].get("source")
829     b_has_source = b_changes["architecture"].get("source")
830     if a_has_source and not b_has_source:
831         return -1
832     elif b_has_source and not a_has_source:
833         return 1
834
835     # Fall back to sort by filename
836     return cmp(a, b)
837
838 ################################################################################
839
840 def find_next_free (dest, too_many=100):
841     extra = 0
842     orig_dest = dest
843     while os.path.exists(dest) and extra < too_many:
844         dest = orig_dest + '.' + repr(extra)
845         extra += 1
846     if extra >= too_many:
847         raise NoFreeFilenameError
848     return dest
849
850 ################################################################################
851
852 def result_join (original, sep = '\t'):
853     resultlist = []
854     for i in xrange(len(original)):
855         if original[i] == None:
856             resultlist.append("")
857         else:
858             resultlist.append(original[i])
859     return sep.join(resultlist)
860
861 ################################################################################
862
863 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
864     out = ""
865     for line in str.split('\n'):
866         line = line.strip()
867         if line or include_blank_lines:
868             out += "%s%s\n" % (prefix, line)
869     # Strip trailing new line
870     if out:
871         out = out[:-1]
872     return out
873
874 ################################################################################
875
876 def validate_changes_file_arg(filename, require_changes=1):
877     """
878     'filename' is either a .changes or .dak file.  If 'filename' is a
879     .dak file, it's changed to be the corresponding .changes file.  The
880     function then checks if the .changes file a) exists and b) is
881     readable and returns the .changes filename if so.  If there's a
882     problem, the next action depends on the option 'require_changes'
883     argument:
884
885       - If 'require_changes' == -1, errors are ignored and the .changes
886         filename is returned.
887       - If 'require_changes' == 0, a warning is given and 'None' is returned.
888       - If 'require_changes' == 1, a fatal error is raised.
889
890     """
891     error = None
892
893     orig_filename = filename
894     if filename.endswith(".dak"):
895         filename = filename[:-4]+".changes"
896
897     if not filename.endswith(".changes"):
898         error = "invalid file type; not a changes file"
899     else:
900         if not os.access(filename,os.R_OK):
901             if os.path.exists(filename):
902                 error = "permission denied"
903             else:
904                 error = "file not found"
905
906     if error:
907         if require_changes == 1:
908             fubar("%s: %s." % (orig_filename, error))
909         elif require_changes == 0:
910             warn("Skipping %s - %s" % (orig_filename, error))
911             return None
912         else: # We only care about the .dak file
913             return filename
914     else:
915         return filename
916
917 ################################################################################
918
919 def real_arch(arch):
920     return (arch != "source" and arch != "all")
921
922 ################################################################################
923
924 def join_with_commas_and(list):
925     if len(list) == 0: return "nothing"
926     if len(list) == 1: return list[0]
927     return ", ".join(list[:-1]) + " and " + list[-1]
928
929 ################################################################################
930
931 def pp_deps (deps):
932     pp_deps = []
933     for atom in deps:
934         (pkg, version, constraint) = atom
935         if constraint:
936             pp_dep = "%s (%s %s)" % (pkg, constraint, version)
937         else:
938             pp_dep = pkg
939         pp_deps.append(pp_dep)
940     return " |".join(pp_deps)
941
942 ################################################################################
943
944 def get_conf():
945     return Cnf
946
947 ################################################################################
948
949 def parse_args(Options):
950     """ Handle -a, -c and -s arguments; returns them as SQL constraints """
951     # XXX: This should go away and everything which calls it be converted
952     #      to use SQLA properly.  For now, we'll just fix it not to use
953     #      the old Pg interface though
954     session = DBConn().session()
955     # Process suite
956     if Options["Suite"]:
957         suite_ids_list = []
958         for suitename in split_args(Options["Suite"]):
959             suite = get_suite(suitename, session=session)
960             if suite.suite_id is None:
961                 warn("suite '%s' not recognised." % (suite.suite_name))
962             else:
963                 suite_ids_list.append(suite.suite_id)
964         if suite_ids_list:
965             con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
966         else:
967             fubar("No valid suite given.")
968     else:
969         con_suites = ""
970
971     # Process component
972     if Options["Component"]:
973         component_ids_list = []
974         for componentname in split_args(Options["Component"]):
975             component = get_component(componentname, session=session)
976             if component is None:
977                 warn("component '%s' not recognised." % (componentname))
978             else:
979                 component_ids_list.append(component.component_id)
980         if component_ids_list:
981             con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
982         else:
983             fubar("No valid component given.")
984     else:
985         con_components = ""
986
987     # Process architecture
988     con_architectures = ""
989     check_source = 0
990     if Options["Architecture"]:
991         arch_ids_list = []
992         for archname in split_args(Options["Architecture"]):
993             if archname == "source":
994                 check_source = 1
995             else:
996                 arch = get_architecture(archname, session=session)
997                 if arch is None:
998                     warn("architecture '%s' not recognised." % (archname))
999                 else:
1000                     arch_ids_list.append(arch.arch_id)
1001         if arch_ids_list:
1002             con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
1003         else:
1004             if not check_source:
1005                 fubar("No valid architecture given.")
1006     else:
1007         check_source = 1
1008
1009     return (con_suites, con_architectures, con_components, check_source)
1010
1011 ################################################################################
1012
1013 # Inspired(tm) by Bryn Keller's print_exc_plus (See
1014 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
1015
1016 def print_exc():
1017     tb = sys.exc_info()[2]
1018     while tb.tb_next:
1019         tb = tb.tb_next
1020     stack = []
1021     frame = tb.tb_frame
1022     while frame:
1023         stack.append(frame)
1024         frame = frame.f_back
1025     stack.reverse()
1026     traceback.print_exc()
1027     for frame in stack:
1028         print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
1029                                              frame.f_code.co_filename,
1030                                              frame.f_lineno)
1031         for key, value in frame.f_locals.items():
1032             print "\t%20s = " % key,
1033             try:
1034                 print value
1035             except:
1036                 print "<unable to print>"
1037
1038 ################################################################################
1039
1040 def try_with_debug(function):
1041     try:
1042         function()
1043     except SystemExit:
1044         raise
1045     except:
1046         print_exc()
1047
1048 ################################################################################
1049
1050 def arch_compare_sw (a, b):
1051     """
1052     Function for use in sorting lists of architectures.
1053
1054     Sorts normally except that 'source' dominates all others.
1055     """
1056
1057     if a == "source" and b == "source":
1058         return 0
1059     elif a == "source":
1060         return -1
1061     elif b == "source":
1062         return 1
1063
1064     return cmp (a, b)
1065
1066 ################################################################################
1067
1068 def split_args (s, dwim=1):
1069     """
1070     Split command line arguments which can be separated by either commas
1071     or whitespace.  If dwim is set, it will complain about string ending
1072     in comma since this usually means someone did 'dak ls -a i386, m68k
1073     foo' or something and the inevitable confusion resulting from 'm68k'
1074     being treated as an argument is undesirable.
1075     """
1076
1077     if s.find(",") == -1:
1078         return s.split()
1079     else:
1080         if s[-1:] == "," and dwim:
1081             fubar("split_args: found trailing comma, spurious space maybe?")
1082         return s.split(",")
1083
1084 ################################################################################
1085
1086 def Dict(**dict): return dict
1087
1088 ########################################
1089
1090 def gpgv_get_status_output(cmd, status_read, status_write):
1091     """
1092     Our very own version of commands.getouputstatus(), hacked to support
1093     gpgv's status fd.
1094     """
1095
1096     cmd = ['/bin/sh', '-c', cmd]
1097     p2cread, p2cwrite = os.pipe()
1098     c2pread, c2pwrite = os.pipe()
1099     errout, errin = os.pipe()
1100     pid = os.fork()
1101     if pid == 0:
1102         # Child
1103         os.close(0)
1104         os.close(1)
1105         os.dup(p2cread)
1106         os.dup(c2pwrite)
1107         os.close(2)
1108         os.dup(errin)
1109         for i in range(3, 256):
1110             if i != status_write:
1111                 try:
1112                     os.close(i)
1113                 except:
1114                     pass
1115         try:
1116             os.execvp(cmd[0], cmd)
1117         finally:
1118             os._exit(1)
1119
1120     # Parent
1121     os.close(p2cread)
1122     os.dup2(c2pread, c2pwrite)
1123     os.dup2(errout, errin)
1124
1125     output = status = ""
1126     while 1:
1127         i, o, e = select.select([c2pwrite, errin, status_read], [], [])
1128         more_data = []
1129         for fd in i:
1130             r = os.read(fd, 8196)
1131             if len(r) > 0:
1132                 more_data.append(fd)
1133                 if fd == c2pwrite or fd == errin:
1134                     output += r
1135                 elif fd == status_read:
1136                     status += r
1137                 else:
1138                     fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
1139         if not more_data:
1140             pid, exit_status = os.waitpid(pid, 0)
1141             try:
1142                 os.close(status_write)
1143                 os.close(status_read)
1144                 os.close(c2pread)
1145                 os.close(c2pwrite)
1146                 os.close(p2cwrite)
1147                 os.close(errin)
1148                 os.close(errout)
1149             except:
1150                 pass
1151             break
1152
1153     return output, status, exit_status
1154
1155 ################################################################################
1156
1157 def process_gpgv_output(status):
1158     # Process the status-fd output
1159     keywords = {}
1160     internal_error = ""
1161     for line in status.split('\n'):
1162         line = line.strip()
1163         if line == "":
1164             continue
1165         split = line.split()
1166         if len(split) < 2:
1167             internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
1168             continue
1169         (gnupg, keyword) = split[:2]
1170         if gnupg != "[GNUPG:]":
1171             internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
1172             continue
1173         args = split[2:]
1174         if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
1175             internal_error += "found duplicate status token ('%s').\n" % (keyword)
1176             continue
1177         else:
1178             keywords[keyword] = args
1179
1180     return (keywords, internal_error)
1181
1182 ################################################################################
1183
1184 def retrieve_key (filename, keyserver=None, keyring=None):
1185     """
1186     Retrieve the key that signed 'filename' from 'keyserver' and
1187     add it to 'keyring'.  Returns nothing on success, or an error message
1188     on error.
1189     """
1190
1191     # Defaults for keyserver and keyring
1192     if not keyserver:
1193         keyserver = Cnf["Dinstall::KeyServer"]
1194     if not keyring:
1195         keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
1196
1197     # Ensure the filename contains no shell meta-characters or other badness
1198     if not re_taint_free.match(filename):
1199         return "%s: tainted filename" % (filename)
1200
1201     # Invoke gpgv on the file
1202     status_read, status_write = os.pipe()
1203     cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
1204     (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
1205
1206     # Process the status-fd output
1207     (keywords, internal_error) = process_gpgv_output(status)
1208     if internal_error:
1209         return internal_error
1210
1211     if not keywords.has_key("NO_PUBKEY"):
1212         return "didn't find expected NO_PUBKEY in gpgv status-fd output"
1213
1214     fingerprint = keywords["NO_PUBKEY"][0]
1215     # XXX - gpg sucks.  You can't use --secret-keyring=/dev/null as
1216     # it'll try to create a lockfile in /dev.  A better solution might
1217     # be a tempfile or something.
1218     cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
1219           % (Cnf["Dinstall::SigningKeyring"])
1220     cmd += " --keyring %s --keyserver %s --recv-key %s" \
1221            % (keyring, keyserver, fingerprint)
1222     (result, output) = commands.getstatusoutput(cmd)
1223     if (result != 0):
1224         return "'%s' failed with exit code %s" % (cmd, result)
1225
1226     return ""
1227
1228 ################################################################################
1229
1230 def gpg_keyring_args(keyrings=None):
1231     if not keyrings:
1232         keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1233
1234     return " ".join(["--keyring %s" % x for x in keyrings])
1235
1236 ################################################################################
1237
1238 def check_signature (sig_filename, data_filename="", keyrings=None, autofetch=None):
1239     """
1240     Check the signature of a file and return the fingerprint if the
1241     signature is valid or 'None' if it's not.  The first argument is the
1242     filename whose signature should be checked.  The second argument is a
1243     reject function and is called when an error is found.  The reject()
1244     function must allow for two arguments: the first is the error message,
1245     the second is an optional prefix string.  It's possible for reject()
1246     to be called more than once during an invocation of check_signature().
1247     The third argument is optional and is the name of the files the
1248     detached signature applies to.  The fourth argument is optional and is
1249     a *list* of keyrings to use.  'autofetch' can either be None, True or
1250     False.  If None, the default behaviour specified in the config will be
1251     used.
1252     """
1253
1254     rejects = []
1255
1256     # Ensure the filename contains no shell meta-characters or other badness
1257     if not re_taint_free.match(sig_filename):
1258         rejects.append("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
1259         return (None, rejects)
1260
1261     if data_filename and not re_taint_free.match(data_filename):
1262         rejects.append("!!WARNING!! tainted data filename: '%s'." % (data_filename))
1263         return (None, rejects)
1264
1265     if not keyrings:
1266         keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1267
1268     # Autofetch the signing key if that's enabled
1269     if autofetch == None:
1270         autofetch = Cnf.get("Dinstall::KeyAutoFetch")
1271     if autofetch:
1272         error_msg = retrieve_key(sig_filename)
1273         if error_msg:
1274             rejects.append(error_msg)
1275             return (None, rejects)
1276
1277     # Build the command line
1278     status_read, status_write = os.pipe()
1279     cmd = "gpgv --status-fd %s %s %s %s" % (
1280         status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
1281
1282     # Invoke gpgv on the file
1283     (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1284
1285     # Process the status-fd output
1286     (keywords, internal_error) = process_gpgv_output(status)
1287
1288     # If we failed to parse the status-fd output, let's just whine and bail now
1289     if internal_error:
1290         rejects.append("internal error while performing signature check on %s." % (sig_filename))
1291         rejects.append(internal_error, "")
1292         rejects.append("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1293         return (None, rejects)
1294
1295     # Now check for obviously bad things in the processed output
1296     if keywords.has_key("KEYREVOKED"):
1297         rejects.append("The key used to sign %s has been revoked." % (sig_filename))
1298     if keywords.has_key("BADSIG"):
1299         rejects.append("bad signature on %s." % (sig_filename))
1300     if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1301         rejects.append("failed to check signature on %s." % (sig_filename))
1302     if keywords.has_key("NO_PUBKEY"):
1303         args = keywords["NO_PUBKEY"]
1304         if len(args) >= 1:
1305             key = args[0]
1306         rejects.append("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1307     if keywords.has_key("BADARMOR"):
1308         rejects.append("ASCII armour of signature was corrupt in %s." % (sig_filename))
1309     if keywords.has_key("NODATA"):
1310         rejects.append("no signature found in %s." % (sig_filename))
1311     if keywords.has_key("EXPKEYSIG"):
1312         args = keywords["EXPKEYSIG"]
1313         if len(args) >= 1:
1314             key = args[0]
1315         rejects.append("Signature made by expired key 0x%s" % (key))
1316     if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1317         args = keywords["KEYEXPIRED"]
1318         expiredate=""
1319         if len(args) >= 1:
1320             timestamp = args[0]
1321             if timestamp.count("T") == 0:
1322                 try:
1323                     expiredate = time.strftime("%Y-%m-%d", time.gmtime(float(timestamp)))
1324                 except ValueError:
1325                     expiredate = "unknown (%s)" % (timestamp)
1326             else:
1327                 expiredate = timestamp
1328         rejects.append("The key used to sign %s has expired on %s" % (sig_filename, expiredate))
1329
1330     if len(rejects) > 0:
1331         return (None, rejects)
1332
1333     # Next check gpgv exited with a zero return code
1334     if exit_status:
1335         rejects.append("gpgv failed while checking %s." % (sig_filename))
1336         if status.strip():
1337             rejects.append(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1338         else:
1339             rejects.append(prefix_multi_line_string(output, " [GPG output:] "), "")
1340         return (None, rejects)
1341
1342     # Sanity check the good stuff we expect
1343     if not keywords.has_key("VALIDSIG"):
1344         rejects.append("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1345     else:
1346         args = keywords["VALIDSIG"]
1347         if len(args) < 1:
1348             rejects.append("internal error while checking signature on %s." % (sig_filename))
1349         else:
1350             fingerprint = args[0]
1351     if not keywords.has_key("GOODSIG"):
1352         rejects.append("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1353     if not keywords.has_key("SIG_ID"):
1354         rejects.append("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1355
1356     # Finally ensure there's not something we don't recognise
1357     known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1358                           SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1359                           NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1360
1361     for keyword in keywords.keys():
1362         if not known_keywords.has_key(keyword):
1363             rejects.append("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1364
1365     if len(rejects) > 0:
1366         return (None, rejects)
1367     else:
1368         return (fingerprint, [])
1369
1370 ################################################################################
1371
1372 def gpg_get_key_addresses(fingerprint):
1373     """retreive email addresses from gpg key uids for a given fingerprint"""
1374     addresses = key_uid_email_cache.get(fingerprint)
1375     if addresses != None:
1376         return addresses
1377     addresses = set()
1378     cmd = "gpg --no-default-keyring %s --fingerprint %s" \
1379                 % (gpg_keyring_args(), fingerprint)
1380     (result, output) = commands.getstatusoutput(cmd)
1381     if result == 0:
1382         for l in output.split('\n'):
1383             m = re_gpg_uid.match(l)
1384             if m:
1385                 addresses.add(m.group(1))
1386     key_uid_email_cache[fingerprint] = addresses
1387     return addresses
1388
1389 ################################################################################
1390
1391 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1392
1393 def wrap(paragraph, max_length, prefix=""):
1394     line = ""
1395     s = ""
1396     have_started = 0
1397     words = paragraph.split()
1398
1399     for word in words:
1400         word_size = len(word)
1401         if word_size > max_length:
1402             if have_started:
1403                 s += line + '\n' + prefix
1404             s += word + '\n' + prefix
1405         else:
1406             if have_started:
1407                 new_length = len(line) + word_size + 1
1408                 if new_length > max_length:
1409                     s += line + '\n' + prefix
1410                     line = word
1411                 else:
1412                     line += ' ' + word
1413             else:
1414                 line = word
1415         have_started = 1
1416
1417     if have_started:
1418         s += line
1419
1420     return s
1421
1422 ################################################################################
1423
1424 def clean_symlink (src, dest, root):
1425     """
1426     Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1427     Returns fixed 'src'
1428     """
1429     src = src.replace(root, '', 1)
1430     dest = dest.replace(root, '', 1)
1431     dest = os.path.dirname(dest)
1432     new_src = '../' * len(dest.split('/'))
1433     return new_src + src
1434
1435 ################################################################################
1436
1437 def temp_filename(directory=None, prefix="dak", suffix=""):
1438     """
1439     Return a secure and unique filename by pre-creating it.
1440     If 'directory' is non-null, it will be the directory the file is pre-created in.
1441     If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
1442     If 'suffix' is non-null, the filename will end with it.
1443
1444     Returns a pair (fd, name).
1445     """
1446
1447     return tempfile.mkstemp(suffix, prefix, directory)
1448
1449 ################################################################################
1450
1451 def temp_dirname(parent=None, prefix="dak", suffix=""):
1452     """
1453     Return a secure and unique directory by pre-creating it.
1454     If 'parent' is non-null, it will be the directory the directory is pre-created in.
1455     If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
1456     If 'suffix' is non-null, the filename will end with it.
1457
1458     Returns a pathname to the new directory
1459     """
1460
1461     return tempfile.mkdtemp(suffix, prefix, parent)
1462
1463 ################################################################################
1464
1465 def is_email_alias(email):
1466     """ checks if the user part of the email is listed in the alias file """
1467     global alias_cache
1468     if alias_cache == None:
1469         aliasfn = which_alias_file()
1470         alias_cache = set()
1471         if aliasfn:
1472             for l in open(aliasfn):
1473                 alias_cache.add(l.split(':')[0])
1474     uid = email.split('@')[0]
1475     return uid in alias_cache
1476
1477 ################################################################################
1478
1479 def get_changes_files(dir):
1480     """
1481     Takes a directory and lists all .changes files in it (as well as chdir'ing
1482     to the directory; this is due to broken behaviour on the part of p-u/p-a
1483     when you're not in the right place)
1484
1485     Returns a list of filenames
1486     """
1487     try:
1488         # Much of the rest of p-u/p-a depends on being in the right place
1489         os.chdir(dir)
1490         changes_files = [x for x in os.listdir(dir) if x.endswith('.changes')]
1491     except OSError, e:
1492         fubar("Failed to read list from directory %s (%s)" % (dir, e))
1493
1494     return changes_files
1495
1496 ################################################################################
1497
1498 apt_pkg.init()
1499
1500 Cnf = apt_pkg.newConfiguration()
1501 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1502
1503 if which_conf_file() != default_config:
1504     apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1505
1506 ###############################################################################