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