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

J
Joerg Jaspert 已提交
4
""" Utility functions """
5
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
6 7

################################################################################
J
James Troup 已提交
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

# 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

23 24
################################################################################

25
import codecs, commands, email.Header, os, pwd, re, select, socket, shutil, \
26
       sys, tempfile, traceback, stat
27
import apt_pkg
J
James Troup 已提交
28
import database
J
Joerg Jaspert 已提交
29
import time
J
Joerg Jaspert 已提交
30
from dak_exceptions import *
M
Mark Hymers 已提交
31 32 33
from regexes import re_html_escaping, html_escaping, re_single_line_field, \
                    re_multi_line_field, re_srchasver, re_verwithext, \
                    re_parse_maintainer, re_taint_free, re_gpg_uid
34 35

################################################################################
J
James Troup 已提交
36

37 38
default_config = "/etc/dak/dak.conf"
default_apt_config = "/etc/dak/apt.conf"
J
James Troup 已提交
39

T
Thomas Viehmann 已提交
40
alias_cache = None
41
key_uid_email_cache = {}
T
Thomas Viehmann 已提交
42

43 44 45 46
# (hashname, function, earliest_changes_version)
known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
                ("sha256", apt_pkg.sha256sum, (1, 8))]

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

J
Joerg Jaspert 已提交
49 50 51 52 53
def html_escape(s):
    return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)

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

54
def open_file(filename, mode='r'):
J
James Troup 已提交
55
    try:
56
        f = open(filename, mode)
J
James Troup 已提交
57
    except IOError:
J
Joerg Jaspert 已提交
58
        raise CantOpenError, filename
J
James Troup 已提交
59 60
    return f

61
################################################################################
J
James Troup 已提交
62

63 64
def our_raw_input(prompt=""):
    if prompt:
65 66
        sys.stdout.write(prompt)
    sys.stdout.flush()
J
James Troup 已提交
67
    try:
68 69
        ret = raw_input()
        return ret
J
James Troup 已提交
70
    except EOFError:
71 72
        sys.stderr.write("\nUser interrupt (^D).\n")
        raise SystemExit
J
James Troup 已提交
73

74
################################################################################
J
James Troup 已提交
75

J
James Troup 已提交
76
def extract_component_from_section(section):
77
    component = ""
J
James Troup 已提交
78

79
    if section.find('/') != -1:
80
        component = section.split('/')[0]
81 82

    # Expand default component
J
James Troup 已提交
83
    if component == "":
84
        if Cnf.has_key("Component::%s" % section):
85
            component = section
86
        else:
87
            component = "main"
J
James Troup 已提交
88

89
    return (section, component)
J
James Troup 已提交
90

91 92
################################################################################

93
def parse_deb822(contents, signing_rules=0):
94 95
    error = ""
    changes = {}
96

97 98
    # Split the lines in the input, keeping the linebreaks.
    lines = contents.splitlines(True)
J
James Troup 已提交
99

100
    if len(lines) == 0:
J
Joerg Jaspert 已提交
101
        raise ParseChangesError, "[Empty changes file]"
102

J
James Troup 已提交
103 104
    # Reindex by line number so we can easily verify the format of
    # .dsc files...
105 106
    index = 0
    indexed_lines = {}
J
James Troup 已提交
107
    for line in lines:
108 109
        index += 1
        indexed_lines[index] = line[:-1]
J
James Troup 已提交
110

111
    inside_signature = 0
J
James Troup 已提交
112

113 114 115
    num_of_lines = len(indexed_lines.keys())
    index = 0
    first = -1
116
    while index < num_of_lines:
117 118
        index += 1
        line = indexed_lines[index]
J
James Troup 已提交
119
        if line == "":
120
            if signing_rules == 1:
121
                index += 1
122
                if index > num_of_lines:
J
Joerg Jaspert 已提交
123
                    raise InvalidDscError, index
124
                line = indexed_lines[index]
125
                if not line.startswith("-----BEGIN PGP SIGNATURE"):
J
Joerg Jaspert 已提交
126
                    raise InvalidDscError, index
127 128
                inside_signature = 0
                break
129
            else:
130
                continue
131
        if line.startswith("-----BEGIN PGP SIGNATURE"):
132
            break
133
        if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
134
            inside_signature = 1
135
            if signing_rules == 1:
136
                while index < num_of_lines and line != "":
137 138 139
                    index += 1
                    line = indexed_lines[index]
            continue
140
        # If we're not inside the signed data, don't process anything
141
        if signing_rules >= 0 and not inside_signature:
