[Secure-testing-commits] r1934 - / bin lib lib/python stamps

Florian Weimer fw at costa.debian.org
Mon Sep 12 16:32:24 UTC 2005


Author: fw
Date: 2005-09-12 16:32:23 +0000 (Mon, 12 Sep 2005)
New Revision: 1934

Added:
   Makefile
   bin/check-syntax
   bin/update-bug-list-db
   lib/
   lib/python/
   lib/python/bugs.py
   lib/python/debian_support.py
   lib/python/security_db.py
   stamps/
Log:
Add list parser written in Python.

"make check" runs a syntax check (no SQLite required).  "make all"
updates the SQLite database, and performs cross-list consistency checks.

There is some support for loading Debian Package/Sources files, but this
information is currently not used by the checks.


Added: Makefile
===================================================================
--- Makefile	2005-09-12 16:27:44 UTC (rev 1933)
+++ Makefile	2005-09-12 16:32:23 UTC (rev 1934)
@@ -0,0 +1,33 @@
+PYTHON = python
+PYTHON_MODULES = $(wildcard lib/python/*.py)
+BUG_LISTS = $(wildcard data/*/list)
+
+all: stamps/bug-lists-imported
+
+stamps/bug-lists-imported: bin/update-bug-list-db \
+	$(BUG_LISTS) $(PYTHON_MODULES)
+	$(PYTHON) bin/update-bug-list-db
+	touch $@
+
+.PHONY: check check-syntax
+
+test check: check-syntax
+
+check-syntax: stamps/CAN-syntax stamps/CVE-syntax \
+	stamps/DSA-syntax stamps/DTSA-syntax
+
+stamps/CAN-syntax: data/CAN/list bin/check-syntax $(PYTHON_MODULES)
+	$(PYTHON) bin/check-syntax CAN data/CAN/list
+	touch $@
+
+stamps/CVE-syntax: data/CVE/list bin/check-syntax $(PYTHON_MODULES)
+	$(PYTHON) bin/check-syntax CVE data/CVE/list
+	touch $@
+
+stamps/DSA-syntax: data/DSA/list bin/check-syntax $(PYTHON_MODULES)
+	$(PYTHON) bin/check-syntax DSA data/DSA/list
+	touch $@
+
+stamps/DTSA-syntax: data/DTSA/list bin/check-syntax $(PYTHON_MODULES)
+	$(PYTHON) bin/check-syntax DTSA data/DTSA/list
+	touch $@

Added: bin/check-syntax
===================================================================
--- bin/check-syntax	2005-09-12 16:27:44 UTC (rev 1933)
+++ bin/check-syntax	2005-09-12 16:32:23 UTC (rev 1934)
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+
+import os
+import os.path
+import string
+import sys
+
+def setup_paths():
+    check_file = 'lib/python/debian_support.py'
+    path = os.getcwd()
+    while 1:
+        if os.path.exists("%s/%s" % (path, check_file)):
+            sys.path = [path + '/lib/python'] + sys.path
+            return path
+        idx = string.rfind(path, '/')
+        if idx == -1:
+            raise ImportError, "could not setup paths"
+        path = path[0:idx]
+root_path = setup_paths()
+
+import bugs
+
+def do_parse(f):
+    names = {}
+    errors = False
+    for r in f:
+        n = r.name
+        if n[0:4] in ('CAN', 'CVE'):
+            n = n[4:]
+        if names.has_key(n):
+            if names[n] <> r.name:
+                sys.stderr.write("error: duplicate CVE entry: %s and %s\n"
+                                 % (names[n], r.name))
+            else:
+                sys.stderr.write("error: duplicate CVE entry: %s\n"
+                                 % r.name)
+            errors = True
+        names[n] = r.name
+    if errors:
+        sys.exit(1)
+    
+
+def parse_CAN(name):
+    do_parse(bugs.CVEFile(name))
+
+def parse_CVE(name):
+    f = bugs.CVEFile(name)
+    # Relax syntax checking a bit.
+    f.no_version_needs_note = False
+    do_parse(f)
+
+def parse_DSA(name):
+    do_parse(bugs.DSAFile(name))
+
+def parse_DTSA(name):
+    do_parse(bugs.DTSAFile(name))
+
+file_types = {'CAN' : parse_CAN,
+              'CVE' : parse_CVE,
+              'DSA' : parse_DSA,
+              'DTSA' : parse_DTSA}
+
+if len(sys.argv) <> 3 or not file_types.has_key(sys.argv[1]):
+    l = file_types.keys()
+    l.sort()
+    sys.stderr.write("usage: check-syntax {%s} file-name\n"
+                     % '|'.join(l))
+    sys.exit(1)
+
+file_types[sys.argv[1]](sys.argv[2])


Property changes on: bin/check-syntax
___________________________________________________________________
Name: svn:executable
   + *

Added: bin/update-bug-list-db
===================================================================
--- bin/update-bug-list-db	2005-09-12 16:27:44 UTC (rev 1933)
+++ bin/update-bug-list-db	2005-09-12 16:32:23 UTC (rev 1934)
@@ -0,0 +1,50 @@
+#!/usr/bin/python
+
+import os
+import os.path
+import string
+import sys
+
+def setup_paths():
+    check_file = 'lib/python/debian_support.py'
+    path = os.getcwd()
+    while 1:
+        if os.path.exists("%s/%s" % (path, check_file)):
+            sys.path = [path + '/lib/python'] + sys.path
+            return path
+        idx = string.rfind(path, '/')
+        if idx == -1:
+            raise ImportError, "could not setup paths"
+        path = path[0:idx]
+root_path = setup_paths()
+
+import bugs
+import security_db
+
+db_file = root_path + '/data/security.db'
+new_file = not os.path.exists(db_file)
+db = security_db.DB(db_file)
+if new_file:
+    db.initSchema()
+cursor = db.writeTxn()
+db.deleteBugs(cursor)
+try:
+    db.insertBugs(cursor, bugs.CVEFile(root_path + '/data/CAN/list'))
+    db.insertBugs(cursor, bugs.CVEFile(root_path + '/data/CVE/list',
+                                       no_version_needs_note=False))
+    db.insertBugs(cursor, bugs.DSAFile(root_path + '/data/DSA/list'))
+    db.insertBugs(cursor, bugs.DTSAFile(root_path + '/data/DTSA/list'))
+except security_db.InsertError, e:
+    db.rollback(cursor)
+    for err in e.errors:
+        print err
+    sys.exit(1)
+
+warnings = db.finishBugs(cursor)
+if warnings:
+    db.rollback(cursor)
+    for x in warnings:
+        print "error:", x
+    sys.exit(1)
+else:
+    db.commit(cursor)


