utils.py 49.3 KB
Newer Older
1
#!/usr/bin/env python
2
# vim:set et ts=4 sw=4:
3

J
Joerg Jaspert 已提交
4
"""Utility functions
5

J
Joerg Jaspert 已提交
6 7 8 9
@contact: Debian FTP Master <ftpmaster@debian.org>
@copyright: 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
@license: GNU General Public License version 2 or later
"""
J
James Troup 已提交
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

J
Joerg Jaspert 已提交
25 26 27 28 29 30 31 32 33 34 35 36
import codecs
import commands
import email.Header
import os
import pwd
import select
import socket
import shutil
import sys
import tempfile
import traceback
import stat
37
import apt_pkg
J
James Troup 已提交
38
import database
J
Joerg Jaspert 已提交
39
import time
J
Joerg Jaspert 已提交
40
import re
J
Joerg Jaspert 已提交
41
import email as modemail
J
Joerg Jaspert 已提交
42
from dak_exceptions import *
M
Mark Hymers 已提交
43 44
from regexes import re_html_escaping, html_escaping, re_single_line_field, \
                    re_multi_line_field, re_srchasver, re_verwithext, \
J
Joerg Jaspert 已提交
45
                    re_parse_maintainer, re_taint_free, re_gpg_uid, re_re_mark
46 47

################################################################################
J
James Troup 已提交
48

J
Joerg Jaspert 已提交
49 50
default_config = "/etc/dak/dak.conf"     #: default dak config, defines host properties
default_apt_config = "/etc/dak/apt.conf" #: default apt config, not normally used
J
James Troup 已提交
51

J
Joerg Jaspert 已提交
52 53
alias_cache = None        #: Cache for email alias checks
key_uid_email_cache = {}  #: Cache for email addresses from gpg key uids
T
Thomas Viehmann 已提交
54

55 56
# (hashname, function, earliest_changes_version)
known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
J
Joerg Jaspert 已提交
57
                ("sha256", apt_pkg.sha256sum, (1, 8))] #: hashes we accept for entries in .changes/.dsc
58

59
################################################################################
J
James Troup 已提交
60

J
Joerg Jaspert 已提交
61
def html_escape(s):
J
Joerg Jaspert 已提交
62
    """ Escape html chars """
J
Joerg Jaspert 已提交
63 64 65 66
    return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)

################################################################################

67
def open_file(filename, mode='r'):
J
Joerg Jaspert 已提交
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
    """
    Open C{file}, return fileobject.

    @type filename: string
    @param filename: path/filename to open

    @type mode: string
    @param mode: open mode

    @rtype: fileobject
    @return: open fileobject

    @raise CantOpenError: If IOError is raised by open, reraise it as CantOpenError.

    """
J
James Troup 已提交
83
    try:
84
        f = open(filename, mode)
J
James Troup 已提交
85
    except IOError:
J
Joerg Jaspert 已提交
86
        raise CantOpenError, filename
J
James Troup 已提交
87 88
    return f

89
################################################################################
J
James Troup 已提交
90

91 92
def our_raw_input(prompt=""):
    if prompt:
93 94
        sys.stdout.write(prompt)
    sys.stdout.flush()
J
James Troup 已提交
95
    try:
96 97
        ret = raw_input()
        return ret
J
James Troup 已提交
98
    except EOFError:
99 100
        sys.stderr.write("\nUser interrupt (^D).\n")
        raise SystemExit
J
James Troup 已提交
101

102
################################################################################
J
James Troup 已提交
103

J
James Troup 已提交
104
def extract_component_from_section(section):
105
    component = ""
J
James Troup 已提交
106

107
    if section.find('/') != -1:
108
        component = section.split('/')[0]
109 110

    # Expand default component
J
James Troup 已提交
111
    if component == "":
112
        if Cnf.has_key("Component::%s" % section):
113
            component = section
114
        else:
115
            component = "main"
J
James Troup 已提交
116

117
    return (section, component)
J
James Troup 已提交
118

119 120
################################################################################

121
def parse_deb822(contents, signing_rules=0):
122 123
    error = ""
    changes = {}
124

125 126
    # Split the lines in the input, keeping the linebreaks.
    lines = contents.splitlines(True)
J
James Troup 已提交
127

128
    if len(lines) == 0:
J
Joerg Jaspert 已提交
129
        raise ParseChangesError, "[Empty changes file]"
130

J
James Troup 已提交
131 132
    # Reindex by line number so we can easily verify the format of
    # .dsc files...
133 134
    index = 0
    indexed_lines = {}
J
James Troup 已提交
135
    for line in lines:
136 137
        index += 1
        indexed_lines[index] = line[:-1]
J
James Troup 已提交
138

139
    inside_signature = 0
J
James Troup 已提交
140

141 142 143
    num_of_lines = len(indexed_lines.keys())
    index = 0
    first = -1
144
    while index < num_of_lines:
145 146
        index += 1
        line = indexed_lines[index]
J
James Troup 已提交
147
        if line == "":
148
            if signing_rules == 1:
149
                index += 1
150
                if index > num_of_lines:
J
Joerg Jaspert 已提交
151
                    raise InvalidDscError, index
152
                line = indexed_lines[index]
153
                if not line.startswith("-----BEGIN PGP SIGNATURE"):
J
Joerg Jaspert 已提交
154
                    raise InvalidDscError, index
155 156
                inside_signature = 0
                break
157
            else:
158
                continue
159
        if line.startswith("-----BEGIN PGP SIGNATURE"):
160
            break
161
        if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
162
            inside_signature = 1
163
            if signing_rules == 1:
164
                while index < num_of_lines and line != "":
165 166 167
                    index += 1
                    line = indexed_lines[index]
            continue
168
        # If we're not inside the signed data, don't process anything
169
        if signing_rules >= 0 and not inside_signature:
170 171
            continue
        slf = re_single_line_field.match(line)
J
James Troup 已提交
172
        if slf:
173 174
            field = slf.groups()[0].lower()
            changes[field] = slf.groups()[1]
175
            first = 1
176
            continue
J
James Troup 已提交
177
        if line == " .":
178 179 180
            changes[field] += '\n'
            continue
        mlf = re_multi_line_field.match(line)
J
James Troup 已提交
181
        if mlf:
182
            if first == -1:
J
Joerg Jaspert 已提交
183
                raise ParseChangesError, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
184
            if first == 1 and changes[field] != "":
185 186
                changes[field] += '\n'
            first = 0