142 143
            continue
        slf = re_single_line_field.match(line)
J
James Troup 已提交
144
        if slf:
145 146
            field = slf.groups()[0].lower()
            changes[field] = slf.groups()[1]
147
            first = 1
148
            continue
J
James Troup 已提交
149
        if line == " .":
150 151 152
            changes[field] += '\n'
            continue
        mlf = re_multi_line_field.match(line)
J
James Troup 已提交
153
        if mlf:
154
            if first == -1:
J
Joerg Jaspert 已提交
155
                raise ParseChangesError, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
156
            if first == 1 and changes[field] != "":
157 158
                changes[field] += '\n'
            first = 0
159
            changes[field] += mlf.groups()[0] + '\n'
160
            continue
161
        error += line
J
James Troup 已提交
162

163
    if signing_rules == 1 and inside_signature:
J
Joerg Jaspert 已提交
164
        raise InvalidDscError, index
J
James Troup 已提交
165

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

168 169
    if changes.has_key("source"):
        # Strip the source version in brackets from the source field,
170
        # put it in the "source-version" field instead.
171
        srcver = re_srchasver.search(changes["source"])
172
        if srcver:
173
            changes["source"] = srcver.group(1)
174
            changes["source-version"] = srcver.group(2)
175

176
    if error:
J
Joerg Jaspert 已提交
177
        raise ParseChangesError, error
J
James Troup 已提交
178

179
    return changes
J
James Troup 已提交
180

181
################################################################################
J
James Troup 已提交
182

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
def parse_changes(filename, signing_rules=0):
    """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.

signing_rules is an optional argument:

 o If signing_rules == -1, no signature is required.
 o If signing_rules == 0 (the default), a signature is required.
 o If signing_rules == 1, it turns on the same strict format checking
   as dpkg-source.

The rules for (signing_rules == 1)-mode are:

  o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
    followed by any PGP header data and must end with a blank line.

  o The data section must end with a blank line and must be followed by
    "-----BEGIN PGP SIGNATURE-----".
"""

    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):
    """create_hash extends the passed files dict with the given hash by
    iterating over all files on disk and passing them to the hashing
    function given."""

221
    rejmsg = []
222
    for f in files.keys():
223 224 225 226 227
        try:
            file_handle = open_file(f)
        except CantOpenError:
            rejmsg.append("Could not open file %s for checksumming" % (f))

228
        files[f][hash_key(hashname)] = hashfunc(file_handle)
229

230
        file_handle.close()
231 232 233 234
    return rejmsg

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

235 236 237 238
def check_hash(where, files, hashname, hashfunc):
    """check_hash checks the given hash in the files dict against the actual
    files on disk.  The hash values need to be present consistently in
    all file entries.  It does not modify its input in any way."""
239

240 241
    rejmsg = []
    for f in files.keys():
242
        file_handle = None
243
        try:
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
            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 已提交
259
                # warn("Cannot open file %s" % f)
260 261
                continue
        finally:
262 263
            if file_handle:
                file_handle.close()
264
    return rejmsg
265

266 267 268 269 270 271 272 273
################################################################################

def check_size(where, files):
    """check_size checks the file sizes in the passed files dict against the
    files on disk."""

    rejmsg = []
    for f in files.keys():
274 275 276 277 278 279 280 281 282
        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]
283
        size = int(files[f]["size"])
284 285 286
        if size != actual_size:
            rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
                   % (f, actual_size, size, where))
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
    return rejmsg

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

def check_hash_fields(what, manifest):
    """check_hash_fields ensures that there are no checksum fields in the
    given dict that we do not know about."""

    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):
    """ensure_dsc_hashes' task is to ensure that each and every *present* hash
    in the dsc is correct, i.e. identical to the changes file and if necessary
    the pool.  The latter task is delegated to check_hash."""
329

330 331 332 333 334 335 336
    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))
337 338 339 340
    return rejmsg

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

M
Mark Hymers 已提交
341
def ensure_hashes(changes, dsc, files, dsc_files):
342 343 344 345
    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 已提交
346 347 348 349 350
    if len(format) == 2:
        format = int(format[0]), int(format[1])
    else:
        format = int(float(format[0])), 0

351 352 353 354 355 356 357 358 359 360 361 362 363
    # 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))
364 365 366

    # 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
367 368 369 370 371 372
    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))
373

374
    return rejmsg
375

376 377 378 379 380
def parse_checksums(where, files, manifest, hashname):
    rejmsg = []
    field = 'checksums-%s' % hashname
    if not field in manifest:
        return rejmsg
