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

J
James Troup 已提交
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 *
31 32

################################################################################
J
James Troup 已提交
33 34

re_comments = re.compile(r"\#.*")
35 36
re_no_epoch = re.compile(r"^\d+\:")
re_no_revision = re.compile(r"-[^-]+$")
J
James Troup 已提交
37 38
re_arch_from_filename = re.compile(r"/binary-[^/]+/")
re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
39 40
re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$")
re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$")
41

42 43 44
re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)")
re_multi_line_field = re.compile(r"^\s(.*)")
re_taint_free = re.compile(r"^[-+~/\.\w]+$")
45

46
re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>")
47
re_gpg_uid = re.compile('^uid.*<([^>]*)>')
J
James Troup 已提交
48

49
re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
50
re_verwithext = re.compile(r"^(\d+)(?:\.(\d+))(?:\s+\((\S+)\))?$")
J
James Troup 已提交
51

52 53
re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")

J
Joerg Jaspert 已提交
54 55 56
html_escaping = {'"':'&quot;', '&':'&amp;', '<':'&lt;', '>':'&gt;'}
re_html_escaping = re.compile('|'.join(map(re.escape, html_escaping.keys())))

57 58
default_config = "/etc/dak/dak.conf"
default_apt_config = "/etc/dak/apt.conf"
J
James Troup 已提交
59

T
Thomas Viehmann 已提交
60
alias_cache = None
61
key_uid_email_cache = {}
T
Thomas Viehmann 已提交
62

63 64 65 66
# (hashname, function, earliest_changes_version)
known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
                ("sha256", apt_pkg.sha256sum, (1, 8))]

67
################################################################################
J
James Troup 已提交
68

J
Joerg Jaspert 已提交
69 70 71 72 73
def html_escape(s):
    return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)

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

74
def open_file(filename, mode='r'):
J
James Troup 已提交
75
    try:
76
        f = open(filename, mode)
J
James Troup 已提交
77
    except IOError:
J
Joerg Jaspert 已提交
78
        raise CantOpenError, filename
J
James Troup 已提交
79 80
    return f

81
################################################################################
J
James Troup 已提交
82

83 84
def our_raw_input(prompt=""):
    if prompt:
85 86
        sys.stdout.write(prompt)
    sys.stdout.flush()
J
James Troup 已提交
87
    try:
88 89
        ret = raw_input()
        return ret
J
James Troup 已提交
90
    except EOFError:
91 92
        sys.stderr.write("\nUser interrupt (^D).\n")
        raise SystemExit
J
James Troup 已提交
93

94
################################################################################
J
James Troup 已提交
95

J
James Troup 已提交
96
def extract_component_from_section(section):
97
    component = ""
J
James Troup 已提交
98

99
    if section.find('/') != -1:
100
        component = section.split('/')[0]
101 102

    # Expand default component
J
James Troup 已提交
103
    if component == "":
104
        if Cnf.has_key("Component::%s" % section):
105
            component = section
106
        else:
107
            component = "main"
J
James Troup 已提交
108

109
    return (section, component)
J
James Troup 已提交
110

111 112
################################################################################

113
def parse_deb822(contents, signing_rules=0):
114 115
    error = ""
    changes = {}
116

117 118
    # Split the lines in the input, keeping the linebreaks.
    lines = contents.splitlines(True)
J
James Troup 已提交
119

120
    if len(lines) == 0:
J
Joerg Jaspert 已提交
121
        raise ParseChangesError, "[Empty changes file]"
122

J
James Troup 已提交
123 124
    # Reindex by line number so we can easily verify the format of
    # .dsc files...
125 126
    index = 0
    indexed_lines = {}
J
James Troup 已提交
127
    for line in lines:
128 129
        index += 1
        indexed_lines[index] = line[:-1]
J
James Troup 已提交
130

131
    inside_signature = 0
J
James Troup 已提交
132

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

183
    if signing_rules == 1 and inside_signature:
J
Joerg Jaspert 已提交
184
        raise InvalidDscError, index