187
            changes[field] += mlf.groups()[0] + '\n'
188
            continue
189
        error += line
J
James Troup 已提交
190

191
    if signing_rules == 1 and inside_signature:
J
Joerg Jaspert 已提交
192
        raise InvalidDscError, index
J
James Troup 已提交
193

194
    changes["filecontents"] = "".join(lines)
J
James Troup 已提交
195

196 197
    if changes.has_key("source"):
        # Strip the source version in brackets from the source field,
198
        # put it in the "source-version" field instead.
199
        srcver = re_srchasver.search(changes["source"])
200
        if srcver:
201
            changes["source"] = srcver.group(1)
202
            changes["source-version"] = srcver.group(2)
203

204
    if error:
J
Joerg Jaspert 已提交
205
        raise ParseChangesError, error
J
James Troup 已提交
206

207
    return changes
J
James Troup 已提交
208

209
################################################################################
J
James Troup 已提交
210

211
def parse_changes(filename, signing_rules=0):
J
Joerg Jaspert 已提交
212 213 214 215
    """
    Parses a changes file and returns a dictionary where each field is a
    key.  The mandatory first argument is the filename of the .changes
    file.
216

J
Joerg Jaspert 已提交
217
    signing_rules is an optional argument:
218

J
Joerg Jaspert 已提交
219 220 221 222
      - If signing_rules == -1, no signature is required.
      - If signing_rules == 0 (the default), a signature is required.
      - If signing_rules == 1, it turns on the same strict format checking
        as dpkg-source.
223

J
Joerg Jaspert 已提交
224
    The rules for (signing_rules == 1)-mode are:
225

J
Joerg Jaspert 已提交
226 227
      - The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
        followed by any PGP header data and must end with a blank line.
228

J
Joerg Jaspert 已提交
229 230 231
      - The data section must end with a blank line and must be followed by
        "-----BEGIN PGP SIGNATURE-----".
    """
232 233 234 235 236 237 238 239 240 241 242 243 244 245

    changes_in = open_file(filename)
    content = changes_in.read()
    changes_in.close()
    return parse_deb822(content, signing_rules)

################################################################################

def hash_key(hashname):
    return '%ssum' % hashname

################################################################################

def create_hash(where, files, hashname, hashfunc):
J
Joerg Jaspert 已提交
246 247
    """
    create_hash extends the passed files dict with the given hash by
248
    iterating over all files on disk and passing them to the hashing
J
Joerg Jaspert 已提交
249 250
    function given.
    """
251

252
    rejmsg = []
253
    for f in files.keys():
254 255 256 257 258
        try:
            file_handle = open_file(f)
        except CantOpenError:
            rejmsg.append("Could not open file %s for checksumming" % (f))

259
        files[f][hash_key(hashname)] = hashfunc(file_handle)
260

261
        file_handle.close()
262 263 264 265
    return rejmsg

################################################################################

266
def check_hash(where, files, hashname, hashfunc):
J
Joerg Jaspert 已提交
267 268
    """
    check_hash checks the given hash in the files dict against the actual
269
    files on disk.  The hash values need to be present consistently in
J
Joerg Jaspert 已提交
270 271
    all file entries.  It does not modify its input in any way.
    """
272

273 274
    rejmsg = []
    for f in files.keys():
275
        file_handle = None
276
        try:
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
            try:
                file_handle = open_file(f)
    
                # Check for the hash entry, to not trigger a KeyError.
                if not files[f].has_key(hash_key(hashname)):
                    rejmsg.append("%s: misses %s checksum in %s" % (f, hashname,
                        where))
                    continue
    
                # Actually check the hash for correctness.
                if hashfunc(file_handle) != files[f][hash_key(hashname)]:
                    rejmsg.append("%s: %s check failed in %s" % (f, hashname,
                        where))
            except CantOpenError:
                # TODO: This happens when the file is in the pool.
J
Shutup  
Joerg Jaspert 已提交
292
                # warn("Cannot open file %s" % f)
293 294
                continue
        finally:
295 296
            if file_handle:
                file_handle.close()
297
    return rejmsg
298

299 300 301
################################################################################

def check_size(where, files):
J
Joerg Jaspert 已提交
302 303 304 305
    """
    check_size checks the file sizes in the passed files dict against the
    files on disk.
    """
306 307 308

    rejmsg = []
    for f in files.keys():
309 310 311 312 313 314 315 316 317
        try:
            entry = os.stat(f)
        except OSError, exc:
            if exc.errno == 2:
                # TODO: This happens when the file is in the pool.
                continue
            raise

        actual_size = entry[stat.ST_SIZE]
318
        size = int(files[f]["size"])
319 320 321
        if size != actual_size:
            rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
                   % (f, actual_size, size, where))
322 323 324 325 326
    return rejmsg

################################################################################

def check_hash_fields(what, manifest):
J
Joerg Jaspert 已提交
327 328 329 330
    """
    check_hash_fields ensures that there are no checksum fields in the
    given dict that we do not know about.
    """
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362

    rejmsg = []
    hashes = map(lambda x: x[0], known_hashes)
    for field in manifest:
        if field.startswith("checksums-"):
            hashname = field.split("-",1)[1]
            if hashname not in hashes:
                rejmsg.append("Unsupported checksum field for %s "\
                    "in %s" % (hashname, what))
    return rejmsg

################################################################################

def _ensure_changes_hash(changes, format, version, files, hashname, hashfunc):
    if format >= version:
        # The version should contain the specified hash.
        func = check_hash

        # Import hashes from the changes
        rejmsg = parse_checksums(".changes", files, changes, hashname)
        if len(rejmsg) > 0:
            return rejmsg
    else:
        # We need to calculate the hash because it can't possibly
        # be in the file.
        func = create_hash
    return func(".changes", files, hashname, hashfunc)

# We could add the orig which might be in the pool to the files dict to
# access the checksums easily.

def _ensure_dsc_hash(dsc, dsc_files, hashname, hashfunc):
J
Joerg Jaspert 已提交
363 364
    """
    ensure_dsc_hashes' task is to ensure that each and every *present* hash
365
    in the dsc is correct, i.e. identical to the changes file and if necessary
J
Joerg Jaspert 已提交
366 367
    the pool.  The latter task is delegated to check_hash.
    """
368

369 370 371 372 373 374 375
    rejmsg = []
    if not dsc.has_key('Checksums-%s' % (hashname,)):
        return rejmsg
    # Import hashes from the dsc
    parse_checksums(".dsc", dsc_files, dsc, hashname)
    # And check it...
    rejmsg.extend(check_hash(".dsc", dsc_files, hashname, hashfunc))