Property changes on: bin/update-bug-list-db
___________________________________________________________________
Name: svn:executable
   + *


Property changes on: lib/python
___________________________________________________________________
Name: svn:ignore
   + *.pyc
*.pyo
*.db


Added: lib/python/bugs.py
===================================================================
--- lib/python/bugs.py	2005-09-12 16:27:44 UTC (rev 1933)
+++ lib/python/bugs.py	2005-09-12 16:32:23 UTC (rev 1934)
@@ -0,0 +1,657 @@
+# bugs.py -- read bug lists used by Debian's testing security team
+# Copyright (C) 2005 Florian Weimer <fw at deneb.enyo.de>
+# 
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import debian_support
+import re
+import types
+
+class Urgency(debian_support.PseudoEnum): pass
+
+def listUrgencies():
+    urgencies = {}
+    urgs = ("high", "medium", "low", "unimportant", "unknown")
+    for u in range(len(urgs)):
+        urgencies[urgs[u]] = Urgency(urgs[u], -u)
+    Urgency.urgencies = urgencies
+    return urgencies
+def internUrgency(name, urgencies=listUrgencies()):
+    if urgencies.has_key(name):
+        return urgencies[name]
+    else:
+        return None
+del listUrgencies
+
+class PackageNote:
+    """A package note.
+
+    The following member variables are defined:
+
+    release - the release the package note applies to; None means "testing",
+              notes for other releases never apply to testing
+    """
+    
+    def __init__(self, package, fixed_version, release, urgency):
+        self.id = None
+        self.package = package
+        if (fixed_version is not None
+            and type(fixed_version) == types.StringType):
+            self.fixed_version = debian_support.Version(fixed_version)
+        else:
+            self.fixed_version = fixed_version
+        if release == '':
+            self.release = None
+        else:
+            if type(release) == types.StringType:
+                release = debian_support.internRelease(release)
+                if release is None:
+                    raise ValueError, "invalid release"
+            self.release = release
+        if type(urgency) == types.StringType:
+            urgency = internUrgency(urgency)
+        if urgency is None:
+            raise ValueError, "invalid urgency"
+        self.urgency = urgency
+        self.bugs = []
+
+    def affects(self, version, release=None):
+        """Returns true if this package note affects the given version.
+
+        Both version and release can be strings.  In this case, they
+        are automatically promoted to the correct Python objects.
+        """
+
+        if type(version) == types.StringType:
+            version = debian_support.Version(version)
+        if type(release) == types.ReleaseType:
+            release = Release(release)
+
+        if release is None:
+            if self.release is not None:
+                # If there's a release spec, and we are running for
+                # testing, this note does apply.
+                return False
+        else:
+            if self.release is not None and self.release <> release:
+                # If there's a release spec, it must match ours.
+                return False
+        # Standard version comparison if the releases match.
+        return self.version is None or version < self.version
+
+    def writeDB(self, cursor, bug_name):
+        """Writes the object to an SQLite database.
+
+        If the id attibute is already set, it is assumed that the
+        object has already been written.
+        """
+
+        if self.id is not None:
+            return
+                        
+        if self.fixed_version:
+            v = str(self.fixed_version)
+        else:
+            v = None
+        cursor.execute("""INSERT INTO package_notes
+        (bug_name, package, fixed_version, release, urgency)
+        VALUES (?, ?, ?, ?, ?)""",
+                       (bug_name, self.package, v, self.release or '',
+                        str(self.urgency)))
+        for (rowid,) in cursor.execute('SELECT last_insert_rowid()'):
+            self.id = rowid
+            for b in self.bugs:
+                cursor.execute("""INSERT INTO debian_bugs (bug, note)
+                VALUES (?, ?)""", (b, rowid))
+            return
+        assert False
+
+    def loadBugs(self, cursor):
+        assert type(self.id) == types.IntType, self.id
+        assert len(self.bugs) == 0
+        for (b,) in cursor.execute\
+                ("SELECT bug FROM debian_bugs WHERE note = ?", (self.id,)):
+            self.bugs.append(int(b))
+
+class PackageNoteFromDB(PackageNote):
+    def __init__(self, cursor, nid):
+        for bug_name, package, fixed_version, release, urgency \
+            in cursor.execute\
+            ("""SELECT bug_name, package, fixed_version, release, urgency
+            FROM package_notes WHERE id = ?""", (nid,)):
+            PackageNote.__init__(package, fixed_version, release, urgency)
+            self.id = nid
+            self.bug_name = bug_name
+            self.loadBugs(cursor)
+            return
+        raise ValueError, "invalid package note ID %d" % id
+
+class PackageNoteParsed(PackageNote):
+    """Subclass with a constructor that parses package notes."""
+
+    re_bug = re.compile(r'^bug #(\d+)$')
+    re_notes_split = re.compile(r'\s*;\s+') 
+
+    def __init__(self, package, version, notes):
+        rel = None
+        bugs = []
+        urgency = "unknown"
+        if notes is not None:
+            for n in self.re_notes_split.split(notes):
+                u = internUrgency(n)
+                if u:
+                    urgency = u
+                    continue
+                
+                r = debian_support.internRelease(n)
+                if r:
+                    rel = r
+                    continue
+
+                match = self.re_bug.match(n)
+                if match:
+                    (bug,) = match.groups()
+                    bugs.append(int(bug))
+                    continue
+
+                if n == 'unfixed':
+                    self.unfixed = True
+                    continue
+
+                raise SyntaxError , 'unknown package note %s\n' % `n`
+        PackageNote.__init__(self, package, version, rel, urgency)
+        self.bugs = bugs
+
+class BugBase:
+    "Base class for entries in the bug list."""
+
+    re_cve_name = re.compile(r'^(?:CAN|CVE)-\d{4}-\d{4}$')
+
+    def __init__(self, fname, lineno, date, name, description, comments):
+        assert type(fname) == types.StringType
+        assert type(lineno) == types.IntType
+        self.source_file = fname
+        self.source_line = lineno
+        self.date = date
+        self.name = name
+        self.description = description
+        self.comments = comments
+        self.notes = []
+        self.xref = []
+        self.not_for_us = False
+
+    def isFromCVE(self):
+        """Returns True if the name has been officially assigned.
+
+        Our database is mostly CVE-driven, but sometimes we need names
+        which have not been assigned yet.  Therefore, we generate
+        identifiers on the fly.
+        """
+        return self.re_cve_name.match(self.name) is not None
+
+    def cveStatus(self):
+        if self.isFromCVE():
+            if self.name[0:4] == 'CVE':
+                return 'ASSIGNED'
+            return 'CANDIDATE'
+        else:
+            return ''
+
+    def writeDB(self, cursor):
+        """Writes the record to an SQLite3 database."""
+
+        if self.not_for_us:
+            not_for_us = 1
+        else:
+            not_for_us = 0
+
+        import apsw
+        try:
+            cursor.execute("""INSERT INTO bugs
+            (name, cve_status, not_for_us, description,
+             source_file, source_line)
+            VALUES (?, ?, ?, ?, ?, ?)""",
+                           (self.name, self.cveStatus(), not_for_us,
+                            self.description,
+                            self.source_file, self.source_line))
+        except apsw.ConstraintError:
+            raise ValueError, "bug name %s is not unique" % self.name
+
+        for (typ, c) in self.comments:
+            cursor.execute("""INSERT INTO bugs_notes
+            (bug_name, typ, comment) VALUES (?, ?, ?)""",
+                           (self.name, typ, c))
+
+        for n in self.notes:
+            n.writeDB(cursor, self.name)
+
+        for x in self.xref:
+            try:
+                cursor.execute("""INSERT INTO bugs_xref
+                (source, target) VALUES (?, ?)""",
+                               (self.name, x))
+            except apsw.ConstraintError:
+                raise ValueError, \
+                      "cross reference to %s appears multiple times" % x
+
+class Bug(BugBase):
+    """Class for bugs for which we have some data."""
+
+    def __init__(self, fname, lineno, date, name, description, comments, notes,
+                 xref, not_for_us=False):
+        assert len(notes) == 0 or isinstance(notes[0], PackageNote)
+        assert len(xref) == 0 or type(xref[0]) == types.StringType
+        assert type(not_for_us) == types.BooleanType
+        BugBase.__init__(self, fname, lineno, date, name,
+                         description, comments)
+        self.notes = notes
+        self.xref = xref
+        self.not_for_us = not_for_us
+
+class BugFromDB(Bug):
+    def __init__(self, cursor, name):
+        assert type(name) == types.StringType
+        for r in cursor.execute('SELECT * FROM bugs WHERE name = ?', (name,)):
+            rdesc = cursor.getdescription()
+            data = {}
+            for j in range(len(rdesc)):
+                data[rdesc[j][0]] = r[j]
+            # FIXME: load date
+            Bug.__init__(self, data['source_file'], data['source_line'],
+                         None, name, data['description'], comments=[],
+                         notes=[], xref=[],
+                         not_for_us=not not data['not_for_us'])
+            for (x,) in cursor.execute\
+                ('SELECT target FROM bugs_xref WHERE source = ?', (name,)):
+                self.xref.append(x)
+            for (t, c) in cursor.execute\
+                ("""SELECT typ, comment FROM bugs_notes
+                WHERE bug_name = ?
+                ORDER BY rowid""",
+                 (name,)):
+                self.comments.append((t, c))
+
+            # temporary list required because loadBugs needs the cursor
+            for nid, package, fixed_version, release, urgency \
+                    in list(cursor.execute
+                ("""SELECT id, package, fixed_version, release, urgency
+                FROM package_notes WHERE bug_name = ?""", (name,))):
+                n = PackageNote(package, fixed_version, release, urgency)
+                n.id = nid
+                n.bug_name = name
+                n.loadBugs(cursor)
+                self.notes.append(n)
+            return
+        raise ValueError, "unknown bug " + `name`
+
+class BugReservedCVE(BugBase):
+    """Class for reserved CVE entries."""
+    def __init__(self, fname, lineno, name, comments=None):
+        if comments is None:
+            comments = []
+        BugBase.__init__(self, fname, lineno, None, name, "RESERVED", comments)
+    def cveStatus(self):
+        return 'RESERVED'
+
+class BugRejectedCVE(BugBase):
+    """Class for rejected CVE entries."""
+    def __init__(self, fname, lineno, name):
+        BugBase.__init__(self, fname, lineno, None, name, "REJECTED", [])
+    def cveStatus(self):
+        return 'REJECTED'
+
+class FileBase(debian_support.PackageFile):
+    re_non_ascii = re.compile(r'.*([^\n\t -~]).*')
+    re_empty = re.compile(r'^(?:\s*$|--)')
+    re_indent = re.compile(r'^\s+(.*?)\s*$')
+    re_begin_claim = re.compile(r'^begin claimed by (\S+)\s*$')
+    re_end_claim = re.compile(r'^end claimed by (\S+)\s*$')
+    re_stop = re.compile(r'^STOP:')
+
+    re_xref_required = re.compile(r'^\{')
+    re_xref = re.compile(r'^\{\s*([^\}]+?)\s*\}$')
+    re_whitespace = re.compile(r'\s+')
+    re_xref_entry = re.compile('^(?:(?:CAN|CVE)-\d{4}-\d{4}'
+                               + r'|VU#\d{6}'
+                               + r'|DSA-\d+(?:-\d+)?|DTSA-\d+-\d+)$')
+    
+    re_package_required = re.compile(r'^-')
+    re_package = re.compile(r'^- ([A-Za-z0-9:.+-]+)'
+                            + r'(?:\s+([A-Za-z0-9:.+-]+))?\s*(?:\((.*)\))?$')
+    re_not_for_us_required = re.compile(r'^NOTE:\s+not?e?-fo?r-u')
+    re_not_for_us = re.compile(r'^NOTE:\s+not-for-us(?:\s+\((.*)\))?\s*$')
+    re_reserved = re.compile(r'^NOTE:\s+reserved\s*$')
+    re_rejected = re.compile(r'^NOTE:\s+rejected\s*$')
+    re_note = re.compile(r'^NOTE:\s+(.*)$')
+    re_todo = re.compile(r'^TODO:\s+(.*)$')
+
+    def isUniqueName(self, name):
+        """Returns True if the name is a real, unique name."""
+        return True
+
+    def matchHeader(self, line):
+        """Parses the header of a record.
+
+        Must be overriden by child classes."""
+        assert False
+
+    def getLine(self):
+        while 1:
+            self.line = self.file.readline()
+            self.lineno += 1
+
+            if self.line == '' or not self.re_empty.match(self.line):
+                break
+
+        match = self.re_non_ascii.match(self.line)
+        if match is not None:
+            self.raiseSyntaxError('invalid non-printable character %s'
+                                  % `match.groups()[0]`)
+
+    def rawRecords(self):
+        """Generator which returns raw records.
+
+        These records are 4-tuples with the following contents:
+
+        - line number of the start of the record
+        - release data; can be None
+        - something which resembles a CVE name; is not necessarily unique
+          if it does not match the CVE syntax
+        - part of the CVE description
+        - subrecords, a list of pairs line number/string
+        """
+
+        self.getLine()
+        record = []
+        while self.line:
+            first_line = self.lineno
+           
+            if self.re_stop.match(self.line):
+                # Theoretically, we could stop here, but we want
+                # syntax checks for the remaining records, too.
+                self.getLine()
+                continue
+                
+            # We ignore claims, but check their syntax nevertheless.
+            match = self.re_begin_claim.match(self.line)
+            if match:
+                self.getLine()
+                continue
+            match = self.re_end_claim.match(self.line)
+            if match:
+                self.getLine()
+                continue
+            
+            (date, record_name, description) = self.matchHeader(self.line)
+
+            record = []
+            while self.line:
+                self.getLine()
+
+                match = self.re_indent.match(self.line)
+                if match:
+                    (r,) = match.groups()
+                    record.append((self.lineno, r))
+                else:
+                    break
+            # line contains the next line at this point.
+
+            yield (first_line, date, record_name, description, record)
+
+    def __iter__(self):
+        """Generator for Bug objects."""
+        for (first_lineno, date, record_name, description, record)\
+                in self.rawRecords():
+
+            not_for_us = None
+            xref = []
+            pkg_notes = []
+            comments = []
+            cve_reserved = False
+            cve_rejected = False
+            first_bug = 0
+
+            for (lineno, r) in record:
+                if self.re_xref_required.match(r):
+                    match = self.re_xref.match(r)
+                    if match:
+                        (xref_string,) = match.groups()
+                        for x in self.re_whitespace.split(xref_string):
+                            if self.re_xref_entry.match(x):
+                                xref.append(x)
+                            else:
+                                self.raiseSyntaxError\
+                                    ("invalid cross reference " + `x`, lineno)
+                        continue
+                    else:
+                        self.raiseSyntaxError("expected cross reference, got: "
+                                              + `r`, lineno)
+
+                if self.re_package_required.match(r):
+                    match = self.re_package.match(r)
+                    if match:
+                        (p, v, d) = match.groups()
+                        if v is None and d is None and \
+                               self.no_version_needs_note:
+                                raise SyntaxError, \
+                                'version-less package entry requires note'
+
+                        if v == 'not-affected':
+                            # '0' is the minimum version number possible.
+                            pkg_notes.append(PackageNoteParsed(p, '0', None))
+                            # 'd' is a free-form field in this case.
+                            comments.append(('NOTE', d))
+                        else:
+                            x = PackageNoteParsed(p, v, d)
+                            pkg_notes.append(x)
+                            if first_bug == 0 and len(x.bugs) > 0:
+                                first_bug = x.bugs[0]
+                    else:
+                        self.raiseSyntaxError("expected package entry, got: "
+                                              + `r`, lineno)
+                    continue
+
+                if self.re_not_for_us_required.match(r):
+                    match = self.re_not_for_us.match(r)
+                    if match:
+                        (not_for_us,) = match.groups()
+                        if not_for_us is None:
+                            not_for_us = ''
+                        continue
+                    else:
+                        self.raiseSyntaxError("expected not-for-us entry, "
+                                              + "got: " + `r`, lineno)
+
+                match = self.re_reserved.match(r)
+                if match:
+                    cve_reserved = True
+                    continue
+
+                match = self.re_rejected.match(r)
+                if match:
+                    cve_rejected = True
+                    continue
+
+                match = self.re_note.match(r)
+                if match:
+                    (note,) = match.groups()
+                    comments.append(('NOTE', note))
+                    continue
+
+                match = self.re_todo.match(r)
+                if match:
+                    (todo,) = match.groups()
+                    comments.append(('TODO', todo))
+                    continue
+
+                self.raiseSyntaxError('expected CAN/CVE annotation, got: %s'
+                                      % `r`, lineno)
+                break
+
+            if cve_reserved:
+                if not self.isUniqueName(record_name):
+                    self.raiseSyntaxError\
+                        ('reserved CVE entries must have CAN/CVE names',
+                         first_lineno)
+                if len(pkg_notes) > 0:
+                    # The bug has extra data even though it is marked
+                    # reserved by CVE, we have to issue the full
+                    # version because the official CVE lags a bit.
+                    yield Bug(self.file.name, first_lineno, date,
+                              record_name, description, comments,
+                              notes=pkg_notes, xref=xref)
+                else:
+                    yield BugReservedCVE(self.file.name, first_lineno,
+                                         record_name, comments)
+
+            elif cve_rejected:
+                if not self.isUniqueName(record_name):
+                    self.raiseSyntaxError\
+                        ('rjeected CVE entries must have CAN/CVE names',
+                         first_lineno)
+                if len(pkg_notes) > 0:
+                    self.raiseSyntaxError\
+                        ('rejected CVE entries must not have notes',
+                         first_lineno)
+                yield BugRejectedCVE(self.file.name, first_lineno, record_name)
+
+            elif not_for_us is not None:
+                if not self.isUniqueName(record_name):
+                    self.raiseSyntaxError\
+                        ('not-for-us bug must have CAN/CVE name', first_lineno)
+                if len(pkg_notes) > 0:
+                    self.raiseSyntaxError\
+                        ('package information not allowed in not-for-us bugs',
+                         first_lineno)
+                yield Bug(self.file.name, first_lineno, date,
+                          record_name, description, comments, notes=[],
+                          xref=xref, not_for_us=True)
+            else:
+                if not self.isUniqueName(record_name):
+                    record_name = 'FAKE-%07d-%06d' % (first_bug, first_lineno)
+                yield Bug(self.file.name, first_lineno, date,
+                          record_name, description,
+                          comments, notes=pkg_notes, xref=xref)
+            
+class CVEFile(FileBase):
+    """A CVE file, as used by the Debian testing security team."""
+    
+    re_cve = re.compile(r'^((?:CAN|CVE)-\d{4}-(?:\d{4}|XXXX))\s+(.*?)\s*$')
+
+    def __init__(self, name, fileObj=None, no_version_needs_note=True):
+        FileBase.__init__(self, name, fileObj)
+        self.no_version_needs_note = no_version_needs_note
+
+    def isUniqueName(self, name):
+        return BugBase.re_cve_name.match(name) is not None
+
+    def matchHeader(self, line):
+        match = self.re_cve.match(line)
+        if not match:
+            self.raiseSyntaxError("expected CVE record, got: %s" % `line`)
+            (record_name, description) = match.groups()
+        return (None,) + match.groups()
+
+class DSAFile(FileBase):
+    """A DSA file.
+
+    Similar to a CVE file, only that it contains DSAs as its main
+    reference point, and release dates.
+    """
+
+    re_dsa = re.compile(r'^\[(\d\d) ([A-Z][a-z][a-z]) (\d{4})\] '
+                        + r'(DSA-\d+(?:-\d+)?)\s+'
+                        + r'(.*?)\s*$')
+
+    month_names = {'Jan': 1,
+                   'Feb': 2,
+                   'Mar': 3,
+                   'Apr': 4,
+                   'May': 5,
+                   'Jun': 6,
+                   'Jul': 7,
+                   'Aug': 8,
+                   'Sep': 9,
+                   'Oct': 10,
+                   'Nov': 11,
+                   'Dec': 12}
+
+    # temporary hack, until we know what "!" actually means.
+    re_package_required = re.compile(r'^[-!]')
+    re_package = re.compile(r'^[-!] ([A-Za-z0-9:.+-]+)'
+                            + r'(?:\s+([A-Za-z0-9:.+-]+))?\s*(?:\((.*)\))?$')
+    def matchHeader(self, line):
+        match = self.re_dsa.match(line)
+        if not match:
+            self.raiseSyntaxError("expected DSA record, got: %s" % `line`)
+            (record_name, description) = match.groups()
+        (day, month, year, name, desc) = match.groups()
+        try:
+            month = self.month_names[month]
+        except KeyError:
+            self.raiseSyntaxError("invalid month name %s" % `month`)
+        return ("%s-%02d-%s" % (year, month, day), name, desc)
+
+class DTSAFile(FileBase):
+    """A DTSA file.
+
+    Like a DSA file, but the date format is different.
+    """
+
+    re_dsa = re.compile\
+             (r'^\[([A-Z][a-z]{3,}) (\d\d?)(?:st|nd|rd|th), (\d{4})\] '
+              + r'(DTSA-\d+-\d+)\s+'
+              + r'(.*?)\s*$')
+    month_names = {'January': 1,
+                   'February': 2,
+                   'March': 3,
+                   'April': 4,
+                   'May': 5,
+                   'June': 6,
+                   'July': 7,
+                   'August': 8,
+                   'September': 9,
+                   'October': 10,
+                   'November': 11,
+                   'December': 12}
+
+    def matchHeader(self, line):
+        match = self.re_dsa.match(line)
+        if not match:
+            self.raiseSyntaxError("expected DTSA record, got: %s" % `line`)
+            (record_name, description) = match.groups()
+        (month, day, year, name, desc) = match.groups()
+        try:
+            month = self.month_names[month]
+        except KeyError:
+            self.raiseSyntaxError("invalid month name %s" % `month`)
+        return ("%s-%02d-%02d" % (year, month, int(day)), name, desc)
+
+def test():
+    assert internUrgency("high") > internUrgency("medium")
+
+    assert FileBase.re_non_ascii.match('illegal \xf6 character\n')
+
+    note = PackageNoteParsed('chmlib', '0.36-1', 'bug #327431; medium')
+    assert note.bugs == [327431]
+    assert note.package == 'chmlib'
+    assert note.fixed_version == debian_support.Version('0.36-1')
+    assert note.urgency == internUrgency('medium')
+
+    for p in CVEFile('../../data/CAN/list'):
+        pass
+
+if __name__ == "__main__":
+    test()