J
James Troup 已提交
185

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

188 189
    if changes.has_key("source"):
        # Strip the source version in brackets from the source field,
190
        # put it in the "source-version" field instead.
191
        srcver = re_srchasver.search(changes["source"])
192
        if srcver:
193
            changes["source"] = srcver.group(1)
194
            changes["source-version"] = srcver.group(2)
195

196
    if error:
J
Joerg Jaspert 已提交
197
        raise ParseChangesError, error
J
James Troup 已提交
198

199
    return changes
J
James Troup 已提交
200

201
################################################################################
J
James Troup 已提交
202

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
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."""

241
    rejmsg = []
242
    for f in files.keys():
243 244 245 246 247
        try:
            file_handle = open_file(f)
        except CantOpenError:
            rejmsg.append("Could not open file %s for checksumming" % (f))

248
        files[f][hash_key(hashname)] = hashfunc(file_handle)
249

250
        file_handle.close()
251 252 253 254
    return rejmsg

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

255 256 257 258
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."""
259

260 261
    rejmsg = []
    for f in files.keys():
262
        file_handle = None
263
        try:
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
            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 已提交
279
                # warn("Cannot open file %s" % f)
280 281
                continue
        finally:
282 283
            if file_handle:
                file_handle.close()
284
    return rejmsg
285

286 287 288 289 290 291 292 293
################################################################################

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():
294 295 296 297 298 299 300 301 302
        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]
303
        size = int(files[f]["size"])
304 305 306
        if size != actual_size:
            rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
                   % (f, actual_size, size, where))
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
    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."""
349

350 351 352 353 354 355 356
    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))
357 358 359 360
    return rejmsg

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

M
Mark Hymers 已提交
361
def ensure_hashes(changes, dsc, files, dsc_files):
362 363 364 365
    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 已提交
366 367 368 369 370
    if len(format) == 2:
        format = int(format[0]), int(format[1])
    else:
        format = int(float(format[0])), 0

371 372 373 374 375 376 377 378 379 380 381 382 383
    # 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))
384 385 386

    # 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
387 388 389 390 391 392
    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))
393

394
    return rejmsg
395

396 397 398 399 400
def parse_checksums(where, files, manifest, hashname):
    rejmsg = []
    field = 'checksums-%s' % hashname
    if not field in manifest:
        return rejmsg
J
Joerg Jaspert 已提交
401
    for line in manifest[field].split('\n'):
402 403
        if not line:
            break
J
Joerg Jaspert 已提交
404 405
        checksum, size, checkfile = line.strip().split(' ')
        if not files.has_key(checkfile):
406 407 408 409
        # 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))
410
            continue
J
Joerg Jaspert 已提交
411
        if not files[checkfile]["size"] == size:
412
            rejmsg.append("%s: size differs for files and checksums-%s entry "\
J
Joerg Jaspert 已提交
413
                "in %s" % (checkfile, hashname, where))
414
            continue
J
Joerg Jaspert 已提交
415
        files[checkfile][hash_key(hashname)] = checksum
416 417
    for f in files.keys():
        if not files[f].has_key(hash_key(hashname)):
J
Joerg Jaspert 已提交
418
            rejmsg.append("%s: no entry in checksums-%s in %s" % (checkfile,
419
                hashname, where))
420 421 422 423
    return rejmsg

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

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

426
def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
427
    files = {}
428 429

    # Make sure we have a Files: field to parse...
430
    if not changes.has_key(field):
J
Joerg Jaspert 已提交
431
        raise NoFilesFieldError
432 433

    # Make sure we recognise the format of the Files: field
434 435
    format = re_verwithext.search(changes.get("format", "0.0"))
    if not format:
J
Joerg Jaspert 已提交
436
        raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
437 438 439 440

    format = format.groups()
    if format[1] == None:
        format = int(float(format[0])), 0, format[2]
441
    else:
442 443 444
        format = int(format[0]), int(format[1]), format[2]
    if format[2] == None:
        format = format[:2]
445 446

    if is_a_dsc:
447 448 449 450
        # 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 已提交
451
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
452 453
    else:
        if (format < (1,5) or format > (1,8)):
J
Joerg Jaspert 已提交
454
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
455
        if field != "files" and format < (1,8):
J
Joerg Jaspert 已提交
456
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
457 458

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

460
    # Parse each entry/line:
461
    for i in changes[field].split('\n'):
462
        if not i:
463 464 465
            break
        s = i.split()
        section = priority = ""
J
sync  
James Troup 已提交
466
        try:
467
            if includes_section:
468
                (md5, size, section, priority, name) = s
469 470
            else:
                (md5, size, name) = s
J
sync  
James Troup 已提交
471
        except ValueError:
J
Joerg Jaspert 已提交
472
            raise ParseChangesError, i
J
James Troup 已提交
473

474
        if section == "":
475
            section = "-"
476
        if priority == "":
477
            priority = "-"
J
James Troup 已提交
478

479
        (section, component) = extract_component_from_section(section)
J
James Troup 已提交
480

481
        files[name] = Dict(size=size, section=section,
482
                           priority=priority, component=component)
483
        files[name][hashname] = md5
J
James Troup 已提交
484 485 486

    return files

487
################################################################################
J
James Troup 已提交
488

489 490 491 492
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:
493 494
        unicode(s, 'utf-8')
        return s
495
    except UnicodeError:
496 497
        latin1_s = unicode(s,'iso8859-1')
        return latin1_s.encode('utf-8')
498 499 500 501 502

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:
503
        codecs.lookup('ascii')[1](s)
504
        return s
505
    except UnicodeError:
506
        pass
507
    try:
508
        codecs.lookup('utf-8')[1](s)
509 510
        h = email.Header.Header(s, 'utf-8', 998)
        return str(h)
511
    except UnicodeError:
512 513
        h = email.Header.Header(s, 'iso-8859-1', 998)
        return str(h)
514 515 516 517 518 519

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

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

J
James Troup 已提交
521
def fix_maintainer (maintainer):
522 523 524 525 526 527 528 529 530 531 532
    """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:
533
        return ('', '', '', '')
534

535
    if maintainer.find("<") == -1:
536 537
        email = maintainer
        name = ""
538
    elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
539 540
        email = maintainer[1:-1]
        name = ""
541
    else:
542
        m = re_parse_maintainer.match(maintainer)
543 544
        if not m:
            raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
545 546
        name = m.group(1)
        email = m.group(2)
547 548

    # Get an RFC2047 compliant version of the name
549
    rfc2047_name = rfc2047_encode(name)
550 551

    # Force the name to be UTF-8
552
    name = force_to_utf8(name)
553 554

    if name.find(',') != -1 or name.find('.') != -1:
555 556
        rfc822_maint = "%s (%s)" % (email, name)
        rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
557
    else:
558 559
        rfc822_maint = "%s <%s>" % (name, email)
        rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
560 561 562 563

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

564
    return (rfc822_maint, rfc2047_maint, name, email)
J
James Troup 已提交
565

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

# sendmail wrapper, takes _either_ a message string or a file as arguments
569
def send_mail (message, filename=""):
570 571
        # If we've been passed a string dump it into a temporary file
    if message:
J
Joerg Jaspert 已提交
572
        (fd, filename) = tempfile.mkstemp()
573 574 575 576 577 578
        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 已提交
579
        raise SendmailFailedError, output
580 581 582 583

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

585
################################################################################
J
James Troup 已提交
586 587

def poolify (source, component):
588
    if component:
589
        component += '/'
J
James Troup 已提交
590
    if source[:3] == "lib":
591
        return component + source[:4] + '/' + source + '/'
J
James Troup 已提交
592
    else:
593
        return component + source[:1] + '/' + source + '/'
J
James Troup 已提交
594

595
################################################################################
J
James Troup 已提交
596