376 377 378 379
    return rejmsg

################################################################################

M
Mark Hymers 已提交
380
def ensure_hashes(changes, dsc, files, dsc_files):
381 382 383 384
    rejmsg = []

    # Make sure we recognise the format of the Files: field in the .changes
    format = changes.get("format", "0.0").split(".", 1)
M
Mark Hymers 已提交
385 386 387 388 389
    if len(format) == 2:
        format = int(format[0]), int(format[1])
    else:
        format = int(float(format[0])), 0

390 391 392 393 394 395 396 397 398 399 400 401 402
    # We need to deal with the original changes blob, as the fields we need
    # might not be in the changes dict serialised into the .dak anymore.
    orig_changes = parse_deb822(changes['filecontents'])

    # Copy the checksums over to the current changes dict.  This will keep
    # the existing modifications to it intact.
    for field in orig_changes:
        if field.startswith('checksums-'):
            changes[field] = orig_changes[field]

    # Check for unsupported hashes
    rejmsg.extend(check_hash_fields(".changes", changes))
    rejmsg.extend(check_hash_fields(".dsc", dsc))
403 404 405

    # We have to calculate the hash if we have an earlier changes version than
    # the hash appears in rather than require it exist in the changes file
406 407 408 409 410 411
    for hashname, hashfunc, version in known_hashes:
        rejmsg.extend(_ensure_changes_hash(changes, format, version, files,
            hashname, hashfunc))
        if "source" in changes["architecture"]:
            rejmsg.extend(_ensure_dsc_hash(dsc, dsc_files, hashname,
                hashfunc))
412

413
    return rejmsg
414

415 416 417 418 419
def parse_checksums(where, files, manifest, hashname):
    rejmsg = []
    field = 'checksums-%s' % hashname
    if not field in manifest:
        return rejmsg
J
Joerg Jaspert 已提交
420
    for line in manifest[field].split('\n'):
421 422
        if not line:
            break
J
Joerg Jaspert 已提交
423 424
        checksum, size, checkfile = line.strip().split(' ')
        if not files.has_key(checkfile):
425 426 427 428
        # TODO: check for the file's entry in the original files dict, not
        # the one modified by (auto)byhand and other weird stuff
        #    rejmsg.append("%s: not present in files but in checksums-%s in %s" %
        #        (file, hashname, where))
429
            continue
J
Joerg Jaspert 已提交
430
        if not files[checkfile]["size"] == size:
431
            rejmsg.append("%s: size differs for files and checksums-%s entry "\
J
Joerg Jaspert 已提交
432
                "in %s" % (checkfile, hashname, where))
433
            continue
J
Joerg Jaspert 已提交
434
        files[checkfile][hash_key(hashname)] = checksum
435 436
    for f in files.keys():
        if not files[f].has_key(hash_key(hashname)):
J
Joerg Jaspert 已提交
437
            rejmsg.append("%s: no entry in checksums-%s in %s" % (checkfile,
438
                hashname, where))
439 440 441 442
    return rejmsg

################################################################################

J
James Troup 已提交
443 444
# Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl

445
def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
446
    files = {}
447 448

    # Make sure we have a Files: field to parse...
449
    if not changes.has_key(field):
J
Joerg Jaspert 已提交
450
        raise NoFilesFieldError
451 452

    # Make sure we recognise the format of the Files: field
453 454
    format = re_verwithext.search(changes.get("format", "0.0"))
    if not format:
J
Joerg Jaspert 已提交
455
        raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
456 457 458 459

    format = format.groups()
    if format[1] == None:
        format = int(float(format[0])), 0, format[2]
460
    else:
461 462 463
        format = int(format[0]), int(format[1]), format[2]
    if format[2] == None:
        format = format[:2]
464 465

    if is_a_dsc:
466 467 468 469
        # format = (1,0) are the only formats we currently accept,
        # format = (0,0) are missing format headers of which we still
        # have some in the archive.
        if format != (1,0) and format != (0,0):
J
Joerg Jaspert 已提交
470
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
471 472
    else:
        if (format < (1,5) or format > (1,8)):
J
Joerg Jaspert 已提交
473
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
474
        if field != "files" and format < (1,8):
J
Joerg Jaspert 已提交
475
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
476 477

    includes_section = (not is_a_dsc) and field == "files"
J
James Troup 已提交
478

479
    # Parse each entry/line:
480
    for i in changes[field].split('\n'):
481
        if not i:
482 483 484
            break
        s = i.split()
        section = priority = ""
J
sync  
James Troup 已提交
485
        try:
486
            if includes_section:
487
                (md5, size, section, priority, name) = s
488 489
            else:
                (md5, size, name) = s
J
sync  
James Troup 已提交
490
        except ValueError:
J
Joerg Jaspert 已提交
491
            raise ParseChangesError, i
J
James Troup 已提交
492

493
        if section == "":
494
            section = "-"
495
        if priority == "":
496
            priority = "-"
J
James Troup 已提交
497

498
        (section, component) = extract_component_from_section(section)
J
James Troup 已提交
499

500
        files[name] = Dict(size=size, section=section,
501
                           priority=priority, component=component)
502
        files[name][hashname] = md5
J
James Troup 已提交
503 504 505

    return files

506
################################################################################
J
James Troup 已提交
507

508
def force_to_utf8(s):
J
Joerg Jaspert 已提交
509 510 511 512
    """
    Forces a string to UTF-8.  If the string isn't already UTF-8,
    it's assumed to be ISO-8859-1.
    """
513
    try:
514 515
        unicode(s, 'utf-8')
        return s
516
    except UnicodeError:
517 518
        latin1_s = unicode(s,'iso8859-1')
        return latin1_s.encode('utf-8')
519 520

def rfc2047_encode(s):
J
Joerg Jaspert 已提交
521 522 523 524
    """
    Encodes a (header) string per RFC2047 if necessary.  If the
    string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1.
    """
525
    try:
526
        codecs.lookup('ascii')[1](s)
527
        return s
528
    except UnicodeError:
529
        pass
530
    try:
531
        codecs.lookup('utf-8')[1](s)
532 533
        h = email.Header.Header(s, 'utf-8', 998)
        return str(h)
534
    except UnicodeError:
535 536
        h = email.Header.Header(s, 'iso-8859-1', 998)
        return str(h)
537 538 539 540 541 542

################################################################################

