| 1 |
# security_db.py -- simple, CVE-driven Debian security bugs database
|
| 2 |
# Copyright (C) 2005 Florian Weimer <fw@deneb.enyo.de>
|
| 3 |
#
|
| 4 |
# This program is free software; you can redistribute it and/or modify
|
| 5 |
# it under the terms of the GNU General Public License as published by
|
| 6 |
# the Free Software Foundation; either version 2 of the License, or
|
| 7 |
# (at your option) any later version.
|
| 8 |
#
|
| 9 |
# This program is distributed in the hope that it will be useful,
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 12 |
# GNU General Public License for more details.
|
| 13 |
#
|
| 14 |
# You should have received a copy of the GNU General Public License
|
| 15 |
# along with this program; if not, write to the Free Software
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
| 17 |
|
| 18 |
"""This module implements a small database for tracking security bugs.
|
| 19 |
|
| 20 |
Note that the database is always secondary to the text files. The
|
| 21 |
database is only an implementation tool, and not used for maintaining
|
| 22 |
the data.
|
| 23 |
|
| 24 |
The data is kept in a SQLite 3 database.
|
| 25 |
|
| 26 |
FIXME: Document the database schema once it is finished.
|
| 27 |
"""
|
| 28 |
|
| 29 |
import apsw
|
| 30 |
import bugs
|
| 31 |
import cPickle
|
| 32 |
import cStringIO
|
| 33 |
import debian_support
|
| 34 |
import glob
|
| 35 |
import os
|
| 36 |
import os.path
|
| 37 |
import re
|
| 38 |
import sys
|
| 39 |
import types
|
| 40 |
|
| 41 |
class InsertError(Exception):
|
| 42 |
"""Class for capturing insert errors.
|
| 43 |
|
| 44 |
The 'errors' member collects all error messages.
|
| 45 |
"""
|
| 46 |
|
| 47 |
def __init__(self, errors):
|
| 48 |
assert len(errors) > 0, errors
|
| 49 |
assert type(errors) == types.ListType, errors
|
| 50 |
assert type(errors[0])== types.StringType, errors
|
| 51 |
self.errors = errors
|
| 52 |
|
| 53 |
def __str__(self):
|
| 54 |
return self.errors[0] + ' [more...]'
|
| 55 |
|
| 56 |
def mergeLists(a, b):
|
| 57 |
"""Merges two lists."""
|
| 58 |
if type(a) == types.StringType:
|
| 59 |
if a == "":
|
| 60 |
a = []
|
| 61 |
else:
|
| 62 |
a = a.split(',')
|
| 63 |
if type(b) == types.StringType:
|
| 64 |
if b == "":
|
| 65 |
b = []
|
| 66 |
else:
|
| 67 |
b = b.split(',')
|
| 68 |
result = {}
|
| 69 |
for x in a:
|
| 70 |
result[x] = 1
|
| 71 |
for x in b:
|
| 72 |
result[x] = 1
|
| 73 |
result = result.keys()
|
| 74 |
result.sort()
|
| 75 |
return result
|
| 76 |
|
| 77 |
class NVDEntry:
|
| 78 |
"""A class for an entry in the nvd_data table.
|
| 79 |
Objects have the same fileds as the table."""
|
| 80 |
def __init__(self, row, description):
|
| 81 |
for x in range(len(row)):
|
| 82 |
setattr(self, description[x][0], row[x])
|
| 83 |
def rangeString(self):
|
| 84 |
result = []
|
| 85 |
if self.range_local:
|
| 86 |
result.append("local")
|
| 87 |
if self.range_remote:
|
| 88 |
result.append("remote")
|
| 89 |
if self.range_user_init:
|
| 90 |
result.append("user-initiated")
|
| 91 |
return ", ".join(result)
|
| 92 |
|
| 93 |
class SchemaMismatch(Exception):
|
| 94 |
"""Raised to indicate a schema mismatch.
|
| 95 |
|
| 96 |
The caller is expected to remove and regenerate the database."""
|
| 97 |
|
| 98 |
class DB:
|
| 99 |
"""Access to the security database.
|
| 100 |
|
| 101 |
This is a wrapper around an SQLite database object (which is
|
| 102 |
accessible as the "db" member.
|
| 103 |
|
| 104 |
Most operations need a special cursor object, which can be created
|
| 105 |
with a cursor object. The name "cursor" is somewhat of a
|
| 106 |
misnomer because these objects are quite versatile.
|
| 107 |
"""
|
| 108 |
|
| 109 |
def __init__(self, name, verbose=False):
|
| 110 |
self.name = name
|
| 111 |
self.db = apsw.Connection(name)
|
| 112 |
self.verbose = verbose
|
| 113 |
|
| 114 |
self.schema_version = 15
|
| 115 |
self._initFunctions()
|
| 116 |
|
| 117 |
c = self.cursor()
|
| 118 |
for (v,) in c.execute("PRAGMA user_version"):
|
| 119 |
if v == 0:
|
| 120 |
self.initSchema()
|
| 121 |
elif v <> self.schema_version:
|
| 122 |
if self.verbose:
|
| 123 |
print "DB: schema version mismatch: expected %d, got %d" \
|
| 124 |
% (self.schema_version, v)
|
| 125 |
raise SchemaMismatch, `v`
|
| 126 |
# Database has been created at this point. Small race
|
| 127 |
# condition here (the already opened database might refer
|
| 128 |
# to an older file).
|
| 129 |
self.__stat = os.stat(self.name)
|
| 130 |
return
|
| 131 |
assert False
|
| 132 |
|
| 133 |
def refresh(self):
|
| 134 |
"""Checks if the database file is still the same and reopens
|
| 135 |
it if necessary."""
|
| 136 |
current = os.stat(self.name)
|
| 137 |
if os.path.samestat(self.__stat, current):
|
| 138 |
return
|
| 139 |
self.__stat = current
|
| 140 |
self.db = apsw.Connection(self.name)
|
| 141 |
self._initFunctions()
|
| 142 |
|
| 143 |
def cursor(self):
|
| 144 |
"""Creates a new database cursor.
|
| 145 |
|
| 146 |
Also see the writeTxn method."""
|
| 147 |
return self.db.cursor()
|
| 148 |
|
| 149 |
def writeTxn(self):
|
| 150 |
"""Creates a cursor for an exclusive transaction.
|
| 151 |
|
| 152 |
No other process may modify the database at the same time.
|
| 153 |
After finishing the work, you should invoke the commit or
|
| 154 |
rollback methods below.
|
| 155 |
"""
|
| 156 |
c = self.cursor()
|
| 157 |
c.execute("BEGIN TRANSACTION EXCLUSIVE")
|
| 158 |
return c
|
| 159 |
|
| 160 |
def commit(self, cursor):
|
| 161 |
"""Makes the changes in the transaction permanent."""
|
| 162 |
cursor.execute("COMMIT")
|
| 163 |
|
| 164 |
def rollback(self, cursor):
|
| 165 |
"""Undos the changes in the transaction."""
|
| 166 |
cursor.execute("ROLLBACK")
|
| 167 |
|
| 168 |
def initSchema(self):
|
| 169 |
"""Creates the database schema."""
|
| 170 |
cursor = self.cursor()
|
| 171 |
|
| 172 |
# This gives us better performance (it's usually the file
|
| 173 |
# system block size). This must come first to be ffective.
|
| 174 |
|
| 175 |
cursor.execute("PRAGMA page_size = 4096")
|
| 176 |
|
| 177 |
# Set the schema version to an invalid value which is
|
| 178 |
# different from zero. We can use this to detect a partially
|
| 179 |
# created schema.
|
| 180 |
|
| 181 |
cursor.execute("PRAGMA user_version = 1")
|
| 182 |
|
| 183 |
cursor.execute("""CREATE TABLE inodeprints
|
| 184 |
(file TEXT NOT NULL PRIMARY KEY,
|
| 185 |
inodeprint TEXT NOT NULL,
|
| 186 |
parsed BLOB)""")
|
| 187 |
|
| 188 |
cursor.execute("""CREATE TABLE version_linear_order
|
| 189 |
(id INTEGER NOT NULL PRIMARY KEY,
|
| 190 |
version TEXT NOT NULL UNIQUE)""")
|
| 191 |
|
| 192 |
cursor.execute(
|
| 193 |
"""CREATE TABLE source_packages
|
| 194 |
(name TEXT NOT NULL,
|
| 195 |
release TEXT NOT NULL,
|
| 196 |
subrelease TEXT NOT NULL,
|
| 197 |
archive TEXT NOT NULL,
|
| 198 |
version TEXT NOT NULL,
|
| 199 |
version_id INTEGER NOT NULL DEFAULT 0,
|
| 200 |
PRIMARY KEY (name, release, subrelease, archive))""")
|
| 201 |
|
| 202 |
cursor.execute(
|
| 203 |
"""CREATE TABLE binary_packages
|
| 204 |
(name TEXT NOT NULL,
|
| 205 |
release TEXT NOT NULL,
|
| 206 |
subrelease TEXT NOT NULL,
|
| 207 |
archive TEXT NOT NULL,
|
| 208 |
version TEXT NOT NULL,
|
| 209 |
source TEXT NOT NULL,
|
| 210 |
source_version TEXT NOT NULL,
|
| 211 |
archs TEXT NOT NULL,
|
| 212 |
version_id INTEGER NOT NULL DEFAULT 0,
|
| 213 |
source_version_id INTEGER NOT NULL DEFAULT 0,
|
| 214 |
PRIMARY KEY (name, release, subrelease, archive, version, source,
|
| 215 |
source_version))""")
|
| 216 |
cursor.execute(
|
| 217 |
"""CREATE INDEX binary_packages_source
|
| 218 |
ON binary_packages(source)""")
|
| 219 |
|
| 220 |
cursor.execute("""CREATE TABLE package_notes
|
| 221 |
(id INTEGER NOT NULL PRIMARY KEY,
|
| 222 |
bug_name TEXT NOT NULL,
|
| 223 |
package TEXT NOT NULL,
|
| 224 |
fixed_version TEXT
|
| 225 |
CHECK (fixed_version IS NULL OR fixed_version <> ''),
|
| 226 |
fixed_version_id INTEGER NOT NULL DEFAULT 0,
|
| 227 |
release TEXT NOT NULL,
|
| 228 |
package_kind TEXT NOT NULL DEFAULT 'unknown',
|
| 229 |
urgency TEXT NOT NULL,
|
| 230 |
bug_origin TEXT NOT NULL DEFAULT '')""")
|
| 231 |
cursor.execute(
|
| 232 |
"""CREATE UNIQUE INDEX package_notes_bug
|
| 233 |
ON package_notes(bug_name, package, release)""")
|
| 234 |
|
| 235 |
cursor.execute("""CREATE TABLE debian_bugs
|
| 236 |
(bug INTEGER NOT NULL,
|
| 237 |
note INTEGER NOT NULL,
|
| 238 |
PRIMARY KEY (bug, note))""")
|
| 239 |
|
| 240 |
cursor.execute("""CREATE TABLE bugs
|
| 241 |
(name TEXT NOT NULL PRIMARY KEY,
|
| 242 |
cve_status TEXT NOT NULL
|
| 243 |
CHECK (cve_status IN
|
| 244 |
('', 'CANDIDATE', 'ASSIGNED', 'RESERVED', 'REJECTED')),
|
| 245 |
not_for_us INTEGER NOT NULL CHECK (not_for_us IN (0, 1)),
|
| 246 |
description TEXT NOT NULL,
|
| 247 |
release_date TEXT NOT NULL,
|
| 248 |
source_file TEXT NOT NULL,
|
| 249 |
source_line INTEGER NOT NULL)""")
|
| 250 |
|
| 251 |
cursor.execute("""CREATE TABLE bugs_notes
|
| 252 |
(bug_name TEXT NOT NULL CHECK (typ <> ''),
|
| 253 |
typ TEXT NOT NULL CHECK (typ IN ('TODO', 'NOTE')),
|
| 254 |
release TEXT NOT NULL DEFAULT '',
|
| 255 |
comment TEXT NOT NULL CHECK (comment <> ''))""")
|
| 256 |
|
| 257 |
cursor.execute("""CREATE TABLE bugs_xref
|
| 258 |
(source TEXT NOT NULL,
|
| 259 |
target TEXT NOT NULL,
|
| 260 |
normalized_target TEXT NOT NULL DEFAULT '',
|
| 261 |
copy_notes INTEGER NOT NULL DEFAULT 0,
|
| 262 |
PRIMARY KEY (source, target))""")
|
| 263 |
cursor.execute(
|
| 264 |
"""CREATE INDEX bugs_xref_normalized_target
|
| 265 |
ON bugs_xref(normalized_target)""")
|
| 266 |
|
| 267 |
cursor.execute("""CREATE TABLE bug_status
|
| 268 |
(bug_name TEXT NOT NULL,
|
| 269 |
release TEXT NOT NULL,
|
| 270 |
status TEXT NOT NULL
|
| 271 |
CHECK (status IN ('vulnerable', 'fixed', 'unknown',
|
| 272 |
'partially-fixed', 'todo')),
|
| 273 |
reason TEXT NOT NULL,
|
| 274 |
PRIMARY KEY (bug_name, release))""")
|
| 275 |
|
| 276 |
cursor.execute("""CREATE TABLE source_package_status
|
| 277 |
(bug_name TEXT NOT NULL,
|
| 278 |
package INTEGER NOT NULL,
|
| 279 |
vulnerable INTEGER NOT NULL,
|
| 280 |
urgency TEXT NOT NULL,
|
| 281 |
PRIMARY KEY (bug_name, package))""")
|
| 282 |
cursor.execute(
|
| 283 |
"""CREATE INDEX source_package_status_package
|
| 284 |
ON source_package_status(package)""")
|
| 285 |
|
| 286 |
cursor.execute("""CREATE TABLE binary_package_status
|
| 287 |
(bug_name TEXT NOT NULL,
|
| 288 |
package INTEGER NOT NULL,
|
| 289 |
vulnerable INTEGER NOT NULL,
|
| 290 |
urgency TEXT NOT NULL,
|
| 291 |
PRIMARY KEY (bug_name, package))""")
|
| 292 |
cursor.execute(
|
| 293 |
"""CREATE INDEX binary_package_status_package
|
| 294 |
ON binary_package_status(package)""")
|
| 295 |
|
| 296 |
cursor.execute(
|
| 297 |
"CREATE TABLE removed_packages (name TEXT NOT NULL PRIMARY KEY)")
|
| 298 |
|
| 299 |
cursor.execute(
|
| 300 |
"""CREATE TABLE nvd_data
|
| 301 |
(cve_name TEXT NOT NULL PRIMARY KEY,
|
| 302 |
discovered TEXT NOT NULL,
|
| 303 |
published TEXT NOT NULL,
|
| 304 |
severity TEXT NOT NULL,
|
| 305 |
range_local INTEGER,
|
| 306 |
range_remote INTEGER,
|
| 307 |
range_user_init INTEGER,
|
| 308 |
loss_avail INTEGER NOT NULL,
|
| 309 |
loss_conf INTEGER NOT NULL,
|
| 310 |
loss_int INTEGER NOT NULL,
|
| 311 |
loss_sec_prot_user INTEGER NOT NULL,
|
| 312 |
loss_sec_prot_admin INTEGER NOT NULL,
|
| 313 |
loss_sec_prot_other INTEGER NOT NULL)""")
|
| 314 |
|
| 315 |
cursor.execute(
|
| 316 |
"""CREATE VIEW testing_status AS
|
| 317 |
SELECT DISTINCT sp.name AS package, st.bug_name AS bug,
|
| 318 |
sp.archive AS section, st.urgency AS urgency,
|
| 319 |
(SELECT vulnerable
|
| 320 |
FROM source_packages AS sidp, source_package_status AS sidst
|
| 321 |
WHERE sidp.name = sp.name
|
| 322 |
AND sidp.release = 'sid' AND sidp.subrelease = ''
|
| 323 |
AND sidp.archive = sp.archive
|
| 324 |
AND sidst.bug_name = st.bug_name
|
| 325 |
AND sidst.package = sidp.rowid) AS unstable_vulnerable,
|
| 326 |
COALESCE((SELECT NOT vulnerable
|
| 327 |
FROM source_packages AS tsecp, source_package_status AS tsecst
|
| 328 |
WHERE tsecp.name = sp.name
|
| 329 |
AND tsecp.release = 'etch' AND tsecp.subrelease = 'security'
|
| 330 |
AND tsecp.archive = sp.archive
|
| 331 |
AND tsecst.bug_name = st.bug_name
|
| 332 |
AND tsecst.package = tsecp.rowid), 0) AS testing_security_fixed,
|
| 333 |
(SELECT range_remote FROM nvd_data
|
| 334 |
WHERE cve_name = st.bug_name) AS remote
|
| 335 |
FROM source_package_status AS st, source_packages AS sp
|
| 336 |
WHERE st.vulnerable AND st.urgency <> 'unimportant'
|
| 337 |
AND sp.rowid = st.package AND sp.release = 'etch'
|
| 338 |
AND sp.subrelease = ''
|
| 339 |
ORDER BY sp.name, urgency_to_number(st.urgency), st.bug_name""")
|
| 340 |
|
| 341 |
cursor.execute(
|
| 342 |
"""CREATE VIEW stable_status AS
|
| 343 |
SELECT DISTINCT sp.name AS package, st.bug_name AS bug,
|
| 344 |
sp.archive AS section, st.urgency AS urgency,
|
| 345 |
(SELECT range_remote FROM nvd_data
|
| 346 |
WHERE cve_name = st.bug_name) AS remote
|
| 347 |
FROM source_package_status AS st, source_packages AS sp
|
| 348 |
WHERE st.vulnerable AND st.urgency <> 'unimportant'
|
| 349 |
AND sp.rowid = st.package AND sp.release = 'sarge'
|
| 350 |
AND sp.subrelease = ''
|
| 351 |
AND NOT COALESCE((SELECT NOT vulnerable
|
| 352 |
FROM source_packages AS secp, source_package_status AS secst
|
| 353 |
WHERE secp.name = sp.name
|
| 354 |
AND secp.release = 'sarge' AND secp.subrelease = 'security'
|
| 355 |
AND secp.archive = sp.archive
|
| 356 |
AND secst.bug_name = st.bug_name
|
| 357 |
AND secst.package = secp.rowid), 0)
|
| 358 |
ORDER BY sp.name, urgency_to_number(urgency), st.bug_name""")
|
| 359 |
|
| 360 |
cursor.execute("PRAGMA user_version = %d" % self.schema_version)
|
| 361 |
|
| 362 |
def _initFunctions(self):
|
| 363 |
"""Registers user-defined SQLite functions."""
|
| 364 |
|
| 365 |
def string_list_add(lst, *args):
|
| 366 |
for arg in args:
|
| 367 |
lst.append(arg)
|
| 368 |
def string_list_to_string(lst):
|
| 369 |
return ', '.join(lst)
|
| 370 |
def string_list_factory():
|
| 371 |
return ([], string_list_add, string_list_to_string)
|
| 372 |
self.db.createaggregatefunction("string_list", string_list_factory)
|
| 373 |
|
| 374 |
def string_set_add(lst, *args):
|
| 375 |
for arg in args:
|
| 376 |
for arch in arg.split(','):
|
| 377 |
lst[arch] = True
|
| 378 |
def string_set_to_archs(lst):
|
| 379 |
l = lst.keys()
|
| 380 |
l.sort()
|
| 381 |
return ','.join(l)
|
| 382 |
def string_set_factory():
|
| 383 |
return ({}, string_set_add, string_set_to_archs)
|
| 384 |
self.db.createaggregatefunction("string_set", string_set_factory)
|
| 385 |
|
| 386 |
urgencies = ['high', 'medium', 'low', 'unimportant']
|
| 387 |
def urgency_to_number(u):
|
| 388 |
try:
|
| 389 |
return urgencies.index(u)
|
| 390 |
except ValueError:
|
| 391 |
return 999
|
| 392 |
self.db.createscalarfunction("urgency_to_number", urgency_to_number, 1)
|
| 393 |
|
| 394 |
releases = ['potato', 'woody', 'sarge', 'etch', 'sid']
|
| 395 |
def release_to_number(u):
|
| 396 |
try:
|
| 397 |
return releases.index(u)
|
| 398 |
except ValueError:
|
| 399 |
return -1
|
| 400 |
self.db.createscalarfunction("release_to_number", release_to_number, 1)
|
| 401 |
|
| 402 |
def release_name(release, subrelease, archive):
|
| 403 |
if archive <> 'main':
|
| 404 |
release = release + '/' + archive
|
| 405 |
if subrelease:
|
| 406 |
return "%s (%s)" % (release, subrelease)
|
| 407 |
else:
|
| 408 |
return release
|
| 409 |
self.db.createscalarfunction("release_name", release_name, 3)
|
| 410 |
|
| 411 |
# This is slower than the version_linear_order table, but
|
| 412 |
# often more convenient to use in queries.
|
| 413 |
def collate_version(a, b):
|
| 414 |
return debian_support.Version(a).__cmp__(debian_support.Version(b))
|
| 415 |
self.db.createcollation("version", collate_version)
|
| 416 |
|
| 417 |
def filePrint(self, filename):
|
| 418 |
"""Returns a fingerprint string for filename."""
|
| 419 |
|
| 420 |
st = os.stat(filename)
|
| 421 |
# The "1" is a version number which can be used to trigger a
|
| 422 |
# re-read if the code has changed in an incompatible way.
|
| 423 |
return `(st.st_size, st.st_ino, st.st_mtime, 1)`
|
| 424 |
|
| 425 |
def _parseFile(self, cursor, filename):
|
| 426 |
current_print = self.filePrint(filename)
|
| 427 |
|
| 428 |
def do_parse(packages):
|
| 429 |
if self.verbose:
|
| 430 |
print " reading " + `filename`
|
| 431 |
|
| 432 |
re_source = re.compile\
|
| 433 |
(r'^([a-zA-Z0-9.+-]+)(?:\s+\(([a-zA-Z0-9.+:-]+)\))?$')
|
| 434 |
|
| 435 |
data = []
|
| 436 |
for pkg in packages:
|
| 437 |
pkg_name = None
|
| 438 |
pkg_version = None
|
| 439 |
pkg_arch = None
|
| 440 |
pkg_source = None
|
| 441 |
pkg_source_version = None
|
| 442 |
for (name, contents) in pkg:
|
| 443 |
if name == "Package":
|
| 444 |
pkg_name = contents
|
| 445 |
elif name == "Version":
|
| 446 |
pkg_version = contents
|
| 447 |
elif name == "Source":
|
| 448 |
match = re_source.match(contents)
|
| 449 |
if match is None:
|
| 450 |
raise SyntaxError(('package %s references '
|
| 451 |
+ 'invalid source package %s') %
|
| 452 |
(pkg_name, `contents`))
|
| 453 |
(pkg_source, pkg_source_version) = match.groups()
|
| 454 |
elif name == "Architecture":
|
| 455 |
pkg_arch = contents
|
| 456 |
if pkg_name is None:
|
| 457 |
raise SyntaxError\
|
| 458 |
("package record does not contain package name")
|
| 459 |
if pkg_version is None:
|
| 460 |
raise SyntaxError\
|
| 461 |
("package record for %s does not contain version"
|
| 462 |
% pkg_name)
|
| 463 |
if pkg_arch is None:
|
| 464 |
raise SyntaxError\
|
| 465 |
("package record for %s lacks Architecture: field"
|
| 466 |
% pkg_name)
|
| 467 |
data.append((pkg_name, pkg_version, pkg_arch,
|
| 468 |
pkg_source, pkg_source_version))
|
| 469 |
|
| 470 |
return data
|
| 471 |
|
| 472 |
def toString(data):
|
| 473 |
result = cStringIO.StringIO()
|
| 474 |
cPickle.dump(data, result)
|
| 475 |
return result.getvalue()
|
| 476 |
|
| 477 |
for (old_print, contents) in cursor.execute(
|
| 478 |
"SELECT inodeprint, parsed FROM inodeprints WHERE file = ?",
|
| 479 |
(filename,)):
|
| 480 |
if old_print == current_print:
|
| 481 |
return (True, cPickle.load(cStringIO.StringIO(contents)))
|
| 482 |
result = do_parse(debian_support.PackageFile(filename))
|
| 483 |
cursor.execute("""UPDATE inodeprints SET inodeprint = ?, parsed = ?
|
| 484 |
WHERE file = ?""", (current_print, toString(result), filename))
|
| 485 |
return (False, result)
|
| 486 |
|
| 487 |
# No inodeprints entry, load file and add one.
|
| 488 |
result = do_parse(debian_support.PackageFile(filename))
|
| 489 |
cursor.execute("""INSERT INTO inodeprints (file, inodeprint, parsed)
|
| 490 |
VALUES (?, ?, ?)""", (filename, current_print, toString(result)))
|
| 491 |
return (False, result)
|
| 492 |
|
| 493 |
def readPackages(self, cursor, directory):
|
| 494 |
"""Reads a directory of package files."""
|
| 495 |
|
| 496 |
if self.verbose:
|
| 497 |
print "readPackages:"
|
| 498 |
|
| 499 |
self._readSourcePackages(cursor, directory)
|
| 500 |
self._readBinaryPackages(cursor, directory)
|
| 501 |
|
| 502 |
if self.verbose:
|
| 503 |
print " finished"
|
| 504 |
|
| 505 |
def _readSourcePackages(self, cursor, directory):
|
| 506 |
"""Reads from directory with source package files."""
|
| 507 |
|
| 508 |
re_sources = re.compile(r'.*/([a-z-]+)_([a-z-]*)_([a-z-]+)_Sources$')
|
| 509 |
|
| 510 |
|
| 511 |
if self.verbose:
|
| 512 |
print " reading source packages"
|
| 513 |
|
| 514 |
for filename in glob.glob(directory + '/*_Sources'):
|
| 515 |
match = re_sources.match(filename)
|
| 516 |
if match is None:
|
| 517 |
raise ValueError, "invalid file name: " + `filename`
|
| 518 |
|
| 519 |
(release, subrelease, archive) = match.groups()
|
| 520 |
(unchanged, parsed) = self._parseFile(cursor, filename)
|
| 521 |
if unchanged:
|
| 522 |
continue
|
| 523 |
|
| 524 |
cursor.execute(
|
| 525 |
"""DELETE FROM source_packages
|
| 526 |
WHERE release = ? AND subrelease = ? AND archive = ?""",
|
| 527 |
(release, subrelease, archive))
|
| 528 |
self._clearVersions(cursor)
|
| 529 |
|
| 530 |
def gen():
|
| 531 |
for (name, version, archs, source, source_version) in parsed:
|
| 532 |
assert source is None
|
| 533 |
assert source_version is None
|
| 534 |
yield name, release, subrelease, archive, version
|
| 535 |
cursor.executemany(
|
| 536 |
"""INSERT INTO source_packages
|
| 537 |
(name, release, subrelease, archive, version)
|
| 538 |
VALUES (?, ?, ?, ?, ?)""",
|
| 539 |
gen())
|
| 540 |
|
| 541 |
def _readBinaryPackages(self, cursor, directory):
|
| 542 |
"""Reads from a directory with binary package files."""
|
| 543 |
|
| 544 |
re_packages \
|
| 545 |
= re.compile(
|
| 546 |
r'.*/([a-z-]+)_([a-z-]*)_([a-z-]+)_([a-z0-9]+)_Packages$')
|
| 547 |
|
| 548 |
if self.verbose:
|
| 549 |
print " reading binary packages"
|
| 550 |
|
| 551 |
# First check for any changes.
|
| 552 |
|
| 553 |
filenames = glob.glob(directory + '/*_Packages')
|
| 554 |
filenames.sort()
|
| 555 |
changed = False
|
| 556 |
for filename in filenames:
|
| 557 |
changed = True
|
| 558 |
for (old_print,) in cursor.execute(
|
| 559 |
"SELECT inodeprint FROM inodeprints WHERE file = ?",
|
| 560 |
(filename,)):
|
| 561 |
if self.filePrint(filename) == old_print:
|
| 562 |
changed = False
|
| 563 |
if changed:
|
| 564 |
break
|
| 565 |
if not changed:
|
| 566 |
if self.verbose:
|
| 567 |
print " finished (no changes)"
|
| 568 |
return
|
| 569 |
|
| 570 |
# Real import. We have to re-read all Packages files even if
|
| 571 |
# only some of them have changed because the database only
|
| 572 |
# stores aggregated data, and there is no efficient way to
|
| 573 |
# handle updates of the records related to a single file.
|
| 574 |
|
| 575 |
packages = {}
|
| 576 |
unchanged = True
|
| 577 |
for filename in filenames:
|
| 578 |
match = re_packages.match(filename)
|
| 579 |
if match is None:
|
| 580 |
raise ValueError, "invalid file name: " + `filename`
|
| 581 |
|
| 582 |
(release, subrelease, archive, architecture) = match.groups()
|
| 583 |
(unch, parsed) = self._parseFile(cursor, filename)
|
| 584 |
unchanged = unchanged and unch
|
| 585 |
for (name, version, arch, source, source_version) in parsed:
|
| 586 |
if source is None:
|
| 587 |
source = name
|
| 588 |
if source_version is None:
|
| 589 |
source_version = version
|
| 590 |
if arch <> 'all' and arch <> architecture:
|
| 591 |
raise ValueError, ("invalid architecture %s for package %s"
|
| 592 |
% (arch, name))
|
| 593 |
key = (name, release, subrelease, archive, version,
|
| 594 |
source, source_version)
|
| 595 |
if packages.has_key(key):
|
| 596 |
packages[key][arch] = 1
|
| 597 |
else:
|
| 598 |
packages[key] = {arch : 1}
|
| 599 |
|
| 600 |
if unchanged:
|
| 601 |
if self.verbose:
|
| 602 |
print " finished (no changes)"
|
| 603 |
return
|
| 604 |
|
| 605 |
if self.verbose:
|
| 606 |
print " deleting old data"
|
| 607 |
cursor.execute("DELETE FROM binary_packages")
|
| 608 |
self._clearVersions(cursor)
|
| 609 |
|
| 610 |
l = packages.keys()
|
| 611 |
|
| 612 |
if len(l) == 0:
|
| 613 |
raise ValueError, "no binary packages found"
|
| 614 |
|
| 615 |
l.sort()
|
| 616 |
def gen():
|
| 617 |
for key in l:
|
| 618 |
archs = packages[key].keys()
|
| 619 |
archs.sort()
|
| 620 |
archs = ','.join(archs)
|
| 621 |
yield key + (archs,)
|
| 622 |
|
| 623 |
if self.verbose:
|
| 624 |
print " storing binary package data"
|
| 625 |
|
| 626 |
cursor.executemany(
|
| 627 |
"""INSERT INTO binary_packages
|
| 628 |
(name, release, subrelease, archive, version,
|
| 629 |
source, source_version, archs)
|
| 630 |
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
| 631 |
gen())
|
| 632 |
|
| 633 |
def readBugs(self, cursor, path):
|
| 634 |
if self.verbose:
|
| 635 |
print "readBugs:"
|
| 636 |
|
| 637 |
def clear_db(cleared=[False]):
|
| 638 |
# Avoid clearing the database multiple times.
|
| 639 |
if cleared[0]:
|
| 640 |
return
|
| 641 |
else:
|
| 642 |
cleared[0] = True
|
| 643 |
|
| 644 |
cursor.execute("DELETE FROM debian_bugs")
|
| 645 |
cursor.execute("DELETE FROM bugs")
|
| 646 |
cursor.execute("DELETE FROM package_notes")
|
| 647 |
cursor.execute("DELETE FROM bugs_notes")
|
| 648 |
cursor.execute("DELETE FROM bugs_xref")
|
| 649 |
|
| 650 |
# The *_status tables are regenerated anyway, no need to
|
| 651 |
# delete them here.
|
| 652 |
|
| 653 |
self._clearVersions(cursor)
|
| 654 |
|
| 655 |
def do_parse(source, cleared=[False]):
|
| 656 |
errors = []
|
| 657 |
|
| 658 |
clear_db()
|
| 659 |
|
| 660 |
if self.verbose:
|
| 661 |
print " reading " + `source.name`
|
| 662 |
|
| 663 |
for bug in source:
|
| 664 |
try:
|
| 665 |
bug.writeDB(cursor)
|
| 666 |
except ValueError, e:
|
| 667 |
errors.append("%s: %d: error: %s"
|
| 668 |
% (bug.source_file, bug.source_line, e))
|
| 669 |
if errors:
|
| 670 |
raise InsertError(errors)
|
| 671 |
|
| 672 |
def has_changed(filename):
|
| 673 |
current_print = self.filePrint(filename)
|
| 674 |
for (old_print,) in cursor.execute(
|
| 675 |
"SELECT inodeprint FROM inodeprints WHERE file = ?",
|
| 676 |
(filename,)):
|
| 677 |
if old_print == current_print:
|
| 678 |
return False
|
| 679 |
else:
|
| 680 |
return True
|
| 681 |
return True
|
| 682 |
|
| 683 |
sources = ((bugs.CVEFile, '/CVE/list'),
|
| 684 |
(bugs.DSAFile, '/DSA/list'),
|
| 685 |
(bugs.DTSAFile, '/DTSA/list'))
|
| 686 |
|
| 687 |
unchanged = True
|
| 688 |
for (_, name) in sources:
|
| 689 |
if has_changed(path + name):
|
| 690 |
unchanged = False
|
| 691 |
break
|
| 692 |
if unchanged:
|
| 693 |
if self.verbose:
|
| 694 |
print " finished (no changes)"
|
| 695 |
return
|
| 696 |
|
| 697 |
clear_db()
|
| 698 |
|
| 699 |
def read_one(source):
|
| 700 |
filename = source.name
|
| 701 |
current_print = self.filePrint(filename)
|
| 702 |
|
| 703 |
do_parse(source)
|
| 704 |
cursor.execute(
|
| 705 |
"""INSERT OR REPLACE INTO inodeprints (inodeprint, file)
|
| 706 |
VALUES (?, ?)""", (current_print, filename))
|
| 707 |
|
| 708 |
for (cls, name) in sources:
|
| 709 |
read_one(cls(path + name))
|
| 710 |
|
| 711 |
errors = []
|
| 712 |
|
| 713 |
if self.verbose:
|
| 714 |
print " check cross-references"
|
| 715 |
|
| 716 |
# Keep normalized_target column for now.
|
| 717 |
cursor.execute("UPDATE bugs_xref SET normalized_target = target")
|
| 718 |
|
| 719 |
for (bug,) in cursor.execute(
|
| 720 |
"""SELECT DISTINCT target FROM bugs_xref
|
| 721 |
EXCEPT SELECT name FROM bugs"""):
|
| 722 |
if bug[0:3] == "VU#":
|
| 723 |
continue
|
| 724 |
errors.append("reference to unknwown bug " + bug)
|
| 725 |
|
| 726 |
if self.verbose:
|
| 727 |
print " apply FIXES"
|
| 728 |
|
| 729 |
target_sources = {}
|
| 730 |
for source, target in list(cursor.execute(
|
| 731 |
"""SELECT source, normalized_target
|
| 732 |
FROM bugs_xref WHERE copy_notes""")):
|
| 733 |
if target_sources.has_key(target):
|
| 734 |
target_sources[target][source] = True
|
| 735 |
else:
|
| 736 |
target_sources[target] = {source : True}
|
| 737 |
|
| 738 |
# Recursively collect all sources for each target. Add new
|
| 739 |
# sources until the set of sources stabilizes.
|
| 740 |
for sources in target_sources.values():
|
| 741 |
while 1:
|
| 742 |
old_size = len(sources.keys())
|
| 743 |
for src in sources.keys():
|
| 744 |
for s in target_sources.get(src, {}).keys():
|
| 745 |
sources[s] = True
|
| 746 |
if len(sources.keys()) == old_size:
|
| 747 |
break
|
| 748 |
|
| 749 |
# Copy all the notes from all sources.
|
| 750 |
for (target, sources) in target_sources.items():
|
| 751 |
sources = sources.keys()
|
| 752 |
for source in sources:
|
| 753 |
source_bug = bugs.BugFromDB(cursor, source)
|
| 754 |
for n in source_bug.notes:
|
| 755 |
# Only copy "real" notes, not notes which have
|
| 756 |
# already bee ncopied.
|
| 757 |
if n.bug_origin:
|
| 758 |
continue
|
| 759 |
if n.release:
|
| 760 |
rel = str(n.release)
|
| 761 |
else:
|
| 762 |
rel = ''
|
| 763 |
present = False
|
| 764 |
for (version, origin) in list(cursor.execute(
|
| 765 |
"""SELECT fixed_version, bug_origin
|
| 766 |
FROM package_notes
|
| 767 |
WHERE bug_name = ? AND package = ? AND release = ?""",
|
| 768 |
(target, n.package, rel))):
|
| 769 |
if version <> str(n.fixed_version):
|
| 770 |
bug = bugs.BugFromDB(cursor, origin or target)
|
| 771 |
errors.append(
|
| 772 |
("%s: %d: version %s for package %s "
|
| 773 |
+ "conflicts with %s")
|
| 774 |
% (bug.source_file, bug.source_line,
|
| 775 |
version, n.package, source_bug.name))
|
| 776 |
errors.append("%s: %d: location of %s"
|
| 777 |
% (source_bug.source_file,
|
| 778 |
source_bug.source_line,
|
| 779 |
source_bug.name))
|
| 780 |
present = True
|
| 781 |
if not present:
|
| 782 |
n.writeDB(cursor, target, bug_origin=source)
|
| 783 |
|
| 784 |
if errors:
|
| 785 |
raise InsertError(errors)
|
| 786 |
|
| 787 |
if self.verbose:
|
| 788 |
print " finished"
|
| 789 |
|
| 790 |
def availableReleases(self, cursor=None):
|
| 791 |
"""Returns a list of tuples (RELEASE, ARCHIVE,
|
| 792 |
SOURCES-PRESENT, ARCHITECTURE-LIST)."""
|
| 793 |
if cursor is None:
|
| 794 |
cursor = self.cursor()
|
| 795 |
|
| 796 |
releases = {}
|
| 797 |
for r in cursor.execute(
|
| 798 |
"""SELECT DISTINCT release, subrelease, archive
|
| 799 |
FROM source_packages"""):
|
| 800 |
releases[r] = (True, [])
|
| 801 |
|
| 802 |
for (rel, subrel, archive, archs) in cursor.execute(
|
| 803 |
"""SELECT DISTINCT release, subrelease, archive, archs
|
| 804 |
FROM binary_packages"""):
|
| 805 |
key = (rel, subrel, archive)
|
| 806 |
if not releases.has_key(key):
|
| 807 |
releases[key] = (False, [])
|
| 808 |
releases[key][1][:] = mergeLists(releases[key][1], archs)
|
| 809 |
|
| 810 |
result = []
|
| 811 |
for ((rel, subrel, archive), (sources, archs)) in releases.items():
|
| 812 |
result.append((rel, subrel, archive, sources, archs))
|
| 813 |
result.sort()
|
| 814 |
|
| 815 |
return result
|
| 816 |
|
| 817 |
def getFunnyPackageVersions(self):
|
| 818 |
"""Returns a list of (PACKAGE, RELEASE, ARCHIVE, VERSION,
|
| 819 |
SOURCE-VERSION) tuples such that PACKAGE is both a source and
|
| 820 |
binary package, but the associated version numbers are
|
| 821 |
different."""
|
| 822 |
|
| 823 |
return list(self.db.cursor().execute(
|
| 824 |
"""SELECT DISTINCT name, release, archive, version, source_version
|
| 825 |
FROM binary_packages
|
| 826 |
WHERE name = source AND version <> source_version
|
| 827 |
ORDER BY name, release, archive"""))
|
| 828 |
|
| 829 |
def _clearVersions(self, cursor):
|
| 830 |
cursor.execute("DELETE FROM version_linear_order")
|
| 831 |
|
| 832 |
def _updateVersions(self, cursor):
|
| 833 |
"""Updates the linear version table."""
|
| 834 |
|
| 835 |
if self.verbose:
|
| 836 |
print "updateVersions:"
|
| 837 |
|
| 838 |
for x in cursor.execute("SELECT * FROM version_linear_order LIMIT 1"):
|
| 839 |
if self.verbose:
|
| 840 |
print " finished (no changes)"
|
| 841 |
return
|
| 842 |
|
| 843 |
if self.verbose:
|
| 844 |
print " reading"
|
| 845 |
|
| 846 |
versions = []
|
| 847 |
for (v,) in cursor.execute(
|
| 848 |
"""SELECT DISTINCT *
|
| 849 |
FROM (SELECT fixed_version FROM package_notes
|
| 850 |
WHERE fixed_version IS NOT NULL
|
| 851 |
UNION ALL SELECT version FROM source_packages
|
| 852 |
UNION ALL SELECT version FROM binary_packages
|
| 853 |
UNION ALL SELECT source_version FROM binary_packages)"""):
|
| 854 |
versions.append(debian_support.Version(v))
|
| 855 |
|
| 856 |
if self.verbose:
|
| 857 |
print " calculating linear order"
|
| 858 |
versions.sort()
|
| 859 |
|
| 860 |
if self.verbose:
|
| 861 |
print " storing linear order"
|
| 862 |
for v in versions:
|
| 863 |
cursor.execute(
|
| 864 |
"INSERT INTO version_linear_order (version) VALUES (?)",
|
| 865 |
(str(v),))
|
| 866 |
|
| 867 |
if self.verbose:
|
| 868 |
print " updating package notes"
|
| 869 |
cursor.execute(
|
| 870 |
"""UPDATE package_notes
|
| 871 |
SET fixed_version_id = (SELECT id FROM version_linear_order
|
| 872 |
WHERE version = package_notes.fixed_version)
|
| 873 |
WHERE fixed_version IS NOT NULL""")
|
| 874 |
|
| 875 |
if self.verbose:
|
| 876 |
print " updating source packages"
|
| 877 |
cursor.execute(
|
| 878 |
"""UPDATE source_packages
|
| 879 |
SET version_id = (SELECT id FROM version_linear_order
|
| 880 |
WHERE version = source_packages.version)""")
|
| 881 |
|
| 882 |
if self.verbose:
|
| 883 |
print " updating binary packages"
|
| 884 |
cursor.execute(
|
| 885 |
"""UPDATE binary_packages
|
| 886 |
SET version_id = (SELECT id FROM version_linear_order
|
| 887 |
WHERE version = binary_packages.version),
|
| 888 |
source_version_id = (SELECT id FROM version_linear_order
|
| 889 |
WHERE version = binary_packages.source_version)""")
|
| 890 |
|
| 891 |
if self.verbose:
|
| 892 |
print " finished"
|
| 893 |
|
| 894 |
def calculateVulnerabilities(self, cursor):
|
| 895 |
"""Calculate vulnerable packages.
|
| 896 |
|
| 897 |
To each package note, a release-specific vulnerability status
|
| 898 |
is attached. Currently, only etch/testing is processed.
|
| 899 |
|
| 900 |
Returns a list strings describing inconsistencies.
|
| 901 |
"""
|
| 902 |
|
| 903 |
result = []
|
| 904 |
|
| 905 |
self._updateVersions(cursor)
|
| 906 |
|
| 907 |
if self.verbose:
|
| 908 |
print "calculateVulnerabilities:"
|
| 909 |
print " checking version consistency in package notes"
|
| 910 |
|
| 911 |
# Ignore etch because sarge issues may be fast-tracked into
|
| 912 |
# testing, bypassing unstable.
|
| 913 |
for (bug_name, pkg_name, rel, unstable_ver, rel_ver) \
|
| 914 |
in list(cursor.execute(
|
| 915 |
"""SELECT a.bug_name, a.package, b.release,
|
| 916 |
a.fixed_version, b.fixed_version
|
| 917 |
FROM package_notes a, package_notes b
|
| 918 |
WHERE a.bug_name = b.bug_name AND a.package = b.package
|
| 919 |
AND a.release = '' AND b.release NOT IN ('', 'etch')
|
| 920 |
AND a.fixed_version IS NOT NULL
|
| 921 |
AND a.fixed_version_id < b.fixed_version_id""")):
|
| 922 |
b = bugs.BugFromDB(cursor, bug_name)
|
| 923 |
result.append("%s:%d: inconsistent versions for package %s"
|
| 924 |
% (b.source_file, b.source_line, pkg_name))
|
| 925 |
result.append("%s:%d: unstable: %s"
|
| 926 |
% (b.source_file, b.source_line, unstable_ver))
|
| 927 |
result.append("%s:%d: release %s: %s"
|
| 928 |
% (b.source_file, b.source_line, `rel`, rel_ver))
|
| 929 |
|
| 930 |
if self.verbose:
|
| 931 |
print " checking source/binary packages"
|
| 932 |
cursor.execute(
|
| 933 |
"""UPDATE package_notes SET package_kind = 'unknown'
|
| 934 |
WHERE package_kind IN ('source', 'binary')""")
|
| 935 |
cursor.execute(
|
| 936 |
"""UPDATE package_notes SET package_kind = 'source'
|
| 937 |
WHERE package_kind = 'unknown'
|
| 938 |
AND EXISTS (SELECT * FROM source_packages AS p
|
| 939 |
WHERE p.name = package_notes.package)""")
|
| 940 |
cursor.execute(
|
| 941 |
"""UPDATE package_notes SET package_kind = 'binary'
|
| 942 |
WHERE package_kind = 'unknown'
|
| 943 |
AND EXISTS (SELECT * FROM binary_packages AS p
|
| 944 |
WHERE p.name = package_notes.package)""")
|
| 945 |
|
| 946 |
for (bug_name, s_package, b_package) in list(cursor.execute(
|
| 947 |
"""SELECT DISTINCT s.bug_name, s.package, b.package
|
| 948 |
FROM package_notes AS s, package_notes AS b, binary_packages AS p
|
| 949 |
WHERE s.bug_name = b.bug_name
|
| 950 |
AND s.package_kind = 'source'
|
| 951 |
AND b.package_kind = 'binary'
|
| 952 |
AND p.name = b.package AND p.source = s.package""")):
|
| 953 |
b = bugs.BugFromDB(cursor, bug_name)
|
| 954 |
result.append("%s:%d: source and binary package annotations"
|
| 955 |
% (b.source_file, b.source_line))
|
| 956 |
result.append("%s:%d: source package: %s"
|
| 957 |
% (b.source_file, b.source_line, s_package))
|
| 958 |
result.append("%s:%d: binary package: %s"
|
| 959 |
% (b.source_file, b.source_line, b_package))
|
| 960 |
|
| 961 |
for (bug_name, package, s1, s2) in list(cursor.execute(
|
| 962 |
"""SELECT n.bug_name, n.package,
|
| 963 |
MIN(bp.source) AS s1, MAX (bp.source) AS s2
|
| 964 |
FROM package_notes AS n, binary_packages AS bp
|
| 965 |
WHERE n.package_kind = 'binary' AND bp.name = n.package
|
| 966 |
GROUP BY n.bug_name, n.package
|
| 967 |
HAVING s1 <> s2""")):
|
| 968 |
b = bugs.BugFromDB(cursor, bug_name)
|
| 969 |
result.append("%s:%d: multiple source packages for package %s"
|
| 970 |
% (b.source_file, b.source_line, package))
|
| 971 |
result.append("%s:%d: candidates are %s and %s"
|
| 972 |
% (b.source_file, b.source_line, s1, s2))
|
| 973 |
|
| 974 |
for (bug_name, package) in list(cursor.execute(
|
| 975 |
"""SELECT n.bug_name, n.package
|
| 976 |
FROM package_notes AS n
|
| 977 |
WHERE n.package_kind = 'itp'
|
| 978 |
AND ((EXISTS (SELECT * FROM source_packages
|
| 979 |
WHERE name = n.package))
|
| 980 |
OR (EXISTS (SELECT * FROM binary_packages
|
| 981 |
WHERE name = n.package)))""")):
|
| 982 |
b = bugs.BugFromDB(cursor, bug_name)
|
| 983 |
result.append("%s:%d: ITPed package %s is in the archive"
|
| 984 |
% (b.source_file, b.source_line, package))
|
| 985 |
|
| 986 |
if False:
|
| 987 |
# The following check looks useful, but there are
|
| 988 |
# situations where we want to be very explicit about
|
| 989 |
# vulnerable binary packges (see CAN-2004-0914 and
|
| 990 |
# DSA-573-1).
|
| 991 |
for (bug_name, source_package, b1, b2) in list(cursor.execute(
|
| 992 |
"""SELECT n.bug_name, b.source,
|
| 993 |
MIN(n.package) AS n1, MAX(n.package) AS n2
|
| 994 |
FROM package_notes AS n, binary_packages AS b
|
| 995 |
WHERE n.package_kind = 'binary' AND b.name = n.package
|
| 996 |
GROUP BY n.bug_name, b.source
|
| 997 |
HAVING n1 <> n2""")):
|
| 998 |
b = bugs.BugFromDB(cursor, bug_name)
|
| 999 |
result.append(
|
| 1000 |
"%s:%d: source package %s is referenced multiple times:"
|
| 1001 |
% (b.source_file, b.source_line, source_package))
|
| 1002 |
result.append("%s:%d: binary package: %s"
|
| 1003 |
% (b.source_file, b.source_line, b1))
|
| 1004 |
result.append("%s:%d: binary package: %s"
|
| 1005 |
% (b.source_file, b.source_line, b2))
|
| 1006 |
|
| 1007 |
for (bug_name, pkg_name, release) in list(cursor.execute(
|
| 1008 |
"""SELECT DISTINCT bug_name, package, release FROM package_notes
|
| 1009 |
WHERE package_kind = 'binary' AND release <> ''""")):
|
| 1010 |
b = bugs.BugFromDB(cursor, bug_name)
|
| 1011 |
result.append("%s:%d: binary package %s used with release %s"
|
| 1012 |
% (b.source_file, b.source_line, `pkg_name`,
|
| 1013 |
`release`))
|
| 1014 |
|
| 1015 |
if result:
|
| 1016 |
return result
|
| 1017 |
|
| 1018 |
if self.verbose:
|
| 1019 |
print " remove old status"
|
| 1020 |
cursor.execute("DELETE FROM source_package_status")
|
| 1021 |
cursor.execute("DELETE FROM binary_package_status")
|
| 1022 |
cursor.execute("DELETE FROM bug_status")
|
| 1023 |
|
| 1024 |
if self.verbose:
|
| 1025 |
print " calculate package status"
|
| 1026 |
print " source packages (unqualified)"
|
| 1027 |
|
| 1028 |
cursor.execute(
|
| 1029 |
"""INSERT INTO source_package_status
|
| 1030 |
SELECT n.bug_name, p.rowid,
|
| 1031 |
n.fixed_version IS NULL OR p.version_id < n.fixed_version_id,
|
| 1032 |
n.urgency
|
| 1033 |
FROM package_notes AS n, source_packages AS p
|
| 1034 |
WHERE n.release = '' AND p.name = n.package""")
|
| 1035 |
|
| 1036 |
# Release annotations always override previous results,
|
| 1037 |
# therefore we use INSERT OR REPLACE.
|
| 1038 |
|
| 1039 |
if self.verbose:
|
| 1040 |
print " source packages (qualified)"
|
| 1041 |
cursor.execute(
|
| 1042 |
"""INSERT OR REPLACE INTO source_package_status
|
| 1043 |
SELECT n.bug_name, p.rowid,
|
| 1044 |
n.fixed_version IS NULL OR p.version_id < n.fixed_version_id,
|
| 1045 |
n.urgency
|
| 1046 |
FROM package_notes AS n, source_packages AS p
|
| 1047 |
WHERE p.name = n.package
|
| 1048 |
AND p.release = n.release""")
|
| 1049 |
|
| 1050 |
if self.verbose:
|
| 1051 |
print " binary packages (from source packages)"
|
| 1052 |
cursor.execute(
|
| 1053 |
"""INSERT INTO binary_package_status
|
| 1054 |
SELECT n.bug_name, p.rowid,
|
| 1055 |
n.fixed_version IS NULL
|
| 1056 |
OR p.source_version_id < n.fixed_version_id,
|
| 1057 |
n.urgency
|
| 1058 |
FROM package_notes AS n, binary_packages AS p
|
| 1059 |
WHERE n.release = '' AND p.source = n.package""")
|
| 1060 |
|
| 1061 |
cursor.execute(
|
| 1062 |
"""INSERT OR REPLACE INTO binary_package_status
|
| 1063 |
SELECT n.bug_name, p.rowid,
|
| 1064 |
n.fixed_version IS NULL
|
| 1065 |
OR p.source_version_id < n.fixed_version_id,
|
| 1066 |
n.urgency
|
| 1067 |
FROM package_notes AS n, binary_packages AS p
|
| 1068 |
WHERE p.source = n.package AND p.release = n.release""")
|
| 1069 |
|
| 1070 |
# Almost the same for binary packages. We prefer interpreting
|
| 1071 |
# package names as source packages, so we only process the
|
| 1072 |
# notes which refer to binary packages. (Of course, we do not
|
| 1073 |
# have to add status information for binary package
|
| 1074 |
# separately.)
|
| 1075 |
|
| 1076 |
if self.verbose:
|
| 1077 |
print " binary packages (unqualified)"
|
| 1078 |
cursor.execute(
|
| 1079 |
"""INSERT INTO binary_package_status
|
| 1080 |
SELECT n.bug_name, p.rowid,
|
| 1081 |
n.fixed_version IS NULL OR p.version_id < n.fixed_version_id,
|
| 1082 |
n.urgency
|
| 1083 |
FROM package_notes AS n, binary_packages AS p
|
| 1084 |
WHERE n.release = '' AND p.name = n.package
|
| 1085 |
AND n.package_kind = 'binary'""")
|
| 1086 |
|
| 1087 |
if self.verbose:
|
| 1088 |
print " binary packages (qualified)"
|
| 1089 |
cursor.execute(
|
| 1090 |
"""INSERT OR REPLACE INTO binary_package_status
|
| 1091 |
SELECT n.bug_name, p.rowid,
|
| 1092 |
n.fixed_version IS NULL OR p.version_id < n.fixed_version_id,
|
| 1093 |
n.urgency
|
| 1094 |
FROM package_notes AS n, binary_packages AS p
|
| 1095 |
WHERE p.name = n.package AND p.release = n.release
|
| 1096 |
AND n.package_kind = 'binary'""")
|
| 1097 |
|
| 1098 |
if self.verbose:
|
| 1099 |
print " source packages (from binary packages)"
|
| 1100 |
cursor.execute(
|
| 1101 |
"""INSERT INTO source_package_status
|
| 1102 |
SELECT n.bug_name, s.rowid,
|
| 1103 |
MAX(n.fixed_version IS NULL
|
| 1104 |
OR b.version_id < n.fixed_version_id),
|
| 1105 |
MAX(n.urgency)
|
| 1106 |
FROM package_notes AS n, binary_packages AS b,
|
| 1107 |
source_packages AS s
|
| 1108 |
WHERE n.package_kind = 'binary' AND b.name = n.package
|
| 1109 |
AND s.name = b.source
|
| 1110 |
AND s.release = b.release AND s.subrelease = b.subrelease
|
| 1111 |
AND s.archive = b.archive
|
| 1112 |
GROUP BY n.bug_name, s.rowid, s.release""")
|
| 1113 |
# The "GROUP BY" is needed because we we might have multiple
|
| 1114 |
# rows in the binary_packages table for different
|
| 1115 |
# architectures.
|
| 1116 |
# FIXME: MAX(n.urgency) is wrong.
|
| 1117 |
|
| 1118 |
# Calculate the release-specific bug status.
|
| 1119 |
|
| 1120 |
if self.verbose:
|
| 1121 |
print " calculate release status"
|
| 1122 |
|
| 1123 |
c = self.cursor()
|
| 1124 |
|
| 1125 |
for (bug_name,) in cursor.execute(
|
| 1126 |
"SELECT name FROM bugs WHERE NOT not_for_us"):
|
| 1127 |
|
| 1128 |
self._calcUnstable(c, bug_name)
|
| 1129 |
self._calcTesting(c, bug_name, 'testing', 'etch')
|
| 1130 |
self._calcTesting(c, bug_name, 'stable', 'sarge')
|
| 1131 |
|
| 1132 |
return result
|
| 1133 |
|
| 1134 |
def _calcUnstable(self, cursor, bug_name):
|
| 1135 |
"""Update bug_status with bug_name for unstable."""
|
| 1136 |
|
| 1137 |
vulnerable_packages = []
|
| 1138 |
have_something = False
|
| 1139 |
for (package, vulnerable) in cursor.execute(
|
| 1140 |
"""SELECT DISTINCT sp.name, st.vulnerable
|
| 1141 |
FROM source_package_status AS st,
|
| 1142 |
source_packages AS sp, package_notes AS n
|
| 1143 |
WHERE st.bug_name = ? AND sp.rowid = st.package
|
| 1144 |
AND sp.release = 'sid'
|
| 1145 |
AND n.bug_name = st.bug_name AND n.package = sp.name
|
| 1146 |
AND n.urgency <> 'unimportant'
|
| 1147 |
ORDER BY sp.name""",
|
| 1148 |
(bug_name,)):
|
| 1149 |
have_something = True
|
| 1150 |
if vulnerable:
|
| 1151 |
vulnerable_packages.append(package)
|
| 1152 |
|
| 1153 |
if vulnerable_packages:
|
| 1154 |
if len(vulnerable_packages) == 1:
|
| 1155 |
pkgs = "package %s is vulnerable" % vulnerable_packages[0]
|
| 1156 |
else:
|
| 1157 |
pkgs = ("packages %s are vulnerable"
|
| 1158 |
% ', '.join(vulnerable_packages))
|
| 1159 |
cursor.execute("""INSERT INTO bug_status
|
| 1160 |
(bug_name, release, status, reason)
|
| 1161 |
VALUES (?, 'unstable', 'vulnerable', ?)""",
|
| 1162 |
(bug_name, pkgs))
|
| 1163 |
else:
|
| 1164 |
if have_something:
|
| 1165 |
status = "not vulnerable"
|
| 1166 |
else:
|
| 1167 |
status = "not known to be vulnerable"
|
| 1168 |
cursor.execute("""INSERT INTO bug_status
|
| 1169 |
(bug_name, release, status, reason)
|
| 1170 |
VALUES (?, 'unstable', 'fixed', ?)""",
|
| 1171 |
(bug_name, status))
|
| 1172 |
|
| 1173 |
def _calcTesting(self, cursor, bug_name, suite, nickname):
|
| 1174 |
"""Update bug_status with bug_name for testing/stable."""
|
| 1175 |
|
| 1176 |
# Note that there is at most one source package per
|
| 1177 |
# note/release/subrelease triple, but we should check that
|
| 1178 |
# here.
|
| 1179 |
|
| 1180 |
status = {'' : {}, 'security' : {}}
|
| 1181 |
for (package, note, subrelease, vulnerable) in cursor.execute(
|
| 1182 |
"""SELECT DISTINCT sp.name, n.id, sp.subrelease,
|
| 1183 |
st.vulnerable
|
| 1184 |
FROM source_package_status AS st,
|
| 1185 |
source_packages AS sp, package_notes AS n
|
| 1186 |
WHERE st.bug_name = ? AND sp.rowid = st.package
|
| 1187 |
AND sp.release = ? AND sp.subrelease IN ('', 'security')
|
| 1188 |
AND n.bug_name = st.bug_name AND n.package = sp.name
|
| 1189 |
AND n.urgency <> 'unimportant'
|
| 1190 |
ORDER BY sp.name""",
|
| 1191 |
(bug_name, nickname)):
|
| 1192 |
status[subrelease][(package, note)] = vulnerable
|
| 1193 |
|
| 1194 |
# Check if any packages in plain testing are vulnerable, and
|
| 1195 |
# if all of those have been fixed in the security archive.
|
| 1196 |
fixed_in_security = True
|
| 1197 |
pkgs = {}
|
| 1198 |
for ((package, note), vulnerable) in status[''].items():
|
| 1199 |
if vulnerable:
|
| 1200 |
pkgs[package] = True
|
| 1201 |
if status['security'].get((package, note), True):
|
| 1202 |
fixed_in_security = False
|
| 1203 |
|
| 1204 |
pkgs = pkgs.keys()
|
| 1205 |
pkgs.sort()
|
| 1206 |
if len(pkgs) == 0:
|
| 1207 |
if len(status[''].keys()) == 0:
|
| 1208 |
msg = "not known to be vulnerable"
|
| 1209 |
else:
|
| 1210 |
msg = "not vulnerable"
|
| 1211 |
cursor.execute("""INSERT INTO bug_status
|
| 1212 |
(bug_name, release, status, reason)
|
| 1213 |
VALUES (?, ?, 'fixed', ?)""",
|
| 1214 |
(bug_name, suite, msg))
|
| 1215 |
return
|
| 1216 |
|
| 1217 |
if len(pkgs) == 1:
|
| 1218 |
pkgs = "package " + pkgs[0] + " is "
|
| 1219 |
else:
|
| 1220 |
pkgs = "packages " + ", ".join(pkgs) + " are "
|
| 1221 |
if fixed_in_security:
|
| 1222 |
pkgs = "%sfixed in %s-security" % (pkgs, suite)
|
| 1223 |
if suite == 'stable':
|
| 1224 |
status = 'fixed'
|
| 1225 |
else:
|
| 1226 |
status = "partially-fixed"
|
| 1227 |
else:
|
| 1228 |
pkgs += "vulnerable"
|
| 1229 |
status = "vulnerable"
|
| 1230 |
|
| 1231 |
cursor.execute("""INSERT INTO bug_status
|
| 1232 |
(bug_name, release, status, reason)
|
| 1233 |
VALUES (?, ?, ?, ?)""",
|
| 1234 |
(bug_name, suite, status, pkgs))
|
| 1235 |
|
| 1236 |
def replaceNVD(self, cursor, data):
|
| 1237 |
"""Replaces the stored NVD data."""
|
| 1238 |
cursor.execute("DELETE FROM nvd_data");
|
| 1239 |
cursor.executemany("INSERT INTO nvd_data VALUES (?"
|
| 1240 |
+ (", ?" * (len(data[0]) - 1))
|
| 1241 |
+ ")", data)
|
| 1242 |
|
| 1243 |
def getNVD(self, cursor, cve_name):
|
| 1244 |
"""Returns a dictionary with NVD data corresponding to the CVE name,
|
| 1245 |
or None."""
|
| 1246 |
for row in cursor.execute("SELECT * FROM nvd_data WHERE cve_name = ?",
|
| 1247 |
(cve_name,)):
|
| 1248 |
return NVDEntry(row, cursor.getdescription())
|
| 1249 |
return None
|
| 1250 |
|
| 1251 |
def getSourcePackageVersions(self, cursor, pkg):
|
| 1252 |
"""A generator which returns tuples (RELEASE-LIST, VERSION),
|
| 1253 |
the available versions of the source package pkg."""
|
| 1254 |
|
| 1255 |
for (releases, version) in cursor.execute(
|
| 1256 |
"""SELECT string_list(release) AS releases, version
|
| 1257 |
FROM (SELECT release, version FROM source_packages
|
| 1258 |
WHERE name = ?
|
| 1259 |
ORDER BY release_to_number(release))
|
| 1260 |
GROUP BY version""", (pkg,)):
|
| 1261 |
yield releases.split(', '), version
|
| 1262 |
|
| 1263 |
def getBinaryPackageVersions(self, cursor, pkg):
|
| 1264 |
"""A generator which returns tuples (RELEASE-LIST,
|
| 1265 |
SOURCE-PACKAGE, VERSION, ARCH-LIST), the available versions of
|
| 1266 |
the binary package pkg."""
|
| 1267 |
|
| 1268 |
for (releases, source, version, archs) in cursor.execute(
|
| 1269 |
"""SELECT string_list(release) AS releases, source, version, archs
|
| 1270 |
FROM (SELECT release, source, version, string_set(archs) AS archs
|
| 1271 |
FROM binary_packages
|
| 1272 |
WHERE name = ?
|
| 1273 |
GROUP BY release, source, version
|
| 1274 |
ORDER BY release_to_number(release))
|
| 1275 |
GROUP BY source, version, archs""", (pkg,)):
|
| 1276 |
yield releases.split(', '), source, version, archs.split(',')
|
| 1277 |
|
| 1278 |
def getBinaryPackagesForSource(self, cursor, pkg):
|
| 1279 |
"""A generator which returns tuples (PACKAGES, RELEASE-LIST,
|
| 1280 |
VERSION), the available binary packages built from the source
|
| 1281 |
package pkg."""
|
| 1282 |
|
| 1283 |
for (packages, releases, version, archs) in cursor.execute(
|
| 1284 |
"""SELECT string_list(package) AS packages, releases, version,
|
| 1285 |
archs
|
| 1286 |
FROM (SELECT package, string_list(rel) AS releases, version, archs
|
| 1287 |
FROM (SELECT name AS package,
|
| 1288 |
release_name(release, subrelease, archive) AS rel,
|
| 1289 |
version, string_set(archs) AS archs
|
| 1290 |
FROM binary_packages
|
| 1291 |
WHERE source = ?
|
| 1292 |
GROUP BY name, release, subrelease, archive, version
|
| 1293 |
ORDER BY release_to_number(release), subrelease)
|
| 1294 |
GROUP BY package, version, archs
|
| 1295 |
ORDER BY package)
|
| 1296 |
GROUP BY releases, version, archs
|
| 1297 |
ORDER BY version COLLATE version""", (pkg,)):
|
| 1298 |
yield (packages.split(', '), releases.split(', '),
|
| 1299 |
archs.split(','), version)
|
| 1300 |
|
| 1301 |
def getSourcePackages(self, cursor, bug):
|
| 1302 |
"""A generator which returns tuples (SOURCE-PACKAGE,
|
| 1303 |
RELEASE-LIST, VERSION, VULNERABLE-FLAG) of source packages
|
| 1304 |
which are related to the given bug."""
|
| 1305 |
|
| 1306 |
for (package, releases, version, vulnerable) in cursor.execute(
|
| 1307 |
"""SELECT package, string_list(release), version, vulnerable
|
| 1308 |
FROM (SELECT p.name AS package,
|
| 1309 |
release_name(p.release, p.subrelease, p.archive) AS release,
|
| 1310 |
p.version AS version, s.vulnerable AS vulnerable
|
| 1311 |
FROM source_package_status AS s, source_packages AS p
|
| 1312 |
WHERE s.bug_name = ? AND p.rowid = s.package
|
| 1313 |
ORDER BY release_to_number(p.release), p.subrelease)
|
| 1314 |
GROUP BY package, version, vulnerable
|
| 1315 |
ORDER BY package, version COLLATE version""",
|
| 1316 |
(bug,)):
|
| 1317 |
yield package, releases.split(', '), version, vulnerable
|
| 1318 |
|
| 1319 |
def getBinaryPackages(self, cursor, bug):
|
| 1320 |
"""A generator which returns tuples (BINARY-PACKAGE-LIST,
|
| 1321 |
RELEASE-LIST, VERSION, ARCH-LIST, VULNERABLE-FLAG) of binary
|
| 1322 |
packages which are related to the given bug."""
|
| 1323 |
|
| 1324 |
for (packages, releases, version, archs, vulnerable) in cursor.execute(
|
| 1325 |
"""SELECT string_list(package) AS packages, releases,
|
| 1326 |
version, archs, vulnerable
|
| 1327 |
FROM (SELECT package, string_set(release) AS releases,
|
| 1328 |
version, archs, vulnerable
|
| 1329 |
FROM (SELECT p.name AS package,
|
| 1330 |
release_name(p.release, p.subrelease, p.archive) AS release,
|
| 1331 |
p.version AS version, string_set(archs) AS archs,
|
| 1332 |
s.vulnerable AS vulnerable
|
| 1333 |
FROM binary_package_status AS s, binary_packages AS p
|
| 1334 |
WHERE s.bug_name = ? AND p.rowid = s.package
|
| 1335 |
GROUP BY p.name, p.release, p.subrelease, p.archive,
|
| 1336 |
p.version, vulnerable
|
| 1337 |
ORDER BY release_to_number(p.release), p.subrelease)
|
| 1338 |
GROUP BY package, version, vulnerable, archs
|
| 1339 |
ORDER BY package)
|
| 1340 |
GROUP BY releases, version, vulnerable, archs
|
| 1341 |
ORDER BY packages, version COLLATE version""",
|
| 1342 |
(bug,)):
|
| 1343 |
yield (packages.split(', '), releases.split(','), version,
|
| 1344 |
archs.split(','), vulnerable)
|
| 1345 |
|
| 1346 |
def getBugsFromDebianBug(self, cursor, number):
|
| 1347 |
"""A generator which returns a list of tuples
|
| 1348 |
(BUG-NAME, URGENCY, DESCRIPTION)."""
|
| 1349 |
|
| 1350 |
return cursor.execute(
|
| 1351 |
"""SELECT DISTINCT bugs.name, package_notes.urgency,
|
| 1352 |
bugs.description
|
| 1353 |
FROM debian_bugs, package_notes, bugs
|
| 1354 |
WHERE debian_bugs.bug = ? AND package_notes.id = debian_bugs.note
|
| 1355 |
AND bugs.name = package_notes.bug_name
|
| 1356 |
ORDER BY bug_name""", (number,))
|
| 1357 |
|
| 1358 |
def isSourcePackage(self, cursor, pkg):
|
| 1359 |
"""Returns a true value if pkg is a source package."""
|
| 1360 |
((flag,),) = cursor.execute(
|
| 1361 |
"SELECT EXISTS (SELECT * FROM source_packages WHERE name = ?)",
|
| 1362 |
(pkg,))
|
| 1363 |
return flag
|
| 1364 |
|
| 1365 |
def isBinaryPackage(self, cursor, pkg):
|
| 1366 |
"""Returns a true value if pkg is a binary package."""
|
| 1367 |
((flag,),) = cursor.execute(
|
| 1368 |
"SELECT EXISTS (SELECT * FROM binary_packages WHERE name = ?)",
|
| 1369 |
(pkg,))
|
| 1370 |
return flag
|
| 1371 |
|
| 1372 |
def getBugsForSourcePackage(self, cursor, pkg, vulnerable):
|
| 1373 |
"""Returns a generator for a list of (BUG, DESCRIPTION) pairs
|
| 1374 |
which have the requested status."""
|
| 1375 |
return cursor.execute(
|
| 1376 |
"""SELECT DISTINCT name, description
|
| 1377 |
FROM (SELECT bugs.name AS name, bugs.description AS description,
|
| 1378 |
MAX(st.vulnerable
|
| 1379 |
AND COALESCE((SELECT st2.vulnerable FROM source_packages AS sp2,
|
| 1380 |
source_package_status AS st2
|
| 1381 |
WHERE sp2.name = sp.name AND sp2.release = sp.release
|
| 1382 |
AND sp2.subrelease = 'security' AND sp2.archive = sp.archive
|
| 1383 |
AND st2.package = sp2.rowid AND st2.bug_name = st.bug_name
|
| 1384 |
ORDER BY st2.vulnerable DESC), 1)) AS vulnerable
|
| 1385 |
FROM source_packages AS sp, source_package_status AS st, bugs
|
| 1386 |
WHERE sp.name = ? AND sp.subrelease <> 'security'
|
| 1387 |
AND st.package = sp.rowid
|
| 1388 |
AND bugs.name = st.bug_name
|
| 1389 |
AND st.urgency <> 'unimportant'
|
| 1390 |
GROUP BY bugs.name, bugs.description, sp.name)
|
| 1391 |
WHERE vulnerable = ?
|
| 1392 |
ORDER BY name""", (pkg, vulnerable))
|
| 1393 |
|
| 1394 |
def getBugsForBinaryPackage(self, cursor, pkg, vulnerable):
|
| 1395 |
"""Returns a generator for a list of (BUG, DESCRIPTION) pairs
|
| 1396 |
which have the requested status."""
|
| 1397 |
return cursor.execute(
|
| 1398 |
"""SELECT name, description
|
| 1399 |
FROM (SELECT bugs.name AS name, bugs.description AS description,
|
| 1400 |
MAX(st.vulnerable) AS vulnerable
|
| 1401 |
FROM binary_packages AS bp, binary_package_status AS st, bugs
|
| 1402 |
WHERE bp.name = ? AND st.package = bp.rowid
|
| 1403 |
AND st.urgency <> 'unimportant'
|
| 1404 |
AND bugs.name = st.bug_name
|
| 1405 |
GROUP BY bugs.name, bugs.description)
|
| 1406 |
WHERE vulnerable = ?
|
| 1407 |
ORDER BY name""", (pkg, vulnerable))
|
| 1408 |
|
| 1409 |
def getNonBugsForBinaryPackage(self, cursor, pkg):
|
| 1410 |
"""Returns a generator for a list of (BUG, DESCRIPTION) pairs
|
| 1411 |
which have the requested status."""
|
| 1412 |
|
| 1413 |
return cursor.execute(
|
| 1414 |
"""SELECT DISTINCT bugs.name, bugs.description
|
| 1415 |
FROM binary_packages AS bp, binary_package_status AS st, bugs
|
| 1416 |
WHERE bp.name = ? AND st.package = bp.rowid
|
| 1417 |
AND st.urgency = 'unimportant'
|
| 1418 |
AND bugs.name = st.bug_name
|
| 1419 |
ORDER BY bugs.name""", (pkg,))
|
| 1420 |
|
| 1421 |
def getTODOs(self, cursor=None):
|
| 1422 |
"""Returns a list of pairs (BUG-NAME, DESCRIPTION)."""
|
| 1423 |
if cursor is None:
|
| 1424 |
cursor = self.cursor()
|
| 1425 |
return cursor.execute(
|
| 1426 |
"""SELECT DISTINCT bugs.name, bugs.description
|
| 1427 |
FROM bugs_notes, bugs
|
| 1428 |
WHERE bugs_notes.typ = 'TODO' AND bugs.name = bugs_notes.bug_name
|
| 1429 |
ORDER BY name """)
|
| 1430 |
|
| 1431 |
def getBugXrefs(self, cursor, bug):
|
| 1432 |
"""Returns a generator for a list of bug names. The listed
|
| 1433 |
bugs refer to the given bug, or the bug refers to them."""
|
| 1434 |
|
| 1435 |
for (bug_name,) in cursor.execute(
|
| 1436 |
"""SELECT DISTINCT bug
|
| 1437 |
FROM (SELECT normalized_target AS bug
|
| 1438 |
FROM bugs_xref WHERE source = ?
|
| 1439 |
UNION ALL SELECT source AS bug
|
| 1440 |
FROM bugs_xref WHERE normalized_target = ?
|
| 1441 |
UNION ALL SELECT bug_origin AS bug FROM package_notes
|
| 1442 |
WHERE bug_name = ? AND bug_origin <> '')
|
| 1443 |
WHERE bug <> ?
|
| 1444 |
ORDER BY bug""", (bug, bug, bug, bug)):
|
| 1445 |
yield bug_name
|
| 1446 |
|
| 1447 |
def readRemovedPackages(self, cursor, filename):
|
| 1448 |
"""Reads a file of removed packages and stores it in the database."""
|
| 1449 |
|
| 1450 |
f = file(filename)
|
| 1451 |
|
| 1452 |
re_package = re.compile(r'^\s*([a-z0-9]\S+)\s*$')
|
| 1453 |
|
| 1454 |
# Not very good error reporting, but changes to that file are
|
| 1455 |
# rare.
|
| 1456 |
|
| 1457 |
def gen():
|
| 1458 |
for line in f:
|
| 1459 |
if line == '':
|
| 1460 |
break
|
| 1461 |
if line[0] == '#' or line == '\n':
|
| 1462 |
continue
|
| 1463 |
match = re_package.match(line)
|
| 1464 |
if match:
|
| 1465 |
yield match.groups()
|
| 1466 |
else:
|
| 1467 |
raise ValueError, "not a package: " + `line`
|
| 1468 |
|
| 1469 |
cursor.execute("DELETE FROM removed_packages")
|
| 1470 |
cursor.executemany("INSERT INTO removed_packages (name) VALUES (?)",
|
| 1471 |
gen())
|
| 1472 |
|
| 1473 |
|
| 1474 |
def getUnknownPackages(self, cursor):
|
| 1475 |
"""Returns a generator for a list of unknown packages.
|
| 1476 |
Each entry has the form (PACKAGE, BUG-LIST)."""
|
| 1477 |
|
| 1478 |
old_package = ''
|
| 1479 |
bugs = []
|
| 1480 |
for (package, bug_name) in cursor.execute(
|
| 1481 |
"""SELECT DISTINCT package, bug_name
|
| 1482 |
FROM package_notes WHERE package_kind = 'unknown'
|
| 1483 |
AND NOT EXISTS (SELECT * FROM removed_packages
|
| 1484 |
WHERE name = package)
|
| 1485 |
ORDER BY package, bug_name"""):
|
| 1486 |
if package <> old_package:
|
| 1487 |
if old_package:
|
| 1488 |
yield (old_package, bugs)
|
| 1489 |
old_package = package
|
| 1490 |
bugs = []
|
| 1491 |
bugs.append(bug_name)
|
| 1492 |
if old_package:
|
| 1493 |
yield (old_package, bugs)
|
| 1494 |
|
| 1495 |
def getITPs(self, cursor):
|
| 1496 |
"""Returns a generator for a list of unknown packages.
|
| 1497 |
Each entry has the form (PACKAGE, BUG-LIST, DEBIAN-BUG-LIST)."""
|
| 1498 |
|
| 1499 |
# The "|| ''" is required to convert the string_set argument
|
| 1500 |
# to a string.
|
| 1501 |
for (package, bugs, debian_bugs) in cursor.execute(
|
| 1502 |
"""SELECT DISTINCT n.package, string_set(n.bug_name),
|
| 1503 |
string_set(db.bug || '')
|
| 1504 |
FROM package_notes AS n, debian_bugs AS db
|
| 1505 |
WHERE package_kind = 'itp'
|
| 1506 |
AND db.note = n.id
|
| 1507 |
GROUP BY n.package
|
| 1508 |
ORDER BY n.package"""):
|
| 1509 |
yield (package, bugs.split(','), map(int, debian_bugs.split(',')))
|
| 1510 |
|
| 1511 |
def check(self, cursor=None):
|
| 1512 |
"""Runs a simple consistency check and prints the results."""
|
| 1513 |
|
| 1514 |
if cursor is None:
|
| 1515 |
cursor = self.cursor()
|
| 1516 |
|
| 1517 |
for (package, release, archive, architecture, source) in\
|
| 1518 |
cursor.execute(
|
| 1519 |
"""SELECT package, release, archive, architecture, source
|
| 1520 |
FROM binary_packages
|
| 1521 |
WHERE NOT EXISTS
|
| 1522 |
(SELECT *
|
| 1523 |
FROM source_packages AS sp
|
| 1524 |
WHERE sp.package = binary_packages.source
|
| 1525 |
AND sp.release = binary_packages.release
|
| 1526 |
AND sp.archive = binary_packages.archive)
|
| 1527 |
"""):
|
| 1528 |
print "error: binary package without source package"
|
| 1529 |
print " binary package:", package
|
| 1530 |
print " release:", release
|
| 1531 |
if archive:
|
| 1532 |
print " archive:", archive
|
| 1533 |
print " architecture:", architecture
|
| 1534 |
print " missing source package:", source
|
| 1535 |
|
| 1536 |
for (package, release, archive, architecture, version,
|
| 1537 |
source, source_version) \
|
| 1538 |
in cursor.execute("""SELECT binary_packages.package,
|
| 1539 |
binary_packages.release, binary_packages.archive,
|
| 1540 |
binary_packages.architecture,binary_packages.version,
|
| 1541 |
sp.package, sp.version
|
| 1542 |
FROM binary_packages, source_packages AS sp
|
| 1543 |
WHERE sp.package = binary_packages.source
|
| 1544 |
AND sp.release = binary_packages.release
|
| 1545 |
AND sp.archive = binary_packages.archive
|
| 1546 |
AND sp.version <> binary_packages.source_version"""):
|
| 1547 |
relation = cmp(debian_support.Version(version),
|
| 1548 |
debian_support.Version(source_version))
|
| 1549 |
assert relation <> 0
|
| 1550 |
if relation <= 0:
|
| 1551 |
print "error: binary package is older than source package"
|
| 1552 |
else:
|
| 1553 |
print "warning: binary package is newer than source package"
|
| 1554 |
print " binary package: %s (%s)" % (package, version)
|
| 1555 |
print " source package: %s (%s)" % (source, source_version)
|
| 1556 |
print " release:", release
|
| 1557 |
if archive:
|
| 1558 |
print " archive:", archive
|
| 1559 |
print " architecture:", architecture
|
| 1560 |
|
| 1561 |
def test():
|
| 1562 |
assert mergeLists('', '') == [], mergeLists('', '')
|
| 1563 |
assert mergeLists('', []) == []
|
| 1564 |
assert mergeLists('a', 'a') == ['a']
|
| 1565 |
assert mergeLists('a', 'b') == ['a', 'b']
|
| 1566 |
assert mergeLists('a,c', 'b') == ['a', 'b', 'c']
|
| 1567 |
assert mergeLists('a,c', ['b', 'de']) == ['a', 'b', 'c', 'de']
|
| 1568 |
|
| 1569 |
import os
|
| 1570 |
db_file = 'test_security.db'
|
| 1571 |
try:
|
| 1572 |
db = DB(db_file)
|
| 1573 |
except SchemaMismatch:
|
| 1574 |
os.unlink(db_file)
|
| 1575 |
db = DB(db_file)
|
| 1576 |
|
| 1577 |
cursor = db.writeTxn()
|
| 1578 |
db.readBugs(cursor, '../../data')
|
| 1579 |
db.commit(cursor)
|
| 1580 |
|
| 1581 |
b = bugs.BugFromDB(cursor, 'CVE-2005-2491')
|
| 1582 |
assert b.name == 'CVE-2005-2491', b.name
|
| 1583 |
assert b.description == 'Integer overflow in pcre_compile.c in Perl Compatible Regular ...', b.description
|
| 1584 |
assert len(b.xref) == 2, b.xref
|
| 1585 |
assert not b.not_for_us
|
| 1586 |
assert 'DSA-800-1' in b.xref, b.xref
|
| 1587 |
assert 'DTSA-10-1' in b.xref, b.xref
|
| 1588 |
assert tuple(b.comments) == (('NOTE', 'gnumeric/goffice includes one as well; according to upstream not exploitable in gnumeric,'),
|
| 1589 |
('NOTE', 'new copy will be included any way')),\
|
| 1590 |
b.comments
|
| 1591 |
|
| 1592 |
assert len(b.notes) == 4, len(b.notes)
|
| 1593 |
|
| 1594 |
for n in b.notes:
|
| 1595 |
assert n.release is None
|
| 1596 |
if n.package == 'pcre3':
|
| 1597 |
assert n.fixed_version == debian_support.Version('6.3-0.1etch1')
|
| 1598 |
assert tuple(n.bugs) == (324531,), n.bugs
|
| 1599 |
assert n.urgency == bugs.internUrgency('medium')
|
| 1600 |
elif n.package == 'python2.1':
|
| 1601 |
assert n.fixed_version == debian_support.Version('2.1.3dfsg-3')
|
| 1602 |
assert len(n.bugs) == 0, n.bugs
|
| 1603 |
assert n.urgency == bugs.internUrgency('medium')
|
| 1604 |
elif n.package == 'python2.2':
|
| 1605 |
assert n.fixed_version == debian_support.Version('2.2.3dfsg-4')
|
| 1606 |
assert len(n.bugs) == 0, n.bugs
|
| 1607 |
assert n.urgency == bugs.internUrgency('medium')
|
| 1608 |
elif n.package == 'python2.3':
|
| 1609 |
assert n.fixed_version == debian_support.Version('2.3.5-8')
|
| 1610 |
assert len(n.bugs) == 0, n.bugs
|
| 1611 |
assert n.urgency == bugs.internUrgency('medium')
|
| 1612 |
else:
|
| 1613 |
assert False
|
| 1614 |
|
| 1615 |
assert bugs.BugFromDB(cursor, 'DSA-311').isKernelOnly()
|
| 1616 |
|
| 1617 |
if __name__ == "__main__":
|
| 1618 |
test()
|