597
def move (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
598
    if os.path.exists(dest) and os.path.isdir(dest):
599
        dest_dir = dest
J
James Troup 已提交
600
    else:
601
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
602
    if not os.path.exists(dest_dir):
603 604 605
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
606
    #print "Moving %s to %s..." % (src, dest)
J
James Troup 已提交
607
    if os.path.exists(dest) and os.path.isdir(dest):
608
        dest += '/' + os.path.basename(src)
609 610 611
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
612
            fubar("Can't move %s to %s - file already exists." % (src, dest))
613 614
        else:
            if not os.access(dest, os.W_OK):
615 616 617 618
                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 已提交
619

620
def copy (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
621
    if os.path.exists(dest) and os.path.isdir(dest):
622
        dest_dir = dest
J
James Troup 已提交
623
    else:
624
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
625
    if not os.path.exists(dest_dir):
626 627 628
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
629
    #print "Copying %s to %s..." % (src, dest)
J
James Troup 已提交
630
    if os.path.exists(dest) and os.path.isdir(dest):
631
        dest += '/' + os.path.basename(src)
632 633 634
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
J
Joerg Jaspert 已提交
635
            raise FileExistsError
636 637
        else:
            if not os.access(dest, os.W_OK):
J
Joerg Jaspert 已提交
638
                raise CantOverwriteError
639 640
    shutil.copy2(src, dest)
    os.chmod(dest, perms)
J
James Troup 已提交
641

642
################################################################################
J
James Troup 已提交
643 644

def where_am_i ():
645 646
    res = socket.gethostbyaddr(socket.gethostname())
    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
647
    if database_hostname:
648
        return database_hostname
J
James Troup 已提交
649
    else:
650
        return res[0]
J
James Troup 已提交
651 652

def which_conf_file ():
653
    res = socket.gethostbyaddr(socket.gethostname())
654
    if Cnf.get("Config::" + res[0] + "::DakConfig"):
655
        return Cnf["Config::" + res[0] + "::DakConfig"]
J
James Troup 已提交
656
    else:
657
        return default_config
658 659

def which_apt_conf_file ():
660
    res = socket.gethostbyaddr(socket.gethostname())
661
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
662
        return Cnf["Config::" + res[0] + "::AptConfig"]
663
    else:
664
        return default_apt_config
665

T
Thomas Viehmann 已提交
666 667 668 669 670 671 672 673
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

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

676 677 678 679
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
680 681
    s = s.replace('+', '\\\\+')
    s = s.replace('.', '\\\\.')
682 683
    return s

684
################################################################################
685

J
James Troup 已提交
686
# Perform a substition of template
687
def TemplateSubst(map, filename):
688 689
    file = open_file(filename)
    template = file.read()
690
    for x in map.keys():
691 692 693
        template = template.replace(x,map[x])
    file.close()
    return template
J
James Troup 已提交
694

695
################################################################################
J
James Troup 已提交
696 697

def fubar(msg, exit_code=1):
698 699
    sys.stderr.write("E: %s\n" % (msg))
    sys.exit(exit_code)
J
James Troup 已提交
700 701

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

704
################################################################################
J
James Troup 已提交
705

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

711
################################################################################
J
James Troup 已提交
712

J
James Troup 已提交
713
def size_type (c):
714
    t  = " B"
715
    if c > 10240:
716 717
        c = c / 1024
        t = " KB"
718
    if c > 10240:
719 720
        c = c / 1024
        t = " MB"
J
James Troup 已提交
721
    return ("%d%s" % (c, t))
722 723 724 725

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

def cc_fix_changes (changes):
726
    o = changes.get("architecture", "")
727
    if o:
728 729
        del changes["architecture"]
    changes["architecture"] = {}
730
    for j in o.split():
731
        changes["architecture"][j] = 1
732

733
# Sort by source name, source version, 'have source', and then by filename
734 735
def changes_compare (a, b):
    try:
736
        a_changes = parse_changes(a)
737
    except:
738
        return -1
739 740

    try:
741
        b_changes = parse_changes(b)
742
    except:
743
        return 1
J
James Troup 已提交
744

745 746
    cc_fix_changes (a_changes)
    cc_fix_changes (b_changes)
747 748

    # Sort by source name
749 750 751
    a_source = a_changes.get("source")
    b_source = b_changes.get("source")
    q = cmp (a_source, b_source)
752
    if q:
753
        return q
754 755

    # Sort by source version
756 757 758
    a_version = a_changes.get("version", "0")
    b_version = b_changes.get("version", "0")
    q = apt_pkg.VersionCompare(a_version, b_version)
759
    if q:
760
        return q
761

762
    # Sort by 'have source'
763 764
    a_has_source = a_changes["architecture"].get("source")
    b_has_source = b_changes["architecture"].get("source")
765
    if a_has_source and not b_has_source:
766
        return -1
767
    elif b_has_source and not a_has_source:
768
        return 1
769

770
    # Fall back to sort by filename
771
    return cmp(a, b)
772 773

################################################################################
774 775

def find_next_free (dest, too_many=100):
776 777
    extra = 0
    orig_dest = dest
778
    while os.path.exists(dest) and extra < too_many:
779 780
        dest = orig_dest + '.' + repr(extra)
        extra += 1
781
    if extra >= too_many:
J
Joerg Jaspert 已提交
782
        raise NoFreeFilenameError
783
    return dest
J
James Troup 已提交
784

785
################################################################################
786 787

def result_join (original, sep = '\t'):
788
    list = []
789 790
    for i in xrange(len(original)):
        if original[i] == None:
791
            list.append("")
792
        else:
793 794
            list.append(original[i])
    return sep.join(list)
795

796 797
################################################################################

798
def prefix_multi_line_string(str, prefix, include_blank_lines=0):
799
    out = ""
800
    for line in str.split('\n'):
801
        line = line.strip()
802
        if line or include_blank_lines:
803
            out += "%s%s\n" % (prefix, line)
804 805
    # Strip trailing new line
    if out:
806 807
        out = out[:-1]
    return out
808

809
################################################################################
810

811
def validate_changes_file_arg(filename, require_changes=1):
812 813
    """'filename' is either a .changes or .dak file.  If 'filename' is a
.dak file, it's changed to be the corresponding .changes file.  The
814 815 816 817 818 819 820 821 822 823
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.
"""
824
    error = None
825

826
    orig_filename = filename
827
    if filename.endswith(".dak"):
828
        filename = filename[:-4]+".changes"
829

830
    if not filename.endswith(".changes"):
831
        error = "invalid file type; not a changes file"
832
    else:
833 834
        if not os.access(filename,os.R_OK):
            if os.path.exists(filename):
835
                error = "permission denied"
836
            else:
837
                error = "file not found"
838 839

    if error:
840
        if require_changes == 1:
841
            fubar("%s: %s." % (orig_filename, error))
842
        elif require_changes == 0:
843 844
            warn("Skipping %s - %s" % (orig_filename, error))
            return None
845
        else: # We only care about the .dak file
846
            return filename
847
    else:
848
        return filename
849 850 851

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

852
def real_arch(arch):
853
    return (arch != "source" and arch != "all")
854 855 856

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

857
def join_with_commas_and(list):
858 859 860
    if len(list) == 0: return "nothing"
    if len(list) == 1: return list[0]
    return ", ".join(list[:-1]) + " and " + list[-1]
861 862 863

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

864
def pp_deps (deps):
865
    pp_deps = []
866
    for atom in deps:
867
        (pkg, version, constraint) = atom
868
        if constraint:
869
            pp_dep = "%s (%s %s)" % (pkg, constraint, version)
870
        else:
871 872 873
            pp_dep = pkg
        pp_deps.append(pp_dep)
    return " |".join(pp_deps)
874 875 876

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

877
def get_conf():
878
    return Cnf
879 880 881

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

882 883 884 885
# Handle -a, -c and -s arguments; returns them as SQL constraints
def parse_args(Options):
    # Process suite
    if Options["Suite"]:
886
        suite_ids_list = []
887
        for suite in split_args(Options["Suite"]):
J
James Troup 已提交
888
            suite_id = database.get_suite_id(suite)
889
            if suite_id == -1:
890
                warn("suite '%s' not recognised." % (suite))
891
            else:
892
                suite_ids_list.append(suite_id)
893
        if suite_ids_list:
894
            con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
895
        else:
896
            fubar("No valid suite given.")
897
    else:
898
        con_suites = ""
899 900 901

    # Process component
    if Options["Component"]:
902
        component_ids_list = []
903
        for component in split_args(Options["Component"]):
J
James Troup 已提交
904
            component_id = database.get_component_id(component)
905
            if component_id == -1:
906
                warn("component '%s' not recognised." % (component))
907
            else:
908
                component_ids_list.append(component_id)
909
        if component_ids_list:
910
            con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
911
        else:
912
            fubar("No valid component given.")
913
    else:
914
        con_components = ""
915 916

    # Process architecture
917
    con_architectures = ""
918
    if Options["Architecture"]:
919 920
        arch_ids_list = []
        check_source = 0
921
        for architecture in split_args(Options["Architecture"]):
922
            if architecture == "source":
923
                check_source = 1
924
            else:
J
James Troup 已提交
925
                architecture_id = database.get_architecture_id(architecture)
926
                if architecture_id == -1:
927
                    warn("architecture '%s' not recognised." % (architecture))
928
                else:
929
                    arch_ids_list.append(architecture_id)
930
        if arch_ids_list:
931
            con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
932 933
        else:
            if not check_source:
934
                fubar("No valid architecture given.")
935
    else:
936
        check_source = 1
937

938
    return (con_suites, con_architectures, con_components, check_source)
939 940 941

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

942 943 944 945
# Inspired(tm) by Bryn Keller's print_exc_plus (See
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)

def print_exc():
946
    tb = sys.exc_info()[2]
947
    while tb.tb_next:
948 949 950
        tb = tb.tb_next
    stack = []
    frame = tb.tb_frame
951
    while frame:
952 953 954 955
        stack.append(frame)
        frame = frame.f_back
    stack.reverse()
    traceback.print_exc()
956 957 958
    for frame in stack:
        print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
                                             frame.f_code.co_filename,
959
                                             frame.f_lineno)