J
Joerg Jaspert 已提交
381
    for line in manifest[field].split('\n'):
382 383
        if not line:
            break
J
Joerg Jaspert 已提交
384 385
        checksum, size, checkfile = line.strip().split(' ')
        if not files.has_key(checkfile):
386 387 388 389
        # 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))
390
            continue
J
Joerg Jaspert 已提交
391
        if not files[checkfile]["size"] == size:
392
            rejmsg.append("%s: size differs for files and checksums-%s entry "\
J
Joerg Jaspert 已提交
393
                "in %s" % (checkfile, hashname, where))
394
            continue
J
Joerg Jaspert 已提交
395
        files[checkfile][hash_key(hashname)] = checksum
396 397
    for f in files.keys():
        if not files[f].has_key(hash_key(hashname)):
J
Joerg Jaspert 已提交
398
            rejmsg.append("%s: no entry in checksums-%s in %s" % (checkfile,
399
                hashname, where))
400 401 402 403
    return rejmsg

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

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

406
def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
407
    files = {}
408 409

    # Make sure we have a Files: field to parse...
410
    if not changes.has_key(field):
J
Joerg Jaspert 已提交
411
        raise NoFilesFieldError
412 413

    # Make sure we recognise the format of the Files: field
414 415
    format = re_verwithext.search(changes.get("format", "0.0"))
    if not format:
J
Joerg Jaspert 已提交
416
        raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
417 418 419 420

    format = format.groups()
    if format[1] == None:
        format = int(float(format[0])), 0, format[2]
421
    else:
422 423 424
        format = int(format[0]), int(format[1]), format[2]
    if format[2] == None:
        format = format[:2]
425 426

    if is_a_dsc:
427 428 429 430
        # 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 已提交
431
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
432 433
    else:
        if (format < (1,5) or format > (1,8)):
J
Joerg Jaspert 已提交
434
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
435
        if field != "files" and format < (1,8):
J
Joerg Jaspert 已提交
436
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
437 438

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

440
    # Parse each entry/line:
441
    for i in changes[field].split('\n'):
442
        if not i:
443 444 445
            break
        s = i.split()
        section = priority = ""
J
sync  
James Troup 已提交
446
        try:
447
            if includes_section:
448
                (md5, size, section, priority, name) = s
449 450
            else:
                (md5, size, name) = s
J
sync  
James Troup 已提交
451
        except ValueError:
J
Joerg Jaspert 已提交
452
            raise ParseChangesError, i
J
James Troup 已提交
453

454
        if section == "":
455
            section = "-"
456
        if priority == "":
457
            priority = "-"
J
James Troup 已提交
458

459
        (section, component) = extract_component_from_section(section)
J
James Troup 已提交
460

461
        files[name] = Dict(size=size, section=section,
462
                           priority=priority, component=component)
463
        files[name][hashname] = md5
J
James Troup 已提交
464 465 466

    return files

467
################################################################################
J
James Troup 已提交
468

469 470 471 472
def force_to_utf8(s):
    """Forces a string to UTF-8.  If the string isn't already UTF-8,
it's assumed to be ISO-8859-1."""
    try:
473 474
        unicode(s, 'utf-8')
        return s
475
    except UnicodeError:
476 477
        latin1_s = unicode(s,'iso8859-1')
        return latin1_s.encode('utf-8')
478 479 480 481 482

def rfc2047_encode(s):
    """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."""
    try:
483
        codecs.lookup('ascii')[1](s)
484
        return s
485
    except UnicodeError:
486
        pass
487
    try:
488
        codecs.lookup('utf-8')[1](s)
489 490
        h = email.Header.Header(s, 'utf-8', 998)
        return str(h)
491
    except UnicodeError:
492 493
        h = email.Header.Header(s, 'iso-8859-1', 998)
        return str(h)
494 495 496 497 498 499

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

# <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 已提交
500

J
James Troup 已提交
501
def fix_maintainer (maintainer):
502 503 504 505 506 507 508 509 510 511 512
    """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."""
    maintainer = maintainer.strip()
    if not maintainer:
513
        return ('', '', '', '')
514

515
    if maintainer.find("<") == -1:
516 517
        email = maintainer
        name = ""
518
    elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
519 520
        email = maintainer[1:-1]
        name = ""
521
    else:
522
        m = re_parse_maintainer.match(maintainer)
523 524
        if not m:
            raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
525 526
        name = m.group(1)
        email = m.group(2)
527 528

    # Get an RFC2047 compliant version of the name
