/[secure-testing]/lib/python/security_db.py
ViewVC logotype

Contents of /lib/python/security_db.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2488 - (show annotations) (download) (as text)
Thu Oct 20 09:03:39 2005 UTC (7 years, 8 months ago) by fw
File MIME type: text/x-python
File size: 64319 byte(s)
 r638@deneb:  fw | 2005-10-14 15:43:12 +0200
 bin/tracker_service.py (TrackerService.page_home):
   Document external interfaces.
 (TrackerService.page_bug):
   Add NVD references.
 (TrackerService.page_status_release_stable,
  TrackerService.page_status_release_testing):
   Show NVD remote attack range if present.
 (TrackerService.url_nvd, TrackerService.make_nvd_ref):
   New.
 
 lib/python/security_db.py (NVDEntry):
   New class.
 (DB.initSchema):
   New nvd_data table.  Update stable_status and testing_status views.
 (DB.replaceNVD, DB.getNVD):
   New methods.
 
 bin/update-nvd, lib/python/nvd.py:
   New files.
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()

  ViewVC Help
Powered by ViewVC 1.1.5