960
        for key, value in frame.f_locals.items():
961
            print "\t%20s = " % key,
962
            try:
963
                print value
964
            except:
965
                print "<unable to print>"
966 967 968

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

J
James Troup 已提交
969 970
def try_with_debug(function):
    try:
971
        function()
J
James Troup 已提交
972
    except SystemExit:
973
        raise
J
James Troup 已提交
974
    except:
975
        print_exc()
J
James Troup 已提交
976 977 978

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

J
James Troup 已提交
979 980 981 982 983
# 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":
984
        return 0
J
James Troup 已提交
985
    elif a == "source":
986
        return -1
J
James Troup 已提交
987
    elif b == "source":
988
        return 1
J
James Troup 已提交
989

990
    return cmp (a, b)
J
James Troup 已提交
991 992 993

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

994 995
# Split command line arguments which can be separated by either commas
# or whitespace.  If dwim is set, it will complain about string ending
996
# in comma since this usually means someone did 'dak ls -a i386, m68k
997 998 999 1000 1001
# 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:
1002
        return s.split()
1003 1004
    else:
        if s[-1:] == "," and dwim:
1005 1006
            fubar("split_args: found trailing comma, spurious space maybe?")
        return s.split(",")
1007 1008 1009

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

1010 1011 1012 1013 1014 1015 1016
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):
1017 1018 1019 1020 1021
    cmd = ['/bin/sh', '-c', cmd]
    p2cread, p2cwrite = os.pipe()
    c2pread, c2pwrite = os.pipe()
    errout, errin = os.pipe()
    pid = os.fork()