529
    rfc2047_name = rfc2047_encode(name)
530 531

    # Force the name to be UTF-8
532
    name = force_to_utf8(name)
533 534

    if name.find(',') != -1 or name.find('.') != -1:
535 536
        rfc822_maint = "%s (%s)" % (email, name)
        rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
537
    else:
538 539
        rfc822_maint = "%s <%s>" % (name, email)
        rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
540 541 542 543

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

544
    return (rfc822_maint, rfc2047_maint, name, email)
J
James Troup 已提交
545

546
################################################################################
J
James Troup 已提交
547 548

# sendmail wrapper, takes _either_ a message string or a file as arguments
549
def send_mail (message, filename=""):
550 551
        # If we've been passed a string dump it into a temporary file
    if message:
J
Joerg Jaspert 已提交
552
        (fd, filename) = tempfile.mkstemp()
553 554 555 556 557 558
        os.write (fd, message)
        os.close (fd)

    # Invoke sendmail
    (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
    if (result != 0):
J
Joerg Jaspert 已提交
559
        raise SendmailFailedError, output
560 561 562 563

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

565
################################################################################
J
James Troup 已提交
566 567

def poolify (source, component):
568
    if component:
569
        component += '/'
J
James Troup 已提交
570
    if source[:3] == "lib":
571
        return component + source[:4] + '/' + source + '/'
J
James Troup 已提交
572
    else:
573
        return component + source[:1] + '/' + source + '/'
J
James Troup 已提交
574

575
################################################################################
J
James Troup 已提交
576

577
def move (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
578
    if os.path.exists(dest) and os.path.isdir(dest):
579
        dest_dir = dest
J
James Troup 已提交
580
    else:
581
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
582
    if not os.path.exists(dest_dir):
583 584 585
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
586
    #print "Moving %s to %s..." % (src, dest)
J
James Troup 已提交
587
    if os.path.exists(dest) and os.path.isdir(dest):
588
        dest += '/' + os.path.basename(src)
589 590 591
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
592
            fubar("Can't move %s to %s - file already exists." % (src, dest))
593 594
        else:
            if not os.access(dest, os.W_OK):
595 596 597 598
                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 已提交
599

600
def copy (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
601
    if os.path.exists(dest) and os.path.isdir(dest):
602
        dest_dir = dest
J
James Troup 已提交
603
    else:
604
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
605
    if not os.path.exists(dest_dir):
606 607 608
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
609
    #print "Copying %s to %s..." % (src, dest)
J
James Troup 已提交
610
    if os.path.exists(dest) and os.path.isdir(dest):
611
        dest += '/' + os.path.basename(src)
612 613 614
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
J
Joerg Jaspert 已提交
615
            raise FileExistsError
616 617
        else:
            if not os.access(dest, os.W_OK):
J
Joerg Jaspert 已提交
618
                raise CantOverwriteError
619 620
    shutil.copy2(src, dest)
    os.chmod(dest, perms)
J
James Troup 已提交
621

622
################################################################################
J
James Troup 已提交
623 624

def where_am_i ():
625 626
    res = socket.gethostbyaddr(socket.gethostname())
    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
627
    if database_hostname:
628
        return database_hostname
J
James Troup 已提交
629
    else:
630
        return res[0]
J
James Troup 已提交
631 632

def which_conf_file ():
633
    res = socket.gethostbyaddr(socket.gethostname())
634
    if Cnf.get("Config::" + res[0] + "::DakConfig"):
635
        return Cnf["Config::" + res[0] + "::DakConfig"]
J
James Troup 已提交
636
    else:
637
        return default_config
638 639

def which_apt_conf_file ():
640
    res = socket.gethostbyaddr(socket.gethostname())
641
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
642
        return Cnf["Config::" + res[0] + "::AptConfig"]
643
    else:
644
        return default_apt_config
645

T
Thomas Viehmann 已提交
646 647 648 649 650 651 652 653
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

654
################################################################################
J
James Troup 已提交
655

656 657 658 659
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
660 661
    s = s.replace('+', '\\\\+')
    s = s.replace('.', '\\\\.')
662 663
    return s

664
################################################################################
665

J
James Troup 已提交
666
# Perform a substition of template
667
def TemplateSubst(map, filename):
J
Joerg Jaspert 已提交
668 669
    templatefile = open_file(filename)
    template = templatefile.read()
670
    for x in map.keys():
671
        template = template.replace(x,map[x])
J
Joerg Jaspert 已提交
672
    templatefile.close()
673
    return template
J
James Troup 已提交
674

675
################################################################################
J
James Troup 已提交
676 677

def fubar(msg, exit_code=1):
678 679
    sys.stderr.write("E: %s\n" % (msg))
    sys.exit(exit_code)
J
James Troup 已提交
680 681

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

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

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

691
################################################################################
J
James Troup 已提交
692

J
James Troup 已提交
693
def size_type (c):
694
    t  = " B"
695
    if c > 10240:
696 697
        c = c / 1024
        t = " KB"
698
    if c > 10240:
699 700
        c = c / 1024
        t = " MB"
J
James Troup 已提交
701
    return ("%d%s" % (c, t))
702 703 704 705

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

def cc_fix_changes (changes):
706
    o = changes.get("architecture", "")
707
    if o:
708 709
        del changes["architecture"]
    changes["architecture"] = {}
710
    for j in o.split():
711
        changes["architecture"][j] = 1
712

713
# Sort by source name, source version, 'have source', and then by filename
714 715
def changes_compare (a, b):
    try:
716
        a_changes = parse_changes(a)
717
    except:
718
        return -1
719 720

    try:
721
        b_changes = parse_changes(b)
722
    except:
723
        return 1
J
James Troup 已提交
724

725 726
    cc_fix_changes (a_changes)
    cc_fix_changes (b_changes)
727 728

    # Sort by source name
729 730 731
    a_source = a_changes.get("source")
    b_source = b_changes.get("source")
    q = cmp (a_source, b_source)
732
    if q:
733
        return q
734 735

    # Sort by source version
736 737 738
    a_version = a_changes.get("version", "0")
    b_version = b_changes.get("version", "0")
    q = apt_pkg.VersionCompare(a_version, b_version)
739
    if q:
740
        return q
741

742
    # Sort by 'have source'
743 744
    a_has_source = a_changes["architecture"].get("source")
    b_has_source = b_changes["architecture"].get("source")
745
    if a_has_source and not b_has_source:
746
        return -1
747
    elif b_has_source and not a_has_source:
748
        return 1
749

750
    # Fall back to sort by filename
751
    return cmp(a, b)
752 753

################################################################################
754 755

def find_next_free (dest, too_many=100):
756 757
    extra = 0
    orig_dest = dest
758
    while os.path.exists(dest) and extra < too_many:
759 760
        dest = orig_dest + '.' + repr(extra)
        extra += 1
761
    if extra >= too_many:
J
Joerg Jaspert 已提交
762
        raise NoFreeFilenameError
763
    return dest
J
James Troup 已提交
764

765
################################################################################
766 767

def result_join (original, sep = '\t'):
J
Joerg Jaspert 已提交
768
    resultlist = []
769 770
    for i in xrange(len(original)):
        if original[i] == None:
J
Joerg Jaspert 已提交
771
            resultlist.append("")
772
        else:
J
Joerg Jaspert 已提交
773 774
            resultlist.append(original[i])
    return sep.join(resultlist)
775

776 777
################################################################################

778
def prefix_multi_line_string(str, prefix, include_blank_lines=0):
779
    out = ""
780
    for line in str.split('\n'):
781
        line = line.strip()
782
        if line or include_blank_lines:
783
            out += "%s%s\n" % (prefix, line)
784 785
    # Strip trailing new line
    if out:
786 787
        out = out[:-1]
    return out
788

789
################################################################################
790

791
def validate_changes_file_arg(filename, require_changes=1):
792 793
    """'filename' is either a .changes or .dak file.  If 'filename' is a
.dak file, it's changed to be the corresponding .changes file.  The
794 795 796 797 798 799 800 801 802 803
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:

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

806
    orig_filename = filename
807
    if filename.endswith(".dak"):
808
        filename = filename[:-4]+".changes"
809

810
    if not filename.endswith(".changes"):
811
        error = "invalid file type; not a changes file"
812
    else:
813 814
        if not os.access(filename,os.R_OK):
            if os.path.exists(filename):
815
                error = "permission denied"
816
            else:
817
                error = "file not found"
818 819

    if error:
820
        if require_changes == 1:
821
            fubar("%s: %s." % (orig_filename, error))
822
        elif require_changes == 0:
823 824
            warn("Skipping %s - %s" % (orig_filename, error))
            return None
825
        else: # We only care about the .dak file
826
            return filename
827
    else:
828
        return filename
829 830 831

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

832
def real_arch(arch):
833
    return (arch != "source" and arch != "all")
834 835 836

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

837
def join_with_commas_and(list):
838 839 840
    if len(list) == 0: return "nothing"
    if len(list) == 1: return list[0]
    return ", ".join(list[:-1]) + " and " + list[-1]
841 842 843

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

844
def pp_deps (deps):
845
    pp_deps = []
846
    for atom in deps:
847
        (pkg, version, constraint) = atom
848
        if constraint:
849
            pp_dep = "%s (%s %s)" % (pkg, constraint, version)
850
        else:
851 852 853
            pp_dep = pkg
        pp_deps.append(pp_dep)
    return " |".join(pp_deps)
854 855 856

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

857
def get_conf():
858
    return Cnf
859 860 861

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

862 863 864 865
# Handle -a, -c and -s arguments; returns them as SQL constraints
def parse_args(Options):
    # Process suite
    if Options["Suite"]:
866
        suite_ids_list = []
867
        for suite in split_args(Options["Suite"]):
J
James Troup 已提交
868
            suite_id = database.get_suite_id(suite)
869
            if suite_id == -1:
870
                warn("suite '%s' not recognised." % (suite))
871
            else:
872
                suite_ids_list.append(suite_id)
873
        if suite_ids_list:
874
            con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
875
        else:
876
            fubar("No valid suite given.")
877
    else:
878
        con_suites = ""
879 880 881

    # Process component
    if Options["Component"]:
882
        component_ids_list = []
883
        for component in split_args(Options["Component"]):
J
James Troup 已提交
884
            component_id = database.get_component_id(component)
885
            if component_id == -1:
886
                warn("component '%s' not recognised." % (component))
887
            else:
888
                component_ids_list.append(component_id)
889
        if component_ids_list:
890
            con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
891
        else:
892
            fubar("No valid component given.")
893
    else:
894
        con_components = ""
895 896

    # Process architecture
897
    con_architectures = ""
898
    if Options["Architecture"]:
899 900
        arch_ids_list = []
        check_source = 0
901
        for architecture in split_args(Options["Architecture"]):
902
            if architecture == "source":
903
                check_source = 1
904
            else:
J
James Troup 已提交
905
                architecture_id = database.get_architecture_id(architecture)
906
                if architecture_id == -1:
907
                    warn("architecture '%s' not recognised." % (architecture))
908
                else:
909
                    arch_ids_list.append(architecture_id)
910
        if arch_ids_list:
911
            con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
912 913
        else:
            if not check_source:
914
                fubar("No valid architecture given.")
915
    else:
916
        check_source = 1
917

918
    return (con_suites, con_architectures, con_components, check_source)
919 920 921

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

922 923 924 925
# Inspired(tm) by Bryn Keller's print_exc_plus (See
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)

def print_exc():
926
    tb = sys.exc_info()[2]
927
    while tb.tb_next:
928 929 930
        tb = tb.tb_next
    stack = []
    frame = tb.tb_frame
931
    while frame:
932 933 934 935
        stack.append(frame)
        frame = frame.f_back
    stack.reverse()
    traceback.print_exc()
936 937 938
    for frame in stack:
        print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
                                             frame.f_code.co_filename,
939
                                             frame.f_lineno)
940
        for key, value in frame.f_locals.items():
941
            print "\t%20s = " % key,
942
            try:
943
                print value
944
            except:
945
                print "<unable to print>"
946 947 948

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

J
James Troup 已提交
949 950
def try_with_debug(function):
    try:
951
        function()
J
James Troup 已提交
952
    except SystemExit:
953
        raise
J
James Troup 已提交
954
    except:
955
        print_exc()
J
James Troup 已提交
956 957 958

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

J
James Troup 已提交
959 960 961 962 963
# Function for use in sorting lists of architectures.
# Sorts normally except that 'source' dominates all others.

def arch_compare_sw (a, b):
    if a == "source" and b == "source":
964
        return 0
J
James Troup 已提交
965
    elif a == "source":
966
        return -1
J
James Troup 已提交
967
    elif b == "source":
968
        return 1
J
James Troup 已提交
969

970
    return cmp (a, b)
J
James Troup 已提交
971 972 973

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

974 975
# Split command line arguments which can be separated by either commas
# or whitespace.  If dwim is set, it will complain about string ending
976
# in comma since this usually means someone did 'dak ls -a i386, m68k
977 978 979 980 981
# foo' or something and the inevitable confusion resulting from 'm68k'
# being treated as an argument is undesirable.

def split_args (s, dwim=1):
    if s.find(",") == -1:
982
        return s.split()
983 984
    else:
        if s[-1:] == "," and dwim:
985 986
            fubar("split_args: found trailing comma, spurious space maybe?")
        return s.split(",")
987 988 989

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

990 991 992 993 994 995 996
def Dict(**dict): return dict

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

# Our very own version of commands.getouputstatus(), hacked to support
# gpgv's status fd.
def gpgv_get_status_output(cmd, status_read, status_write):
997 998 999 1000 1001
    cmd = ['/bin/sh', '-c', cmd]
    p2cread, p2cwrite = os.pipe()
    c2pread, c2pwrite = os.pipe()
    errout, errin = os.pipe()
    pid = os.fork()
1002 1003
    if pid == 0:
        # Child
1004 1005 1006 1007 1008 1009
        os.close(0)
        os.close(1)
        os.dup(p2cread)
        os.dup(c2pwrite)
        os.close(2)
        os.dup(errin)
1010 1011 1012
        for i in range(3, 256):
            if i != status_write:
                try:
1013
                    os.close(i)
1014
                except:
1015
                    pass
1016
        try:
1017
            os.execvp(cmd[0], cmd)
1018
        finally:
1019
            os._exit(1)
1020

1021
    # Parent
1022
    os.close(p2cread)
1023 1024
    os.dup2(c2pread, c2pwrite)
    os.dup2(errout, errin)
1025

1026
    output = status = ""
1027
    while 1:
1028 1029
        i, o, e = select.select([c2pwrite, errin, status_read], [], [])
        more_data = []
1030
        for fd in i:
1031
            r = os.read(fd, 8196)
1032
            if len(r) > 0:
1033
                more_data.append(fd)
1034
                if fd == c2pwrite or fd == errin:
1035
                    output += r
1036
                elif fd == status_read:
1037
                    status += r
1038
                else:
1039
                    fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
1040 1041 1042
        if not more_data:
            pid, exit_status = os.waitpid(pid, 0)
            try:
1043 1044 1045 1046 1047 1048 1049
                os.close(status_write)
                os.close(status_read)
                os.close(c2pread)
                os.close(c2pwrite)
                os.close(p2cwrite)
                os.close(errin)
                os.close(errout)
1050
            except:
1051 1052
                pass
            break
1053

1054
    return output, status, exit_status
1055

1056
################################################################################
1057

1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
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:]
1075
        if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
            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):
    """Retrieve the key that signed 'filename' from 'keyserver' and
add it to 'keyring'.  Returns nothing on success, or an error message
on error."""

    # Defaults for keyserver and keyring
    if not keyserver:
        keyserver = Cnf["Dinstall::KeyServer"]
    if not keyring:
1094
        keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
1095 1096 1097 1098 1099 1100

    # 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 已提交
1101
    status_read, status_write = os.pipe()
1102 1103
    cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
    (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
1104

1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
    # 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 ""

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

1129 1130 1131 1132 1133 1134 1135 1136
def gpg_keyring_args(keyrings=None):
    if not keyrings:
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")

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

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

1137
def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
1138 1139 1140 1141 1142
    """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,
1143
the second is an optional prefix string.  It's possible for reject()
1144 1145 1146
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
1147 1148 1149
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."""
1150 1151

    # Ensure the filename contains no shell meta-characters or other badness
1152
    if not re_taint_free.match(sig_filename):
1153 1154
        reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
        return None
1155 1156

    if data_filename and not re_taint_free.match(data_filename):
1157 1158
        reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
        return None
1159 1160

    if not keyrings:
1161
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1162

1163 1164 1165 1166 1167 1168 1169 1170 1171
    # 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

1172
    # Build the command line
J
Various  
Joerg Jaspert 已提交
1173
    status_read, status_write = os.pipe()
1174 1175 1176
    cmd = "gpgv --status-fd %s %s %s %s" % (
        status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)

1177
    # Invoke gpgv on the file
1178
    (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1179 1180

    # Process the status-fd output
1181
    (keywords, internal_error) = process_gpgv_output(status)
1182 1183 1184

    # If we failed to parse the status-fd output, let's just whine and bail now
    if internal_error:
1185 1186 1187 1188
        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
1189

1190
    bad = ""
1191 1192
    # Now check for obviously bad things in the processed output
    if keywords.has_key("KEYREVOKED"):
1193 1194
        reject("The key used to sign %s has been revoked." % (sig_filename))
        bad = 1
1195
    if keywords.has_key("BADSIG"):
1196 1197
        reject("bad signature on %s." % (sig_filename))
        bad = 1
1198
    if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1199 1200
        reject("failed to check signature on %s." % (sig_filename))
        bad = 1
1201
    if keywords.has_key("NO_PUBKEY"):
1202
        args = keywords["NO_PUBKEY"]
1203
        if len(args) >= 1:
1204 1205 1206
            key = args[0]
        reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
        bad = 1
1207
    if keywords.has_key("BADARMOR"):
1208 1209
        reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
        bad = 1
1210
    if keywords.has_key("NODATA"):
1211 1212
        reject("no signature found in %s." % (sig_filename))
        bad = 1
J
Joerg Jaspert 已提交
1213 1214 1215 1216
    if keywords.has_key("EXPKEYSIG"):
        args = keywords["EXPKEYSIG"]
        if len(args) >= 1:
            key = args[0]
1217
        reject("Signature made by expired key 0x%s" % (key))
J
Joerg Jaspert 已提交
1218
        bad = 1
1219
    if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1220
        args = keywords["KEYEXPIRED"]
J
Joerg Jaspert 已提交
1221
        expiredate=""
1222
        if len(args) >= 1:
J
Joerg Jaspert 已提交
1223 1224 1225 1226 1227 1228
            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))
1229
        bad = 1
1230 1231

    if bad:
1232
        return None
1233 1234 1235

    # Next check gpgv exited with a zero return code
    if exit_status:
1236
        reject("gpgv failed while checking %s." % (sig_filename))
1237
        if status.strip():
1238
            reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1239
        else:
1240 1241
            reject(prefix_multi_line_string(output, " [GPG output:] "), "")
        return None
1242 1243 1244

    # Sanity check the good stuff we expect
    if not keywords.has_key("VALIDSIG"):
1245 1246
        reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
        bad = 1
1247
    else:
1248
        args = keywords["VALIDSIG"]
1249
        if len(args) < 1:
1250 1251
            reject("internal error while checking signature on %s." % (sig_filename))
            bad = 1
1252
        else:
1253
            fingerprint = args[0]
1254
    if not keywords.has_key("GOODSIG"):
1255 1256
        reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
        bad = 1
1257
    if not keywords.has_key("SIG_ID"):
1258 1259
        reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
        bad = 1
1260 1261 1262 1263

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

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

    if bad:
1272
        return None
1273
    else:
1274
        return fingerprint
1275 1276 1277

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

1278
def gpg_get_key_addresses(fingerprint):
1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293
    """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
1294 1295 1296

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

1297 1298 1299
# Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603

def wrap(paragraph, max_length, prefix=""):
1300 1301 1302 1303
    line = ""
    s = ""
    have_started = 0
    words = paragraph.split()
1304 1305

    for word in words:
1306
        word_size = len(word)
1307 1308
        if word_size > max_length:
            if have_started:
1309 1310
                s += line + '\n' + prefix
            s += word + '\n' + prefix
1311 1312
        else:
            if have_started:
1313
                new_length = len(line) + word_size + 1
1314
                if new_length > max_length:
1315 1316
                    s += line + '\n' + prefix
                    line = word
1317
                else:
1318
                    line += ' ' + word
1319
            else:
1320 1321
                line = word
        have_started = 1
1322 1323

    if have_started:
1324
        s += line
1325

1326
    return s
1327 1328 1329 1330 1331 1332

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

# Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
# Returns fixed 'src'
def clean_symlink (src, dest, root):
1333 1334 1335 1336 1337
    src = src.replace(root, '', 1)
    dest = dest.replace(root, '', 1)
    dest = os.path.dirname(dest)
    new_src = '../' * len(dest.split('/'))
    return new_src + src
1338 1339 1340

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

J
Joerg Jaspert 已提交
1341
def temp_filename(directory=None, prefix="dak", suffix=""):
1342 1343
    """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.
J
Joerg Jaspert 已提交
1344 1345
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.
1346

J
Joerg Jaspert 已提交
1347 1348
Returns a pair (fd, name).
"""
1349

J
Joerg Jaspert 已提交
1350
    return tempfile.mkstemp(suffix, prefix, directory)
1351 1352 1353

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

T
Thomas Viehmann 已提交
1354 1355 1356
# checks if the user part of the email is listed in the alias file

def is_email_alias(email):
T
Thomas Viehmann 已提交
1357 1358 1359 1360 1361 1362 1363 1364 1365
    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 已提交
1366 1367 1368

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

1369
apt_pkg.init()
1370

1371 1372
Cnf = apt_pkg.newConfiguration()
apt_pkg.ReadConfigFileISC(Cnf,default_config)
1373 1374

if which_conf_file() != default_config:
1375
    apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1376 1377

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