# <Culus> 'The standard sucks, but my tool is supposed to interoperate
#          with it. I know - I'll fix the suckage and make things
#          incompatible!'
J
James Troup 已提交
543

J
James Troup 已提交
544
def fix_maintainer (maintainer):
J
Joerg Jaspert 已提交
545 546 547 548 549 550 551 552 553 554 555 556
    """
    Parses a Maintainer or Changed-By field and returns:
      1. an RFC822 compatible version,
      2. an RFC2047 compatible version,
      3. the name
      4. the email

    The name is forced to UTF-8 for both 1. and 3..  If the name field
    contains '.' or ',' (as allowed by Debian policy), 1. and 2. are
    switched to 'email (name)' format.

    """
557 558
    maintainer = maintainer.strip()
    if not maintainer:
559
        return ('', '', '', '')
560

561
    if maintainer.find("<") == -1:
562 563
        email = maintainer
        name = ""
564
    elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
565 566
        email = maintainer[1:-1]
        name = ""
567
    else:
568
        m = re_parse_maintainer.match(maintainer)
569 570
        if not m:
            raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
571 572
        name = m.group(1)
        email = m.group(2)
573 574

    # Get an RFC2047 compliant version of the name
575
    rfc2047_name = rfc2047_encode(name)
576 577

    # Force the name to be UTF-8
578
    name = force_to_utf8(name)
579 580

    if name.find(',') != -1 or name.find('.') != -1:
581 582
        rfc822_maint = "%s (%s)" % (email, name)
        rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
583
    else:
584 585
        rfc822_maint = "%s <%s>" % (name, email)
        rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
586 587 588 589

    if email.find("@") == -1 and email.find("buildd_") != 0:
        raise ParseMaintError, "No @ found in email address part."

590
    return (rfc822_maint, rfc2047_maint, name, email)
J
James Troup 已提交
591

592
################################################################################
J
James Troup 已提交
593

594
def send_mail (message, filename=""):
J
Joerg Jaspert 已提交
595 596 597
    """sendmail wrapper, takes _either_ a message string or a file as arguments"""

    # If we've been passed a string dump it into a temporary file
598
    if message:
J
Joerg Jaspert 已提交
599
        (fd, filename) = tempfile.mkstemp()
600 601 602
        os.write (fd, message)
        os.close (fd)

J
Joerg Jaspert 已提交
603 604 605 606 607 608 609 610 611 612
    if Cnf.has_key("Dinstall::MailWhiteList") and \
           Cnf["Dinstall::MailWhiteList"] != "":
        message_in = open_file(filename)
        message_raw = modemail.message_from_file(message_in)
        message_in.close();

        whitelist = [];
        whitelist_in = open_file(Cnf["Dinstall::MailWhiteList"])
        try:
            for line in whitelist_in:
J
Joerg Jaspert 已提交
613 614
                if re_re_mark.match(line):
                    whitelist.append(re.compile(re_re_mark.sub("", line.strip(), 1)))
J
Joerg Jaspert 已提交
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
                else:
                    whitelist.append(re.compile(re.escape(line.strip())))
        finally:
            whitelist_in.close()

        # Fields to check.
        fields = ["To", "Bcc", "Cc"]
        for field in fields:
            # Check each field
            value = message_raw.get(field, None)
            if value != None:
                match = [];
                for item in value.split(","):
                    (rfc822_maint, rfc2047_maint, name, email) = fix_maintainer(item.strip())
                    mail_whitelisted = 0
                    for wr in whitelist:
                        if wr.match(email):
                            mail_whitelisted = 1
                            break
                    if not mail_whitelisted:
                        print "Skipping %s since it's not in %s" % (item, Cnf["Dinstall::MailWhiteList"])
                        continue
                    match.append(item)

                # Doesn't have any mail in whitelist so remove the header
                if len(match) == 0:
                    del message_raw[field]
                else:
                    message_raw.replace_header(field, string.join(match, ", "))

        # Change message fields in order if we don't have a To header
        if not message_raw.has_key("To"):
            fields.reverse()
            for field in fields:
                if message_raw.has_key(field):
                    message_raw[fields[-1]] = message_raw[field]
                    del message_raw[field]
                    break
            else:
                # Clean up any temporary files
                # and return, as we removed all recipients.
                if message:
                    os.unlink (filename);
                return;

        fd = os.open(filename, os.O_RDWR|os.O_EXCL, 0700);
        os.write (fd, message_raw.as_string(True));
        os.close (fd);