1022 1023
    if pid == 0:
        # Child
1024 1025 1026 1027 1028 1029
        os.close(0)
        os.close(1)
        os.dup(p2cread)
        os.dup(c2pwrite)
        os.close(2)
        os.dup(errin)
1030 1031 1032
        for i in range(3, 256):
            if i != status_write:
                try:
1033
                    os.close(i)
1034
                except:
1035
                    pass
1036
        try:
1037
            os.execvp(cmd[0], cmd)
1038
        finally:
1039
            os._exit(1)
1040

1041
    # Parent
1042
    os.close(p2cread)
1043 1044
    os.dup2(c2pread, c2pwrite)
    os.dup2(errout, errin)
1045

1046
    output = status = ""
1047
    while 1:
1048 1049
        i, o, e = select.select([c2pwrite, errin, status_read], [], [])
        more_data = []
1050
        for fd in i:
1051
            r = os.read(fd, 8196)
1052
            if len(r) > 0:
1053
                more_data.append(fd)
1054
                if fd == c2pwrite or fd == errin:
1055
                    output += r
1056
                elif fd == status_read:
1057
                    status += r
1058
                else:
1059
                    fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
1060 1061 1062
        if not more_data:
            pid, exit_status = os.waitpid(pid, 0)
            try:
1063 1064 1065 1066 1067 1068 1069
                os.close(status_write)
                os.close(status_read)
                os.close(c2pread)
                os.close(c2pwrite)
                os.close(p2cwrite)
                os.close(errin)
                os.close(errout)