Added: lib/python/debian_support.py
===================================================================
--- lib/python/debian_support.py	2005-09-12 16:27:44 UTC (rev 1933)
+++ lib/python/debian_support.py	2005-09-12 16:32:23 UTC (rev 1934)
@@ -0,0 +1,187 @@
+# debian_support.py -- Python module for Debian metadata
+# Copyright (C) 2005 Florian Weimer <fw at deneb.enyo.de>
+# 
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""This module implements facilities to deal with Debian-specific metadata."""
+
+import re
+
+class Version:
+    """This class implements Debian version numbers."""
+
+    def __init__(self, version):
+        """Creates a new Version object."""
+        self.__asString = version
+        self.__parsed = self.__parse(version)
+
+    def __str__(self):
+        return self.__asString
+
+    def __repr__(self):
+        return 'Version(%s)' % `self.__asString`
+
+    def __cmp__(self, other):
+        """Compares two versions.
+
+        This method implements the algorithm in the Debian Policy."""
+        return cmp(self.__parsed, other.__parsed)
+
+    def __parse(self, v, regexp=\
+                re.compile(r'^(?:(\d+):)?([A-Za-z0-9.+:-]+?)'
+                           + r'(?:-([A-Za-z0-9.+]+))?$')):
+        match = regexp.match(v)
+        if match is None:
+            raise ValueError, "invalid Debian version string"
+        (epoch, upstream, debian) = match.groups()
+        if epoch is None:
+            epoch = 0
+        else:
+            epoch = int(epoch)
+        return (epoch, self.__parse_1(upstream), self.__parse_1(debian))
+
+    def __parse_1(self, x, non_digits=re.compile(r'^([^0-9]*)(.*)$'),
+                  digits=re.compile(r'^([0-9]*)(.*)$')):
+        l = []
+        while x is not None and x <> '':
+            (nd, x) = non_digits.match(x).groups()
+            (d, x) = digits.match(x).groups()
+            if d == '':
+                d = 0
+            else:
+                d = int(d)
+            l.append(nd)
+            l.append(d)
+        return l
+
+class PackageFile:
+    """A Debian package file.
+
+    Objects of this class can be used to read Debian's Source and
+    Packages files."""
+
+    re_field = re.compile(r'^([A-Za-z][A-Za-z0-9-]+):\s+(.*?)\s*$')
+    re_continuation = re.compile(r'^\s+(?:\.|(\S.*?)\s*)$')
+
+    def __init__(self, name, fileObj=None):
+        """Creates a new package file object.
+
+        name - the name of the file the data comes from
+        fileObj - an alternate data source; the default is to open the
+                  file with the indicated name.
+        """
+        if fileObj is None:
+            fileObj = file(name)
+        self.name = name
+        self.file = fileObj
+        self.lineno = 0
+
+    def __iter__(self):
+        line = self.file.readline()
+        self.lineno += 1
+        pkg = []
+        while line:
+            if line == '\n':
+                if len(pkg) == 0:
+                    self.raiseSyntaxError('expected package record')
+                yield pkg
+                pkg = []
+                line = self.file.readline()
+                self.lineno += 1
+                continue
+            
+            match = self.re_field.match(line)
+            if not match:
+                self.raiseSyntaxError("expected package field")
+            (name, contents) = match.groups()
+
+            while True:
+                line = self.file.readline()
+                self.lineno += 1
+                match = self.re_continuation.match(line)
+                if match:
+                    (ncontents,) = match.groups()
+                    if ncontents is None:
+                        ncontents = ""
+                    contents = "%s\n%s" % (contents, ncontents)
+                else:
+                    break
+            pkg.append((name, contents))
+
+    def raiseSyntaxError(self, msg, lineno=None):
+        e = SyntaxError(msg)
+        e.filename = self.name
+        if lineno is None:
+            e.lineno = self.lineno
+        else:
+            e.lineno = lineno
+        raise e
+
+class PseudoEnum:
+    """A base class for types which resemble enumeration types."""
+    def __init__(self, name, order):
+        self._name = name
+        self._order = order
+    def __repr__(self):
+        return '%s(%s)'% (self.__class__._name__, `name`)
+    def __str__(self):
+        return self._name
+    def __cmp__(self, other):
+        return cmp(self._order, other._order)
+    def __hash__(self):
+        return hash(self._order)
+
+class Release(PseudoEnum): pass
+
+def listReleases():
+    releases = {}
+    rels = ("woody", "sarge", "etch")
+    for r in range(len(rels)):
+        releases[rels[r]] = Release(rels[r], r)
+    Release.releases = releases
+    return releases
+def internRelease(name, releases=listReleases()):
+    if releases.has_key(name):
+        return releases[name]
+    else:
+        return None
+del listReleases
+
+def test():
+    # Version
+    assert Version('0') < Version('a')
+    assert Version('1.0') < Version('1.1')
+    assert Version('1.2') < Version('1.11')
+    assert Version('1.0-0.1') < Version('1.1')
+    assert Version('1.0-0.1') < Version('1.0-1')
+    assert Version('1.0-0.1') == Version('1.0-0.1')
+    assert Version('1.0-0.1') < Version('1.0-1')
+    assert Version('1.0final-5sarge1') > Version('1.0final-5') \
+           > Version('1.0a7-2')
+    assert Version('0.9.2-5') < Version('0.9.2+cvs.1.0.dev.2004.07.28-1.5')
+    assert Version('1:500') < Version('1:5000')
+    assert Version('100:500') > Version('11:5000')
+
+    # Release
+    assert internRelease('sarge') < internRelease('etch')
+
+    # PackageFile
+    # for p in PackageFile('../../data/packages/sarge/Sources'):
+    #     assert p[0][0] == 'Package'
+    # for p in PackageFile('../../data/packages/sarge/Packages.i386'):
+    #     assert p[0][0] == 'Package'
+
+if __name__ == "__main__":
+    test()

Added: lib/python/security_db.py
===================================================================
--- lib/python/security_db.py	2005-09-12 16:27:44 UTC (rev 1933)
+++ lib/python/security_db.py	2005-09-12 16:32:23 UTC (rev 1934)
@@ -0,0 +1,430 @@
+# security_db.py -- simple, CVE-driven Debian security bugs database
+# Copyright (C) 2005 Florian Weimer <fw at deneb.enyo.de>
+# 
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""This module implements a small database for tracking security bugs.
+
+Note that the database is always secondary to the text files.  The
+database is only an implementation tool, and not used for maintaining
+the data.
+
+The data is kept in a SQLite 3 database.
+
+FIXME: Document the database schema once it is finished.
+"""
+
+import apsw
+import bugs
+import debian_support
+import re
+import sys
+import types
+
+class InsertError(Exception):
+    """Class for capturing insert errors.
+
+    The 'errors' member collects all error messages.
+    """
+    
+    def __init__(self, errors):
+        assert len(errors) > 0, errors
+        assert type(errors) == types.ListType, errors
+        assert type(errors[0])== types.StringType, errors
+        self.errors = errors
+        
+    def __str__(self):
+        return self.errors[0] + ' [more...]'
+
+class DB:
+    """Access to the security database.
+
+    This is a wrapper around an SQLite database object (which is
+    accessible as the "db" member.
+
+    Most operations need a special cursor object, which can be created
+    with a cursor object.  The name "cursor" is somewhat of a
+    misnomer because these objects are quite versatile.
+    """
+
+    def __init__(self, name):
+        self.db = apsw.Connection(name)
+
+    def cursor(self):
+        """Creates a new database cursor.
+
+        Also see the writeTxn method."""
+        return self.db.cursor()
+
+    def writeTxn(self):
+        """Creates a cursor for an exclusive transaction.
+
+        No other process may modify the database at the same time.
+        After finishing the work, you should invoke the commit or
+        rollback methods below.
+        """
+        c = self.cursor()
+        c.execute("BEGIN TRANSACTION EXCLUSIVE")
+        return c
+
+    def commit(self, cursor):
+        """Makes the changes in the transaction permanent."""
+        cursor.execute("COMMIT")
+
+    def rollback(self, cursor):
+        """Undos the changes in the transaction."""
+        cursor.execute("ROLLBACK")
+    
+    def initSchema(self):
+        """Creates the database schema."""
+        cursor = self.cursor()
+
+        cursor.execute("""CREATE TABLE source_packages
+        (package TEXT NOT NULL,
+         release TEXT NOT NULL, subrelease TEXT NOT NULL,
+         version TEXT NOT NULL,
+         PRIMARY KEY (package, release, subrelease));""")
+
+        cursor.execute("""CREATE TABLE binary_packages
+        (package TEXT NOT NULL,
+         release TEXT NOT NULL, subrelease TEXT NOT NULL,
+         architecture TEXT NOT NULL,
+         version TEXT NOT NULL,
+         source TEXT NOT NULL, source_version TEXT NOT NULL,
+         PRIMARY KEY (package, release, subrelease, architecture));""")
+        cursor.execute("""CREATE INDEX binary_packages_source
+        ON binary_packages(source)""")
+
+        cursor.execute("""CREATE TABLE package_notes
+        (id INTEGER NOT NULL PRIMARY KEY,
+         bug_name TEXT NOT NULL,
+         package TEXT NOT NULL,
+         fixed_version TEXT
+             CHECK (fixed_version IS NULL OR fixed_version <> ''),
+         release TEXT NOT NULL,
+         urgency TEXT NOT NULL)""")
+
+        cursor.execute("""CREATE TABLE debian_bugs
+        (bug INTEGER NOT NULL,
+         note INTEGER NOT NULL,
+         PRIMARY KEY (bug, note))""")
+
+        cursor.execute("""CREATE TABLE bugs
+        (name TEXT NOT NULL PRIMARY KEY,
+         cve_status TEXT NOT NULL
+             CHECK (cve_status IN
+                    ('', 'CANDIDATE', 'ASSIGNED', 'RESERVED', 'REJECTED')),
+         not_for_us INTEGER NOT NULL CHECK (not_for_us IN (0, 1)),
+         description TEXT NOT NULL,
+         source_file TEXT NOT NULL,
+         source_line INTEGER NOT NULL)""")
+
+        cursor.execute("""CREATE TABLE bugs_notes
+        (bug_name TEXT NOT NULL CHECK (typ <> ''),
+         typ TEXT NOT NULL CHECK (typ IN ('TODO', 'NOTE')),
+         release TEXT NOT NULL DEFAULT '',
+         comment TEXT NOT NULL CHECK (comment <> ''))""")
+
+        cursor.execute("""CREATE TABLE bugs_xref
+        (source TEXT NOT NULL,
+         target TEXT NOT NULL,
+         normalized_target TEXT NOT NULL DEFAULT '',
+         PRIMARY KEY (source, target))""")
+
+    def updateSources(self, cursor, release, subrelease, packages):
+        """Reads a Sources file and adds it to the database.
+
+        Old records for the same release/subrelease pair are removed.
+
+        cursor - cursor used to update the database
+        release - Debian release (e.g. sarge)
+        subrelease - fork of a release (e.g. security)
+        packages - debian_support.PackageFile object with source packages
+        """
+        
+        cursor.execute('DELETE FROM source_packages '
+                       + 'WHERE release = ? AND subrelease = ?',
+                       (release, subrelease))
+
+        for pkg in packages:
+            pkg_name = None
+            pkg_version = None
+            for (name, contents) in pkg:
+                if name == "Package":
+                    pkg_name = contents
+                elif name == "Version":
+                    pkg_version = debian_support.Version(contents)
+            if pkg_name is None:
+                raise SyntaxError\
+                      ("package record does not contain package name")
+            if pkg_version is None:
+                raise SyntaxError\
+                      ("package record for %s does not contain version"
+                       % pkg_name)
+            cursor.execute('INSERT INTO source_packages '
+                           + '(package, release, subrelease, version) '
+                           + 'VALUES (?, ?, ?, ?)',
+                           (pkg_name, release, subrelease, str(pkg_version)))
+
+            
+    def updatePackages(self, cursor,
+                       release, subrelease, architecture,
+                       packages):
+        """Reads a Packages file and adds it to the database.
+
+        Old records for the same release/subrelease/architecture
+        triple are removed.
+
+        cursor - cursor used to update the database
+        release - Debian release (e.g. sarge)
+        subrelease - fork of a release (e.g. security)
+        architecture - architecture of binary packages (e.g. i386)
+        packages - debian_support.PackageFile object with binary packages
+        """
+
+        re_source = re.compile\
+                    (r'^([a-zA-Z0-9.+-]+)(?:\s+\(([a-zA-Z0-9.+:-]+)\))?$')
+
+        cursor.execute('DELETE FROM binary_packages '
+                + 'WHERE release = ? AND subrelease = ? AND architecture = ?',
+                       (release, subrelease, architecture))
+
+
+        for pkg in packages:
+            pkg_name = None
+            pkg_version = None
+            pkg_source = None
+            pkg_source_version = None
+            for (name, contents) in pkg:
+                if name == "Package":
+                    pkg_name = contents
+                elif name == "Version":
+                    pkg_version = debian_support.Version(contents)
+                elif name == "Source":
+                    match = re_source.match(contents)
+                    if match is None:
+                        raise SyntaxError(('binary package %s references '
+                                           + 'invalid source package %s') %
+                                          (pkg_name, `contents`))
+                    (pkg_source, pkg_source_version) = match.groups()
+
+            if pkg_name is None:
+                raise SyntaxError\
+                      ("binary package record does not contain package name")
+            if pkg_version is None:
+                raise SyntaxError\
+                      ("binary record for %s does not contain version"
+                       % pkg_name)
+            if pkg_source is None:
+                pkg_source = pkg_name
+            if pkg_source_version is None:
+                pkg_source_version = pkg_version
+
+            cursor.execute('INSERT INTO binary_packages '
+                           + '(package, release, subrelease, architecture,'
+                           + 'version, source, source_version) '
+                           + 'VALUES (?, ?, ?, ?, ?, ?, ?)',
+                           (pkg_name, release, subrelease, architecture,
+                            str(pkg_version),
+                            pkg_source, str(pkg_source_version)))
+
+    def deleteBugs(self, cursor):
+        """Deletes all record bug reports from the database."""
+        cursor.execute("DELETE FROM package_notes")
+        cursor.execute("DELETE FROM debian_bugs")
+        cursor.execute("DELETE FROM bugs")
+        cursor.execute("DELETE FROM bugs_notes")
+        cursor.execute("DELETE FROM bugs_xref")
+
+    def insertBugs(self, cursor, source):
+        """Reads the CAN/CVE/DSA/DTSA file and writes them to the database."""
+
+        errors = []
+        for bug in source:
+            try:
+                bug.writeDB(cursor)
+            except ValueError, e:
+                errors.append("%s: %d: error: %s"
+                              % (bug.source_file, bug.source_line, e))
+        if errors:
+            raise InsertError(errors)
+
+    def finishBugs(self, cursor):
+        """After inserting new bugs, update cross-references.
+
+        Returns a list of warning messages."""
+
+        warnings = []
+
+        for b1, b2 in list(cursor.execute\
+            ("""SELECT b1.name, b2.name FROM bugs AS b1, bugs AS b2
+             WHERE b1.name LIKE 'CVE-%'
+             AND b2.name = 'CAN-' || substr(b1.name, 5, 9)""")):
+            b1 = bugs.BugFromDB(cursor, b1)
+            b2 = bugs.BugFromDB(cursor, b2)
+
+            warnings.append("%s:%d: duplicate CVE entries %s and %s"
+                           % (b1.source_file, b1.source_line,
+                              b1.name, b2.name))
+            warnings.append("%s:%d: location of %s"
+                            % (b1.source_file, b1.source_line, b1.name))
+            warnings.append("%s:%d: location of %s"
+                            % (b2.source_file, b2.source_line, b2.name))
+
+
+        for source, target in list(cursor.execute\
+            ("""SELECT source, target FROM bugs_xref
+            WHERE normalized_target = ''""")):
+            if bugs.BugBase.re_cve_name.match(target):
+                can_target = 'CAN-' + target[4:]
+                cve_target = 'CVE-' + target[4:]
+
+                found = False
+                for (t,) in list(cursor.execute("""SELECT name FROM bugs
+                WHERE name IN (?, ?)""", (can_target, cve_target))):
+                    assert not found, t
+                    cursor.execute("""UPDATE bugs_xref
+                    SET normalized_target = ?
+                    WHERE source = ? AND target = ?""",
+                                   (t, source, target))
+                    found = True
+                if not found:
+                    b = bugsFromDB(c, source)
+                    warnings.append\
+                        ("%s: %d: reference to unknwown CVE entry %s"
+                         % (b.source_file, b.source_line, target))
+                 
+        return warnings
+                
+
+    def check(self, cursor=None):
+        """Runs a simple consistency check and prints the results."""
+
+        if cursor is None:
+            cursor = self.cursor()
+
+        for (package, release, subrelease, architecture, source) in\
+            cursor.execute(
+            """SELECT package, release, subrelease, architecture, source
+            FROM binary_packages
+            WHERE NOT EXISTS
+            (SELECT *
+                    FROM source_packages AS sp
+                    WHERE sp.package = binary_packages.source
+                    AND sp.release = binary_packages.release
+                    AND sp.subrelease = binary_packages.subrelease)
+            """):
+            print "error: binary package without source package"
+            print "  binary package:", package
+            print "  release:", release
+            if subrelease:
+                print "  subrelease:", subrelease
+            print "  architecture:", architecture
+            print "  missing source package:", source
+
+        for (package, release, subrelease, architecture, version,
+             source, source_version) \
+            in cursor.execute("""SELECT binary_packages.package,
+            binary_packages.release, binary_packages.subrelease,
+            binary_packages.architecture,binary_packages.version,
+            sp.package, sp.version
+            FROM binary_packages, source_packages AS sp
+            WHERE sp.package = binary_packages.source
+            AND sp.release = binary_packages.release
+            AND sp.subrelease = binary_packages.subrelease
+            AND sp.version <> binary_packages.source_version"""):
+            relation = cmp(debian_support.Version(version),
+                           debian_support.Version(source_version))
+            assert relation <> 0
+            if relation <= 0:
+                print "error: binary package is older than source package"
+            else:
+                print "warning: binary package is newer than source package"
+            print "  binary package: %s (%s)" % (package, version)
+            print "  source package: %s (%s)" % (source, source_version)
+            print "  release:", release
+            if subrelease:
+                print "  subrelease:", subrelease
+            print "  architecture:", architecture
+            
+def test():
+    import os
+
+    os.unlink('test_security.db')
+    db = DB('test_security.db')
+    db.initSchema()
+
+    data_prefix = '../../data/packages/sarge/'
+    if False:
+        cursor = db.writeTxn()
+        db.updateSources(cursor, 'sarge', '',
+                debian_support.PackageFile(data_prefix + 'Sources'))
+        db.updateSources(cursor, 'sarge', 'security',
+                debian_support.PackageFile(data_prefix + 'Sources.security'))
+        db.updatePackages(cursor, 'sarge', '', 'i386',
+                debian_support.PackageFile(data_prefix + 'Packages.i386'))
+        db.updatePackages(cursor, 'sarge', 'security', 'i386',
+                debian_support.PackageFile(data_prefix
+                                           + 'Packages.security-i386'))
+        db.commit(cursor)
+
+    # db.check(cursor)
+
+    cursor = db.writeTxn()
+    db.deleteBugs(cursor)
+    db.insertBugs(cursor, bugs.CVEFile('../../data/CAN/list'))
+    db.insertBugs(cursor, bugs.CVEFile('../../data/CVE/list',
+                                              no_version_needs_note=False))
+    db.insertBugs(cursor, bugs.DSAFile('../../data/DSA/list'))
+    db.insertBugs(cursor, bugs.DTSAFile('../../data/DTSA/list'))
+    db.finishBugs(cursor)
+    db.commit(cursor)
+
+    b = bugs.BugFromDB(cursor, 'CAN-2005-2491')
+    assert b.name == 'CAN-2005-2491', b.name
+    assert b.description == '(Integer overflow in pcre_compile.c in Perl Compatible Regular ...)', b.description
+    assert len(b.xref) == 2, b.xref
+    assert not b.not_for_us
+    assert 'DSA-800-1' in b.xref, b.xref
+    assert 'DTSA-10-1' in b.xref, b.xref
+    assert tuple(b.comments) == (('NOTE', 'gnumeric/goffice includes one as well; according to upstream not exploitable in gnumeric,'),
+                                 ('NOTE', 'new copy will be included any way')),\
+                                 b.comments
+
+    assert len(b.notes) == 4, len(b.notes)
+    
+    for n in b.notes:
+        assert n.release is None
+        if n.package == 'pcre3':
+            assert n.fixed_version == debian_support.Version('6.3-0.1etch1')
+            assert tuple(n.bugs) == (324531,), n.bugs
+            assert n.urgency == bugs.internUrgency('medium')
+        elif n.package == 'python2.1':
+            assert n.fixed_version == debian_support.Version('2.1.3dfsg-3')
+            assert len(n.bugs) == 0, n.bugs
+            assert n.urgency == bugs.internUrgency('medium')
+        elif n.package == 'python2.2':
+            assert n.fixed_version == debian_support.Version('2.2.3dfsg-4')
+            assert len(n.bugs) == 0, n.bugs
+            assert n.urgency == bugs.internUrgency('medium')
+        elif n.package == 'python2.3':
+            assert n.fixed_version == debian_support.Version('2.3.5-8')
+            assert len(n.bugs) == 0, n.bugs
+            assert n.urgency == bugs.internUrgency('medium')
+        else:
+            assert False
+
+if __name__ == "__main__":
+    test()


Property changes on: stamps
___________________________________________________________________
Name: svn:ignore
   + *





More information about the Secure-testing-commits mailing list