664 665 666
    # Invoke sendmail
    (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
    if (result != 0):
J
Joerg Jaspert 已提交
667
        raise SendmailFailedError, output
668 669 670 671

    # Clean up any temporary files
    if message:
        os.unlink (filename)
J
James Troup 已提交
672

673
################################################################################
J
James Troup 已提交
674 675

def poolify (source, component):
676
    if component:
677
        component += '/'
J
James Troup 已提交
678
    if source[:3] == "lib":
679
        return component + source[:4] + '/' + source + '/'
J
James Troup 已提交
680
    else:
681
        return component + source[:1] + '/' + source + '/'
J
James Troup 已提交
682

683
################################################################################
J
James Troup 已提交
684

685
def move (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
686
    if os.path.exists(dest) and os.path.isdir(dest):
687
        dest_dir = dest
J
James Troup 已提交
688
    else:
689
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
690
    if not os.path.exists(dest_dir):
691 692 693
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
694
    #print "Moving %s to %s..." % (src, dest)
J
James Troup 已提交
695
    if os.path.exists(dest) and os.path.isdir(dest):
696
        dest += '/' + os.path.basename(src)
697 698 699
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
700
            fubar("Can't move %s to %s - file already exists." % (src, dest))
701 702
        else:
            if not os.access(dest, os.W_OK):
703 704 705 706
                fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
    shutil.copy2(src, dest)
    os.chmod(dest, perms)
    os.unlink(src)
J
James Troup 已提交
707

708
def copy (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
709
    if os.path.exists(dest) and os.path.isdir(dest):
710
        dest_dir = dest
J
James Troup 已提交
711
    else:
712
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
713
    if not os.path.exists(dest_dir):
714 715 716
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
717
    #print "Copying %s to %s..." % (src, dest)
J
James Troup 已提交
718
    if os.path.exists(dest) and os.path.isdir(dest):
719
        dest += '/' + os.path.basename(src)
720 721 722
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
J
Joerg Jaspert 已提交
723
            raise FileExistsError
724 725
        else:
            if not os.access(dest, os.W_OK):
J
Joerg Jaspert 已提交
726
                raise CantOverwriteError
727 728
    shutil.copy2(src, dest)
    os.chmod(dest, perms)
J
James Troup 已提交
729

730
################################################################################
J
James Troup 已提交
731 732

def where_am_i ():
733 734
    res = socket.gethostbyaddr(socket.gethostname())
    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
735
    if database_hostname:
736
        return database_hostname
J
James Troup 已提交
737
    else:
738
        return res[0]
J
James Troup 已提交
739 740

def which_conf_file ():
741
    res = socket.gethostbyaddr(socket.gethostname())
742
    if Cnf.get("Config::" + res[0] + "::DakConfig"):
743
        return Cnf["Config::" + res[0] + "::DakConfig"]
J
James Troup 已提交
744
    else:
745
        return default_config
746 747

def which_apt_conf_file ():
748
    res = socket.gethostbyaddr(socket.gethostname())
749
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
750
        return Cnf["Config::" + res[0] + "::AptConfig"]
751
    else:
752
        return default_apt_config
753

T
Thomas Viehmann 已提交
754 755 756 757 758 759 760 761
def which_alias_file():
    hostname = socket.gethostbyaddr(socket.gethostname())[0]
    aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
    if os.path.exists(aliasfn):
        return aliasfn
    else:
        return None

762
################################################################################
J
James Troup 已提交
763

764 765 766 767
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
768 769
    s = s.replace('+', '\\\\+')
    s = s.replace('.', '\\\\.')
770 771
    return s

772
################################################################################
773

774
def TemplateSubst(map, filename):
J
Joerg Jaspert 已提交
775
    """ Perform a substition of template """
J
Joerg Jaspert 已提交
776 777
    templatefile = open_file(filename)
    template = templatefile.read()
778
    for x in map.keys():
779
        template = template.replace(x,map[x])
J
Joerg Jaspert 已提交
780
    templatefile.close()
781
    return template
J
James Troup 已提交
782

783
################################################################################
J
James Troup 已提交
784 785

def fubar(msg, exit_code=1):
786 787
    sys.stderr.write("E: %s\n" % (msg))
    sys.exit(exit_code)
J
James Troup 已提交
788 789

def warn(msg):
790
    sys.stderr.write("W: %s\n" % (msg))
J
James Troup 已提交
791

792
################################################################################
J
James Troup 已提交
793

J
James Troup 已提交
794 795 796
# Returns the user name with a laughable attempt at rfc822 conformancy
# (read: removing stray periods).
def whoami ():
797
    return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
J
James Troup 已提交
798

799
################################################################################
J
James Troup 已提交
800

J
James Troup 已提交
801
def size_type (c):
802
    t  = " B"
803
    if c > 10240:
804 805
        c = c / 1024
        t = " KB"
806
    if c > 10240:
807 808
        c = c / 1024
        t = " MB"
J
James Troup 已提交
809
    return ("%d%s" % (c, t))
810 811 812 813

################################################################################

def cc_fix_changes (changes):
814
    o = changes.get("architecture", "")
815
    if o:
816 817
        del changes["architecture"]
    changes["architecture"] = {}
818
    for j in o.split():
819
        changes["architecture"][j] = 1
820 821

def changes_compare (a, b):
J
Joerg Jaspert 已提交
822
    """ Sort by source name, source version, 'have source', and then by filename """
823
    try:
824
        a_changes = parse_changes(a)
825
    except:
826
        return -1
827 828

    try:
829
        b_changes = parse_changes(b)
830
    except:
831
        return 1
J
James Troup 已提交
832

833 834
    cc_fix_changes (a_changes)
    cc_fix_changes (b_changes)
835 836

    # Sort by source name
837 838 839
    a_source = a_changes.get("source")
    b_source = b_changes.get("source")
    q = cmp (a_source, b_source)
840
    if q:
841
        return q
842 843

    # Sort by source version
844 845 846
    a_version = a_changes.get("version", "0")
    b_version = b_changes.get("version", "0")
    q = apt_pkg.VersionCompare(a_version, b_version)
847
    if q:
848
        return q
849

850
    # Sort by 'have source'
851 852
    a_has_source = a_changes["architecture"].get("source")
    b_has_source = b_changes["architecture"].get("source")
853
    if a_has_source and not b_has_source:
854
        return -1
855
    elif b_has_source and not a_has_source:
856
        return 1
857

858
    # Fall back to sort by filename
859
    return cmp(a, b)
860 861

################################################################################
862 863

def find_next_free (dest, too_many=100):
864 865
    extra = 0
    orig_dest = dest
866
    while os.path.exists(dest) and extra < too_many:
867 868
        dest = orig_dest + '.' + repr(extra)
        extra += 1
869
    if extra >= too_many:
J
Joerg Jaspert 已提交
870
        raise NoFreeFilenameError
871
    return dest
J
James Troup 已提交
872

873
################################################################################
874 875

def result_join (original, sep = '\t'):
J
Joerg Jaspert 已提交
876
    resultlist = []
877 878
    for i in xrange(len(original)):
        if original[i] == None:
J
Joerg Jaspert 已提交
879
            resultlist.append("")
880
        else:
J
Joerg Jaspert 已提交
881 882
            resultlist.append(original[i])
    return sep.join(resultlist)
883

884 885
################################################################################

886
def prefix_multi_line_string(str, prefix, include_blank_lines=0):
887
    out = ""
888
    for line in str.split('\n'):
889
        line = line.strip()
890
        if line or include_blank_lines:
891
            out += "%s%s\n" % (prefix, line)
892 893
    # Strip trailing new line
    if out:
894 895
        out = out[:-1]
    return out
896

897
################################################################################
898

899
def validate_changes_file_arg(filename, require_changes=1):
J
Joerg Jaspert 已提交
900 901 902 903 904 905 906 907 908 909 910 911 912 913
    """
    'filename' is either a .changes or .dak file.  If 'filename' is a
    .dak file, it's changed to be the corresponding .changes file.  The
    function then checks if the .changes file a) exists and b) is
    readable and returns the .changes filename if so.  If there's a
    problem, the next action depends on the option 'require_changes'
    argument:

      - If 'require_changes' == -1, errors are ignored and the .changes
        filename is returned.
      - If 'require_changes' == 0, a warning is given and 'None' is returned.
      - If 'require_changes' == 1, a fatal error is raised.

    """
914
    error = None
915

916
    orig_filename = filename
917
    if filename.endswith(".dak"):
918
        filename = filename[:-4]+".changes"
919

920
    if not filename.endswith(".changes"):
921
        error = "invalid file type; not a changes file"
922
    else:
923 924
        if not os.access(filename,os.R_OK):
            if os.path.exists(filename):
925
                error = "permission denied"
926
            else:
927
                error = "file not found"
928 929

    if error:
930
        if require_changes == 1:
931
            fubar("%s: %s." % (orig_filename, error))
932
        elif require_changes == 0:
933 934
            warn("Skipping %s - %s" % (orig_filename, error))
            return None
935
        else: # We only care about the .dak file
936
            return filename
937
    else:
938
        return filename
939 940 941

################################################################################

942
def real_arch(arch):
943
    return (arch != "source" and arch != "all")
944 945 946

################################################################################

947
def join_with_commas_and(list):
948 949 950
    if len(list) == 0: return "nothing"
    if len(list) == 1: return list[0]
    return ", ".join(list[:-1]) + " and " + list[-1]
951 952 953

################################################################################

954
def pp_deps (deps):
955
    pp_deps = []
956
    for atom in deps:
957
        (pkg, version, constraint) = atom
958
        if constraint:
959
            pp_dep = "%s (%s %s)" % (pkg, constraint, version)
960
        else:
961 962 963
            pp_dep = pkg
        pp_deps.append(pp_dep)
    return " |".join(pp_deps)
964 965 966

################################################################################

967
def get_conf():
968
    return Cnf
969 970 971

################################################################################

972
def parse_args(Options):
J
Joerg Jaspert 已提交
973
    """ Handle -a, -c and -s arguments; returns them as SQL constraints """
974 975
    # Process suite
    if Options["Suite"]:
976
        suite_ids_list = []
977
        for suite in split_args(Options["Suite"]):
J
James Troup 已提交
978
            suite_id = database.get_suite_id(suite)
979
            if suite_id == -1:
980
                warn("suite '%s' not recognised." % (suite))
981
            else:
982
                suite_ids_list.append(suite_id)
983
        if suite_ids_list:
984
            con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
985
        else:
986
            fubar("No valid suite given.")
987
    else:
988
        con_suites = ""
989 990 991

    # Process component
    if Options["Component"]:
992
        component_ids_list = []
993
        for component in split_args(Options["Component"]):
J
James Troup 已提交
994
            component_id = database.get_component_id(component)
995
            if component_id == -1:
996
                warn("component '%s' not recognised." % (component))
997
            else:
998
                component_ids_list.append(component_id)
999
        if component_ids_list:
1000
            con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
1001
        else:
1002
            fubar("No valid component given.")
1003
    else:
1004
        con_components = ""
1005 1006

    # Process architecture
1007
    con_architectures = ""
1008
    if Options["Architecture"]:
1009 1010
        arch_ids_list = []
        check_source = 0
1011
        for architecture in split_args(Options["Architecture"]):
1012
            if architecture == "source":
1013
                check_source = 1
1014
            else:
J
James Troup 已提交
1015
                architecture_id = database.get_architecture_id(architecture)
1016
                if architecture_id == -1:
1017
                    warn("architecture '%s' not recognised." % (architecture))
1018
                else:
1019
                    arch_ids_list.append(architecture_id)
1020
        if arch_ids_list:
1021
            con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
1022 1023
        else:
            if not check_source:
1024
                fubar("No valid architecture given.")
1025
    else:
1026
        check_source = 1
1027

1028
    return (con_suites, con_architectures, con_components, check_source)
1029 1030 1031

################################################################################

1032 1033 1034 1035
# Inspired(tm) by Bryn Keller's print_exc_plus (See
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)

def print_exc():
1036
    tb = sys.exc_info()[2]
1037
    while tb.tb_next:
1038 1039 1040
        tb = tb.tb_next
    stack = []
    frame = tb.tb_frame
1041
    while frame:
1042 1043 1044 1045
        stack.append(frame)
        frame = frame.f_back
    stack.reverse()
    traceback.print_exc()
1046 1047 1048
    for frame in stack:
        print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
                                             frame.f_code.co_filename,
1049
                                             frame.f_lineno)
1050
        for key, value in frame.f_locals.items():
1051
            print "\t%20s = " % key,
1052
            try:
1053
                print value
1054
            except:
1055
                print "<unable to print>"
1056 1057 1058

################################################################################

J
James Troup 已提交
1059 1060
def try_with_debug(function):
    try:
1061
        function()
J
James Troup 已提交
1062
    except SystemExit:
1063
        raise
J
James Troup 已提交
1064
    except:
1065
        print_exc()
J
James Troup 已提交
1066 1067 1068

################################################################################

J
James Troup 已提交
1069
def arch_compare_sw (a, b):
J
Joerg Jaspert 已提交
1070 1071 1072 1073 1074 1075
    """
    Function for use in sorting lists of architectures.

    Sorts normally except that 'source' dominates all others.
    """

J
James Troup 已提交
1076
    if a == "source" and b == "source":
1077
        return 0
J
James Troup 已提交
1078
    elif a == "source":
1079
        return -1
J
James Troup 已提交
1080
    elif b == "source":
1081
        return 1
J
James Troup 已提交
1082

1083
    return cmp (a, b)
J
James Troup 已提交
1084 1085 1086

################################################################################

1087
def split_args (s, dwim=1):
J
Joerg Jaspert 已提交
1088 1089 1090 1091 1092 1093 1094 1095
    """
    Split command line arguments which can be separated by either commas
    or whitespace.  If dwim is set, it will complain about string ending
    in comma since this usually means someone did 'dak ls -a i386, m68k
    foo' or something and the inevitable confusion resulting from 'm68k'
    being treated as an argument is undesirable.
    """

1096
    if s.find(",") == -1:
1097
        return s.split()
1098 1099
    else:
        if s[-1:] == "," and dwim:
1100 1101
            fubar("split_args: found trailing comma, spurious space maybe?")
        return s.split(",")
1102 1103 1104

################################################################################

1105 1106 1107 1108 1109
def Dict(**dict): return dict

########################################

def gpgv_get_status_output(cmd, status_read, status_write):
J
Joerg Jaspert 已提交
1110 1111 1112 1113 1114
    """
    Our very own version of commands.getouputstatus(), hacked to support
    gpgv's status fd.
    """

1115 1116 1117 1118 1119
    cmd = ['/bin/sh', '-c', cmd]
    p2cread, p2cwrite = os.pipe()
    c2pread, c2pwrite = os.pipe()
    errout, errin = os.pipe()
    pid = os.fork()
1120 1121
    if pid == 0:
        # Child
1122 1123 1124 1125 1126 1127
        os.close(0)
        os.close(1)
        os.dup(p2cread)
        os.dup(c2pwrite)
        os.close(2)
        os.dup(errin)
1128 1129 1130
        for i in range(3, 256):
            if i != status_write:
                try:
1131
                    os.close(i)
1132
                except:
1133
                    pass
1134
        try:
1135
            os.execvp(cmd[0], cmd)
1136
        finally:
1137
            os._exit(1)
1138

1139
    # Parent
1140
    os.close(p2cread)
1141 1142
    os.dup2(c2pread, c2pwrite)
    os.dup2(errout, errin)
1143

1144
    output = status = ""
1145
    while 1:
1146 1147
        i, o, e = select.select([c2pwrite, errin, status_read], [], [])
        more_data = []
1148
        for fd in i:
1149
            r = os.read(fd, 8196)
1150
            if len(r) > 0:
1151
                more_data.append(fd)
1152
                if fd == c2pwrite or fd == errin:
1153
                    output += r
1154
                elif fd == status_read:
1155
                    status += r
1156
                else:
1157
                    fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
1158 1159 1160
        if not more_data:
            pid, exit_status = os.waitpid(pid, 0)
            try:
1161 1162 1163 1164 1165 1166 1167
                os.close(status_write)
                os.close(status_read)
                os.close(c2pread)
                os.close(c2pwrite)
                os.close(p2cwrite)
                os.close(errin)
                os.close(errout)
1168
            except:
1169 1170
                pass
            break
1171

1172
    return output, status, exit_status
1173

1174
################################################################################
1175

1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192
def process_gpgv_output(status):
    # Process the status-fd output
    keywords = {}
    internal_error = ""
    for line in status.split('\n'):
        line = line.strip()
        if line == "":
            continue
        split = line.split()
        if len(split) < 2:
            internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
            continue
        (gnupg, keyword) = split[:2]
        if gnupg != "[GNUPG:]":
            internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
            continue
        args = split[2:]
1193
        if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
            internal_error += "found duplicate status token ('%s').\n" % (keyword)
            continue
        else:
            keywords[keyword] = args

    return (keywords, internal_error)

################################################################################

def retrieve_key (filename, keyserver=None, keyring=None):
J
Joerg Jaspert 已提交
1204 1205 1206 1207 1208
    """
    Retrieve the key that signed 'filename' from 'keyserver' and
    add it to 'keyring'.  Returns nothing on success, or an error message
    on error.
    """
1209 1210 1211 1212 1213

    # Defaults for keyserver and keyring
    if not keyserver:
        keyserver = Cnf["Dinstall::KeyServer"]
    if not keyring:
1214
        keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
1215 1216 1217 1218 1219 1220

    # Ensure the filename contains no shell meta-characters or other badness
    if not re_taint_free.match(filename):
        return "%s: tainted filename" % (filename)

    # Invoke gpgv on the file
J
Various  
Joerg Jaspert 已提交
1221
    status_read, status_write = os.pipe()
1222 1223
    cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
    (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
1224

1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248
    # Process the status-fd output
    (keywords, internal_error) = process_gpgv_output(status)
    if internal_error:
        return internal_error

    if not keywords.has_key("NO_PUBKEY"):
        return "didn't find expected NO_PUBKEY in gpgv status-fd output"

    fingerprint = keywords["NO_PUBKEY"][0]
    # XXX - gpg sucks.  You can't use --secret-keyring=/dev/null as
    # it'll try to create a lockfile in /dev.  A better solution might
    # be a tempfile or something.
    cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
          % (Cnf["Dinstall::SigningKeyring"])
    cmd += " --keyring %s --keyserver %s --recv-key %s" \
           % (keyring, keyserver, fingerprint)
    (result, output) = commands.getstatusoutput(cmd)
    if (result != 0):
        return "'%s' failed with exit code %s" % (cmd, result)

    return ""

################################################################################

1249 1250 1251 1252 1253 1254 1255 1256
def gpg_keyring_args(keyrings=None):
    if not keyrings:
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")

    return " ".join(["--keyring %s" % x for x in keyrings])

################################################################################

1257
def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
J
Joerg Jaspert 已提交
1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271
    """
    Check the signature of a file and return the fingerprint if the
    signature is valid or 'None' if it's not.  The first argument is the
    filename whose signature should be checked.  The second argument is a
    reject function and is called when an error is found.  The reject()
    function must allow for two arguments: the first is the error message,
    the second is an optional prefix string.  It's possible for reject()
    to be called more than once during an invocation of check_signature().
    The third argument is optional and is the name of the files the
    detached signature applies to.  The fourth argument is optional and is
    a *list* of keyrings to use.  'autofetch' can either be None, True or
    False.  If None, the default behaviour specified in the config will be
    used.
    """
1272 1273

    # Ensure the filename contains no shell meta-characters or other badness
1274
    if not re_taint_free.match(sig_filename):
1275 1276
        reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
        return None
1277 1278

    if data_filename and not re_taint_free.match(data_filename):
1279 1280
        reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
        return None
1281 1282

    if not keyrings:
1283
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1284

1285 1286 1287 1288 1289 1290 1291 1292 1293
    # Autofetch the signing key if that's enabled
    if autofetch == None:
        autofetch = Cnf.get("Dinstall::KeyAutoFetch")
    if autofetch:
        error_msg = retrieve_key(sig_filename)
        if error_msg:
            reject(error_msg)
            return None

1294
    # Build the command line
J
Various  
Joerg Jaspert 已提交
1295
    status_read, status_write = os.pipe()
1296 1297 1298
    cmd = "gpgv --status-fd %s %s %s %s" % (
        status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)

1299
    # Invoke gpgv on the file
1300
    (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1301 1302

    # Process the status-fd output
1303
    (keywords, internal_error) = process_gpgv_output(status)
1304 1305 1306

    # If we failed to parse the status-fd output, let's just whine and bail now
    if internal_error:
1307 1308 1309 1310
        reject("internal error while performing signature check on %s." % (sig_filename))
        reject(internal_error, "")
        reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
        return None
1311

1312
    bad = ""
1313 1314
    # Now check for obviously bad things in the processed output
    if keywords.has_key("KEYREVOKED"):
1315 1316
        reject("The key used to sign %s has been revoked." % (sig_filename))
        bad = 1
1317
    if keywords.has_key("BADSIG"):
1318 1319
        reject("bad signature on %s." % (sig_filename))
        bad = 1
1320
    if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1321 1322
        reject("failed to check signature on %s." % (sig_filename))
        bad = 1
1323
    if keywords.has_key("NO_PUBKEY"):
1324
        args = keywords["NO_PUBKEY"]
1325
        if len(args) >= 1:
1326 1327 1328
            key = args[0]
        reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
        bad = 1
1329
    if keywords.has_key("BADARMOR"):
1330 1331
        reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
        bad = 1
1332
    if keywords.has_key("NODATA"):
1333 1334
        reject("no signature found in %s." % (sig_filename))
        bad = 1
J
Joerg Jaspert 已提交
1335 1336 1337 1338
    if keywords.has_key("EXPKEYSIG"):
        args = keywords["EXPKEYSIG"]
        if len(args) >= 1:
            key = args[0]
1339
        reject("Signature made by expired key 0x%s" % (key))
J
Joerg Jaspert 已提交
1340
        bad = 1
1341
    if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1342
        args = keywords["KEYEXPIRED"]
J
Joerg Jaspert 已提交
1343
        expiredate=""
1344
        if len(args) >= 1:
J
Joerg Jaspert 已提交
1345 1346 1347 1348 1349 1350
            timestamp = args[0]
            if timestamp.count("T") == 0:
                expiredate = time.strftime("%Y-%m-%d", time.gmtime(timestamp))
            else:
                expiredate = timestamp
        reject("The key used to sign %s has expired on %s" % (sig_filename, expiredate))
1351
        bad = 1
1352 1353

    if bad:
1354
        return None
1355 1356 1357

    # Next check gpgv exited with a zero return code
    if exit_status:
1358
        reject("gpgv failed while checking %s." % (sig_filename))
1359
        if status.strip():
1360
            reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1361
        else:
1362 1363
            reject(prefix_multi_line_string(output, " [GPG output:] "), "")
        return None
1364 1365 1366

    # Sanity check the good stuff we expect
    if not keywords.has_key("VALIDSIG"):
1367 1368
        reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
        bad = 1
1369
    else:
1370
        args = keywords["VALIDSIG"]
1371
        if len(args) < 1:
1372 1373
            reject("internal error while checking signature on %s." % (sig_filename))
            bad = 1
1374
        else:
1375
            fingerprint = args[0]
1376
    if not keywords.has_key("GOODSIG"):
1377 1378
        reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
        bad = 1
1379
    if not keywords.has_key("SIG_ID"):
1380 1381
        reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
        bad = 1
1382 1383 1384 1385

    # Finally ensure there's not something we don't recognise
    known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
                          SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1386
                          NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1387 1388 1389

    for keyword in keywords.keys():
        if not known_keywords.has_key(keyword):
1390 1391
            reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
            bad = 1
1392 1393

    if bad:
1394
        return None
1395
    else:
1396
        return fingerprint
1397 1398 1399

################################################################################

1400
def gpg_get_key_addresses(fingerprint):
1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415
    """retreive email addresses from gpg key uids for a given fingerprint"""
    addresses = key_uid_email_cache.get(fingerprint)
    if addresses != None:
        return addresses
    addresses = set()
    cmd = "gpg --no-default-keyring %s --fingerprint %s" \
                % (gpg_keyring_args(), fingerprint)
    (result, output) = commands.getstatusoutput(cmd)
    if result == 0:
        for l in output.split('\n'):
            m = re_gpg_uid.match(l)
            if m:
                addresses.add(m.group(1))
    key_uid_email_cache[fingerprint] = addresses
    return addresses
1416 1417 1418

################################################################################

1419 1420 1421
# Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603

def wrap(paragraph, max_length, prefix=""):
1422 1423 1424 1425
    line = ""
    s = ""
    have_started = 0
    words = paragraph.split()
1426 1427

    for word in words:
1428
        word_size = len(word)
1429 1430
        if word_size > max_length:
            if have_started:
1431 1432
                s += line + '\n' + prefix
            s += word + '\n' + prefix
1433 1434
        else:
            if have_started:
1435
                new_length = len(line) + word_size + 1
1436
                if new_length > max_length:
1437 1438
                    s += line + '\n' + prefix
                    line = word
1439
                else:
1440
                    line += ' ' + word
1441
            else:
1442 1443
                line = word
        have_started = 1
1444 1445

    if have_started:
1446
        s += line
1447

1448
    return s
1449 1450 1451 1452

################################################################################

def clean_symlink (src, dest, root):
J
Joerg Jaspert 已提交
1453 1454 1455 1456
    """
    Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
    Returns fixed 'src'
    """
1457 1458 1459 1460 1461
    src = src.replace(root, '', 1)
    dest = dest.replace(root, '', 1)
    dest = os.path.dirname(dest)
    new_src = '../' * len(dest.split('/'))
    return new_src + src
1462 1463 1464

################################################################################

J
Joerg Jaspert 已提交
1465
def temp_filename(directory=None, prefix="dak", suffix=""):
J
Joerg Jaspert 已提交
1466 1467 1468 1469 1470
    """
    Return a secure and unique filename by pre-creating it.
    If 'directory' is non-null, it will be the directory the file is pre-created in.
    If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
    If 'suffix' is non-null, the filename will end with it.
1471

J
Joerg Jaspert 已提交
1472 1473
    Returns a pair (fd, name).
    """
1474

J
Joerg Jaspert 已提交
1475
    return tempfile.mkstemp(suffix, prefix, directory)
1476 1477 1478

################################################################################

T
Thomas Viehmann 已提交
1479
def is_email_alias(email):
J
Joerg Jaspert 已提交
1480
    """ checks if the user part of the email is listed in the alias file """
T
Thomas Viehmann 已提交
1481 1482 1483 1484 1485 1486 1487 1488 1489
    global alias_cache
    if alias_cache == None:
        aliasfn = which_alias_file()
        alias_cache = set()
        if aliasfn:
            for l in open(aliasfn):
                alias_cache.add(l.split(':')[0])
    uid = email.split('@')[0]
    return uid in alias_cache
T
Thomas Viehmann 已提交
1490 1491 1492

################################################################################

1493
apt_pkg.init()
1494

1495 1496
Cnf = apt_pkg.newConfiguration()
apt_pkg.ReadConfigFileISC(Cnf,default_config)
1497 1498

if which_conf_file() != default_config:
1499
    apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1500 1501

################################################################################