1070
            except:
1071 1072
                pass
            break
1073

1074
    return output, status, exit_status
1075

1076
################################################################################
1077

1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
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:]
1095
        if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113
            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:
1114
        keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
1115 1116 1117 1118 1119 1120

    # 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
1121
    status_read, status_write = os.pipe();
1122 1123
    cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
    (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
1124

1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148
    # 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 ""

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

1149 1150 1151 1152 1153 1154 1155 1156
def gpg_keyring_args(keyrings=None):
    if not keyrings:
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")

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

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

1157
def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
1158 1159 1160 1161 1162
    """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,
1163
the second is an optional prefix string.  It's possible for reject()
1164 1165 1166
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
1167 1168 1169
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."""
1170 1171

    # Ensure the filename contains no shell meta-characters or other badness
1172
    if not re_taint_free.match(sig_filename):
1173 1174
        reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
        return None
1175 1176

    if data_filename and not re_taint_free.match(data_filename):
1177 1178
        reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
        return None
1179 1180

    if not keyrings:
1181
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1182

1183 1184 1185 1186 1187 1188 1189 1190 1191
    # 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

1192
    # Build the command line
1193
    status_read, status_write = os.pipe();
1194 1195 1196
    cmd = "gpgv --status-fd %s %s %s %s" % (
        status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)

1197
    # Invoke gpgv on the file
1198
    (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1199 1200

    # Process the status-fd output
1201
    (keywords, internal_error) = process_gpgv_output(status)
1202 1203 1204

    # If we failed to parse the status-fd output, let's just whine and bail now
    if internal_error:
1205 1206 1207 1208
        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
1209

1210
    bad = ""
1211 1212
    # Now check for obviously bad things in the processed output
    if keywords.has_key("KEYREVOKED"):
1213 1214
        reject("The key used to sign %s has been revoked." % (sig_filename))
        bad = 1
1215
    if keywords.has_key("BADSIG"):
1216 1217
        reject("bad signature on %s." % (sig_filename))
        bad = 1
1218
    if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1219 1220
        reject("failed to check signature on %s." % (sig_filename))
        bad = 1
1221
    if keywords.has_key("NO_PUBKEY"):
1222
        args = keywords["NO_PUBKEY"]
1223
        if len(args) >= 1:
1224 1225 1226
            key = args[0]
        reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
        bad = 1
1227
    if keywords.has_key("BADARMOR"):
1228 1229
        reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
        bad = 1
1230
    if keywords.has_key("NODATA"):
1231 1232
        reject("no signature found in %s." % (sig_filename))
        bad = 1
J
Joerg Jaspert 已提交
1233 1234 1235 1236
    if keywords.has_key("EXPKEYSIG"):
        args = keywords["EXPKEYSIG"]
        if len(args) >= 1:
            key = args[0]
1237
        reject("Signature made by expired key 0x%s" % (key))
J
Joerg Jaspert 已提交
1238
        bad = 1
1239
    if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1240
        args = keywords["KEYEXPIRED"]
J
Joerg Jaspert 已提交
1241
        expiredate=""
1242
        if len(args) >= 1:
J
Joerg Jaspert 已提交
1243 1244 1245 1246 1247 1248
            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))
1249
        bad = 1
1250 1251

    if bad:
1252
        return None
1253 1254 1255

    # Next check gpgv exited with a zero return code
    if exit_status:
1256
        reject("gpgv failed while checking %s." % (sig_filename))
1257
        if status.strip():
1258
            reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1259
        else:
1260 1261
            reject(prefix_multi_line_string(output, " [GPG output:] "), "")
        return None
1262 1263 1264

    # Sanity check the good stuff we expect
    if not keywords.has_key("VALIDSIG"):
1265 1266
        reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
        bad = 1
1267
    else:
1268
        args = keywords["VALIDSIG"]
1269
        if len(args) < 1:
1270 1271
            reject("internal error while checking signature on %s." % (sig_filename))
            bad = 1
1272
        else:
1273
            fingerprint = args[0]
1274
    if not keywords.has_key("GOODSIG"):
1275 1276
        reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
        bad = 1
1277
    if not keywords.has_key("SIG_ID"):
1278 1279
        reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
        bad = 1
1280 1281 1282 1283

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

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

    if bad:
1292
        return None
1293
    else:
1294
        return fingerprint
1295 1296 1297

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

1298
def gpg_get_key_addresses(fingerprint):
1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313
    """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
1314 1315 1316

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

1317 1318 1319
# Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603

def wrap(paragraph, max_length, prefix=""):
1320 1321 1322 1323
    line = ""
    s = ""
    have_started = 0
    words = paragraph.split()
1324 1325

    for word in words:
1326
        word_size = len(word)
1327 1328
        if word_size > max_length:
            if have_started:
1329 1330
                s += line + '\n' + prefix
            s += word + '\n' + prefix
1331 1332
        else:
            if have_started:
1333
                new_length = len(line) + word_size + 1
1334
                if new_length > max_length:
1335 1336
                    s += line + '\n' + prefix
                    line = word
1337
                else:
1338
                    line += ' ' + word
1339
            else:
1340 1341
                line = word
        have_started = 1
1342 1343

    if have_started:
1344
        s += line
1345

1346
    return s
1347 1348 1349 1350 1351 1352

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

# Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
# Returns fixed 'src'
def clean_symlink (src, dest, root):
1353 1354 1355 1356 1357
    src = src.replace(root, '', 1)
    dest = dest.replace(root, '', 1)
    dest = os.path.dirname(dest)
    new_src = '../' * len(dest.split('/'))
    return new_src + src
1358 1359 1360

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

1361 1362 1363 1364 1365 1366
def temp_filename(directory=None, dotprefix=None, perms=0700):
    """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 'dotprefix' is non-null, the filename will be prefixed with a '.'."""

    if directory:
1367 1368
        old_tempdir = tempfile.tempdir
        tempfile.tempdir = directory
1369

1370
    filename = tempfile.mktemp()
1371 1372

    if dotprefix:
1373 1374 1375
        filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
    fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
    os.close(fd)
1376 1377

    if directory:
1378
        tempfile.tempdir = old_tempdir
1379

1380
    return filename
1381 1382 1383

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

T
Thomas Viehmann 已提交
1384 1385 1386
# checks if the user part of the email is listed in the alias file

def is_email_alias(email):
T
Thomas Viehmann 已提交
1387 1388 1389 1390 1391 1392 1393 1394 1395
    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 已提交
1396 1397 1398

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

1399
apt_pkg.init()
1400

1401 1402
Cnf = apt_pkg.newConfiguration()
apt_pkg.ReadConfigFileISC(Cnf,default_config)
1403 1404

if which_conf_file() != default_config:
1405
    apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1406 1407

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