Merge ~sbeattie/ubuntu-cve-tracker/+git/ubuntu-cve-tracker:create_uct_module_refactoring into ubuntu-cve-tracker:master

Proposed by Steve Beattie
Status: Needs review
Proposed branch: ~sbeattie/ubuntu-cve-tracker/+git/ubuntu-cve-tracker:create_uct_module_refactoring
Merge into: ubuntu-cve-tracker:master
Diff against target: 1651 lines (+537/-339)
41 files modified
.coveragerc (+7/-0)
.gitignore (+2/-0)
.launchpad.yaml (+7/-5)
Makefile (+31/-2)
cache/.empty_dir (+0/-0)
scripts/boilerplate-to-json.py (+2/-1)
scripts/check-cves (+14/-234)
scripts/check-unreplaced-templates.py (+2/-1)
scripts/cve_lib.py (+7/-14)
scripts/debian-cve-update (+3/-2)
scripts/dump-features (+0/-2)
scripts/gen-source-map-cache (+2/-1)
scripts/generate-graphs.py (+0/-2)
scripts/plot-usns.py (+2/-4)
scripts/process_cves (+21/-18)
scripts/pull-in-progress.py (+3/-2)
scripts/report-bugs (+3/-2)
scripts/report-bugs-by-assignee (+0/-3)
scripts/report-bugs-by-team (+3/-2)
scripts/report-bugs-for-eol (+3/-2)
scripts/report-cve-age.py (+2/-1)
scripts/report-mistriaged-cves.py (+3/-2)
scripts/report-packages.py (+2/-1)
scripts/report-todo-mir (+0/-4)
scripts/report-todo-sponsoring (+0/-3)
scripts/report-updates.py (+2/-1)
scripts/report-version.py (+2/-1)
scripts/sis-changes (+3/-2)
scripts/sis-generate-usn (+2/-1)
scripts/source_map.py (+4/-22)
scripts/sync-bugs-kernel.py (+2/-1)
scripts/sync-from-usns.py (+2/-1)
scripts/test_uct_config.py (+48/-0)
scripts/test_uct_suggestions.py (+124/-0)
scripts/uct/config.py (+75/-0)
scripts/uct/suggestions.py (+131/-0)
scripts/usn_lib.py (+2/-2)
test/uct/config/no_plb_auth_entry.conf (+2/-0)
test/uct/config/plb_auth_dev_null.conf (+2/-0)
test/uct/config/plb_auth_does_not_exist.conf (+2/-0)
test/uct/config/uct.conf (+15/-0)
Reviewer Review Type Date Requested Status
Marc Deslauriers Approve
Review via email: mp+462102@code.launchpad.net

Commit message

[WIP] refactor UCT layout and add coverage and script syntax check reporting

This branch is motivated by a few different things:

- wanting to break up cve_lib.py into smaller components, as well as support moving things implemented in individual scripts into common importable places
- wanting to know about the deprecation warnings from newer versions of python (like the recent invalid escape sequence warnings that came from python 3.12)
- wanting to see test coverage reports

To do this, I made a couple of changes that we can bikeshed about:

- created a scripts/uct directory / module that can be imported and moved a few things in there.
- created an empty cache/ directory and adjusted process_cves to use it to store and look for external data files during triage

The motivation for the latter was to (a) reduce the number of things that we look at when identifying python or shell scripts to check for syntax or other errors, and (b) I would like to reduce the clutter from the top level of UCT. I have not done it yet, but would like to move the *supported.txt files to a subdirectory (likely the meta_lists/ directory but we can argue about the name/location).

Some results out of this series:

- there are new make targets:
    - check-python: invokes the same tests that the launchpad.yaml unit test job does (these should probably be unified)
    - check-syntax-scripts: calls specific targets for running the syntax checkers on python and shell scripts in the tree

These targets are added as dependencies to the 'check' target but that also runs `check-syntax` on the cve files, so they need to be individually invokable if we're going to keep the separate test jobs in lpci.

Also a `coverage` make target was added that generates an html reoprt of the python test coverage in the coverage_html subdirectory, which reports details about which lines in each python have or have not been exercised. Interestingly, it identifies that a couple of small portions of the existing tests that are supposed to look at the subprojects tree don't get exercised (despite the symlink to the external repo being present on my system); not sure what's going wrong there. In an ideal world the test coverage pages would get published somewhere.

One thing to think about is how to integrate the rest of the oval functional and semi-end-to-end tests that are in the toplevel test/ directory, and work around that some of the data sources may not be available from lpci.

Another note: the read_config() and read_config_file() implementation cannot yet be removed, as there are a number of scripts in repos outsde of UCT Tthat need to be adjusted first.

Description of the change

UCT layout changes and add test coverage and syntax warning reports

* [988c62cb224] uct module: create, reproduce cve_lib.read_config() functionality
* [3aa4f0a5186] UCT/scripts: remove unneeded calls to cve_lib.read_config()
* [a691753e5c9] UCT/scripts: convert most scripts to use uct.config.read_uct_config()
* [26fcf6446ec] cve_lib.py: convert read_config to use uct.config.read_uct_config()
* [657e0da2753] check-cves: move ignored_cache.py under scripts/uct
* [1ef963e634f] check-cves: move ignore suggestion reasons to separate file/class
* [4a803a179ca] launchpad.yaml: run pytest against scripts/test_*.py
* [6578c2f6e15] launchpad.yaml: drop calling check-cves --test
* [85aa9d5031e] check-cves: drop self unit test argument and implementation
* [1c539b24ed7] launchpad.yaml: generate a coverage report for the unit tests
* [6345465aed2] Makefile: add python check and coverage report targets
* [e62116181d9] Add empty cache dir
* [e01c4c53ac5] process_cves: use cache directory for triage
* [2161889cd62] Make: add targets for checking script syntax
* [196a1bff689] launchpad.yaml: add syntax warning to lpci unit test job

To post a comment you must log in.
Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

Do you need to add the new cache directory to .gitignore?

34c2e14... by Steve Beattie

gitignore: ignore cache directory

Signed-off-by: Steve Beattie <email address hidden>

Revision history for this message
Steve Beattie (sbeattie) wrote :

On Sun, Mar 10, 2024 at 07:21:57PM -0000, Marc Deslauriers wrote:
> Do you need to add the new cache directory to .gitignore?

Not *strictly* necessary, but useful for not committing additional files
to that directory. Added a commit for that.

--
Steve Beattie
<email address hidden>

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

I agree with the reasoning behind this, and everything looks good to me. ACK

review: Approve

Unmerged commits

34c2e14... by Steve Beattie

gitignore: ignore cache directory

Signed-off-by: Steve Beattie <email address hidden>

Succeeded
[SUCCEEDED] unit-tests:0 (build)
[SUCCEEDED] check-cves:0 (build)
12 of 2 results
196a1bf... by Steve Beattie

launchpad.yaml: add syntax warning to lpci unit test job

Add needed dependencies and invoke make check-syntax-scripts

Signed-off-by: Steve Beattie <email address hidden>

Succeeded
[SUCCEEDED] unit-tests:0 (build)
[SUCCEEDED] check-cves:0 (build)
12 of 2 results
2161889... by Steve Beattie

Make: add targets for checking script syntax

Add targets for checking scripts syntax, currently using pyflakes and
shellcheck; I tried to use flake8 but it has an issue with the version
of pycodestyle in noble.

These invocations have their error codes suppressed, so that they are
informational only for now, as there are a large number of items
reported that need to be investigated.

This is the reason for the prior commit to try to move the nvd files
into a cache subdirectory to make pruning directories easier so that
e.g. file isn't invoked on all the individual nvd cve json files.

Signed-off-by: Steve Beattie <email address hidden>

e01c4c5... by Steve Beattie

process_cves: use cache directory for triage

Signed-off-by: Steve Beattie <email address hidden>

Succeeded
[SUCCEEDED] unit-tests:0 (build)
[SUCCEEDED] check-cves:0 (build)
12 of 2 results
e621161... by Steve Beattie

Add empty cache dir

Signed-off-by: Steve Beattie <email address hidden>

6345465... by Steve Beattie

Makefile: add python check and coverage report targets

Use the coverage module to generate an html coverage report. As part of
this:

- Adjust the .coveragerc config file to omit files that have not been
  covnerted to be python3 parseable.
- add the coverage_html created directory to .gitignore

Signed-off-by: Steve Beattie <email address hidden>

1c539b2... by Steve Beattie

launchpad.yaml: generate a coverage report for the unit tests

Signed-off-by: Steve Beattie <email address hidden>

85aa9d5... by Steve Beattie

check-cves: drop self unit test argument and implementation

Signed-off-by: Steve Beattie <email address hidden>

6578c2f... by Steve Beattie

launchpad.yaml: drop calling check-cves --test

As the same tests are now covered in the unit tests section, via
test_uct_suggestions.py

Signed-off-by: Steve Beattie <email address hidden>

4a803a1... by Steve Beattie

launchpad.yaml: run pytest against scripts/test_*.py

This way it will automatically pick up any new test scripts, including
the most recent scripts/test_uct_suggestions.py

Signed-off-by: Steve Beattie <email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.coveragerc b/.coveragerc
2index 3ad560c..d59ccbe 100644
3--- a/.coveragerc
4+++ b/.coveragerc
5@@ -3,3 +3,10 @@ branch = True
6 include =
7 scripts/generate-oval
8 scripts/oval_lib.py
9+# the follow scripts have not been converted to be parseable by a
10+# python3 intepreter
11+omit =
12+ scripts/pull-in-progress.py
13+ scripts/report-date.py
14+ scripts/report-mismatched-cve-fixes.py
15+ scripts/report_universe_cves.py
16diff --git a/.gitignore b/.gitignore
17index 551e1e4..d8a74d4 100644
18--- a/.gitignore
19+++ b/.gitignore
20@@ -70,7 +70,9 @@ graphing/cosmic.data
21 ignore_package_regexes.txt
22 .login
23 .coverage
24+coverage_html
25 /subprojects
26 /pkg_cache.json
27 .vscode/
28 /.venv
29+/cache/
30diff --git a/.launchpad.yaml b/.launchpad.yaml
31index a089c1d..9bcdb5d 100644
32--- a/.launchpad.yaml
33+++ b/.launchpad.yaml
34@@ -13,7 +13,9 @@ jobs:
35 architectures: amd64
36 packages:
37 - distro-info
38+ - file
39 - lsb-release
40+ - pyflakes3
41 - python3
42 - python3-apt
43 - python3-configobj
44@@ -21,7 +23,9 @@ jobs:
45 - python3-mock
46 - python3-progressbar
47 - python3-pytest
48+ - python3-pytest-cov
49 - python3-yaml
50+ - shellcheck
51 run-before: |
52 # configure a basic ~/.ubuntu-cve-tracker.conf
53 echo plb_authentication=/dev/null > ~/.ubuntu-cve-tracker.conf
54@@ -35,11 +39,10 @@ jobs:
55 # new one
56 rm -f embargoed
57 mkdir embargoed
58+ echo "reporting syntax issues on scripts"
59+ make check-syntax-scripts
60 echo "Running unit tests..."
61- pytest-3 ./scripts/test_cve_lib.py ./scripts/test_kernel_lib.py ./scripts/test_usn_lib.py ./scripts/test_source_map.py ./scripts/test_publish-cves-to-website-api.py ./scripts/test_sync_from_usns.py ./test/test_oval_lib_unit.py
62- # ideally we would also run ./scripts/check-cves --test here as well as it
63- # is more of a unit test but it requires packages-mirror so run it as part
64- # of check-syntax below
65+ pytest-3 --cov=scripts ./scripts/test_*.py ./test/test_oval_lib_unit.py
66 check-cves:
67 series: jammy
68 architectures: amd64
69@@ -75,6 +78,5 @@ jobs:
70 rm -f embargoed
71 mkdir embargoed
72
73- ./scripts/check-cves --test
74 echo "Checking syntax..."
75 ./scripts/check-syntax
76diff --git a/Makefile b/Makefile
77index 2c0abb7..254b074 100644
78--- a/Makefile
79+++ b/Makefile
80@@ -13,6 +13,9 @@ ifneq ($(UCT_REVIEWED)$(SCRIPTS_RELDIR),$(SCRIPTS_RELDIR))
81 endif
82 export SCRIPTS=$(UCT_SCRIPTS)
83 export ACTIVE=$(shell pwd)/active
84+PYTEST=pytest-3
85+PYTHON=python3
86+PYFLAKES=pyflakes3
87
88 all: cves pkgs tables
89
90@@ -79,7 +82,33 @@ dev_setup:
91 echo '*** $$UQT is not set, unable to find location of lpl_common.py ***' ; \
92 fi
93
94-check:
95+syntax-shell:
96+ # run shellcheck on the specific types of shell scripts we have
97+ # this does not propagate the error code yet because we have a
98+ # a bunch of warnings, some of which because of how things are
99+ # used, just need to be silenced.
100+ find -type d \( -name cache -o -name active -o -name retired -o -name ignored -o -name nvd-database -o -name .git \) -prune -o -exec file {} \+ | grep "Bourne-Again shell script" | cut -d ":" -f 1 | xargs shellcheck -s bash || true
101+ find -type d \( -name cache -o -name active -o -name retired -o -name ignored -o -name nvd-database -o -name .git \) -prune -o -exec file {} \+ | grep -i "posix shell script" | cut -d ":" -f 1 | xargs shellcheck -s dash || true
102+
103+syntax-python:
104+ # run pyflakes on the python scripts we have
105+ # this does not propagate the error code yet because there are
106+ # some scripts that need to be made python3 compliant and some
107+ # imports cleaned up
108+ find -type d \( -name cache -o -name active -o -name retired -o -name ignored -o -name nvd-database -o -name .git \) -prune -o -exec file {} \+ | grep "Python script" | cut -d ":" -f 1 | xargs $(PYFLAKES) || true
109+
110+check-syntax-scripts: syntax-shell syntax-python
111+
112+coverage-python: check-python .coverage
113+ $(PYTHON) -m coverage html --dir coverage_html
114+
115+.coverage: check-python
116+
117+check-python:
118+ $(PYTEST) --cov=scripts ./scripts/test_*.py ./test/test_oval_lib_unit.py
119+
120+check: check-syntax-scripts check-python
121 $(SCRIPTS)/check-syntax
122
123-.PHONY: dev_setup check
124+.PHONY: dev_setup check check-python coverage-python check-syntax-scripts \
125+syntax-shell syntax-python
126diff --git a/cache/.empty_dir b/cache/.empty_dir
127new file mode 100644
128index 0000000..e69de29
129--- /dev/null
130+++ b/cache/.empty_dir
131diff --git a/scripts/boilerplate-to-json.py b/scripts/boilerplate-to-json.py
132index 06d800f..4066116 100755
133--- a/scripts/boilerplate-to-json.py
134+++ b/scripts/boilerplate-to-json.py
135@@ -6,6 +6,7 @@ import re
136 import sys
137
138 import cve_lib
139+from uct.config import read_uct_config
140
141 def parse_boilerplate(filepath):
142 cve_data = cve_lib.load_cve(filepath)
143@@ -131,7 +132,7 @@ def parse_embedded_code_copies(filepath):
144 return data
145
146 # also parse debian's embedded-code-copies and amalgate that into data
147-config = cve_lib.read_config()
148+config = read_uct_config()
149 debian_embedded_copies = os.path.join(config['secure_testing_path'], "data", "embedded-code-copies")
150 code_copies = parse_embedded_code_copies(debian_embedded_copies)
151 for pkg in code_copies["pkgs"]:
152diff --git a/scripts/check-cves b/scripts/check-cves
153index 8a9e8fc..020693f 100755
154--- a/scripts/check-cves
155+++ b/scripts/check-cves
156@@ -28,7 +28,6 @@ import subprocess
157 import sys
158 import tempfile
159 import time
160-import unittest
161 import urllib.request
162 import xml.sax
163 import xml.sax.handler
164@@ -38,10 +37,12 @@ import progressbar
165
166 import cve_lib
167 import source_map
168-from check_cves.ignored_cache import IgnoredCache
169+from uct.ignored_cache import IgnoredCache
170+from uct.suggestions import IgnoreSuggestions
171+from uct.config import read_uct_config
172
173 # load settings, if any
174-cve_lib.read_config()
175+uct_config = read_uct_config()
176
177 parser = optparse.OptionParser()
178 parser.add_option("-r", "--report", help="Just report CVEs that need checking", action="store_true")
179@@ -50,7 +51,7 @@ parser.add_option("-k", "--known", help="Only report CVEs already known", action
180 parser.add_option("-N", "--skip-nfu", help="Skip any CVEs marked as NFU (used with -k)", action="store_true")
181 parser.add_option("-R", "--refresh", help="Refresh CVE descriptions", action="store_true")
182 parser.add_option("-S", "--score-refresh", help="Refresh CVSS scores values only", action="store_true")
183-parser.add_option("", "--test", help="Run regression tests", action="store_true")
184+parser.add_option("", "--test", help="NO LONGER SUPPORTED, see test_uct_suggestions.py", action="help")
185 parser.add_option("--untriaged", help="Process untriaged CVEs from output of locate_cves.py", metavar="FILE")
186 parser.add_option("--mbox", help="Process untriaged CVEs from mbox file", metavar="FILE")
187 parser.add_option("--rhel8oval", help="Process untriaged RHEL8 CVEs", metavar="FILE")
188@@ -242,7 +243,7 @@ def import_debian(handler):
189 return False
190
191 # pull in CVEs from data/DSA/list
192- dsas = cve_lib.load_debian_dsas(cve_lib.config['secure_testing_path'] + '/data/DSA/list', opt.verbose)
193+ dsas = cve_lib.load_debian_dsas(uct_config['secure_testing_path'] + '/data/DSA/list', opt.verbose)
194 for dsa in dsas:
195 for cve in dsas[dsa]['cves']:
196 if not cve_lib.CVE_RE.match(cve):
197@@ -581,12 +582,14 @@ class CVEHandler(xml.sax.handler.ContentHandler):
198 self.saved_cve = ""
199 self.debian = None
200
201+ # get ignore suggestion
202+ self.ignore_suggestion = IgnoreSuggestions()
203 # File-type detection
204
205
206 # Load debian CVE states, if configured
207- if 'secure_testing_path' in cve_lib.config:
208- self.debian = cve_lib.load_debian_cves(cve_lib.config['secure_testing_path'] + '/data/CVE/list')
209+ if 'secure_testing_path' in uct_config:
210+ self.debian = cve_lib.load_debian_cves(uct_config['secure_testing_path'] + '/data/CVE/list')
211
212 def updateTimestamp(self):
213 # Get UTC time
214@@ -792,117 +795,6 @@ class CVEHandler(xml.sax.handler.ContentHandler):
215 def cves(self):
216 return self.cve_list
217
218- def get_ignore_suggestion(self, text):
219- '''Try to find a reasonable suggestion for the user.'''
220- suggestion = ""
221-
222- # remove any whitespace
223- rev_text = text.strip()
224- # strip out the added mailing list stuff (locate_cves.py importing)
225- rev_text = re.sub(r'^ML-Date: .* ML-Subject: ', '', rev_text)
226- rev_text = re.sub(r'^(|Re: )CVE (r|R)equest: ', '', rev_text)
227-
228- first_sentence = re.split(r'\. ', rev_text)[0]
229-
230- # hunt for module/component
231- nouns = ["library", "templates?", "components?", "modules?", "[Pp]lug-?ins?", "extension", "application", "[Tt]hemes?"]
232- products = ["Joomla!", "Drupal", "WordPress", "TYPO3", "Mambo", "Android", "jQuery"]
233- match = re.search(r'(?: in t|^T)he (.*) (%s) (?:.* )?for (%s)' % ("|".join(nouns), "|".join(products)),
234- first_sentence)
235- preposition = "for"
236- if not match:
237- # try swapping order of product and noun in which case there is no preposition
238- match = re.search(r'(?: in t|^T)he (.*) (%s) (%s) (?:.* )' % ("|".join(products), "|".join(nouns)),
239- first_sentence)
240- preposition = ""
241- if match:
242- module = match.group(1)
243- noun = match.group(2)
244- product = match.group(3)
245- for marker in [" module", " ("]:
246- if marker in module:
247- module = module.split(marker)[0]
248- return " ".join(filter(lambda x: len(x) > 0, [module, noun, preposition, product]))
249-
250- # hunt for a description of a thing written in a language or for creating something else
251- match = re.search(r'(.*) is a (?:.*) (written in|for creating) (?:.*)', first_sentence)
252- if match:
253- return match.group(1)
254-
255- # hunt for a product on a platform
256- match = re.search(r'.* for (Windows|Linux|Mac OS|iOS|Android|iPhone|iPad|BlackBerry|Symbian)', first_sentence)
257- if match:
258- return match.group(0)
259-
260- # look for common pattern
261- match = re.search(r'A vulnerability(?:, which was)? classified as (?:.*),? was found in (.*)', first_sentence)
262- if match:
263- return match.group(1)
264-
265- # drop commas-extensions
266- if ',' in first_sentence:
267- first_sentence = re.split(r',', first_sentence)[0]
268- phrases = re.split(r' [io]n ', first_sentence)
269-
270- # default to the last phrase
271- suggestion = phrases[-1]
272- # move to earlier phrase if suggestion starts with "a"
273- if suggestion.startswith('a ') and len(phrases) > 1:
274- suggestion = phrases[-2]
275-
276- version_preps = r'(\s+(before|through|prior to|versions?|[<>]=?))+\s*'
277- version_regex = r'\s+([a-zA-Z\._\-]*[0-9]+[a-zA-Z\._\-]*)+'
278- # prefer 'Apple iOS before <version>' or 'Apple Mac OS X through
279- # <version' in the last phrase over other suggestions
280- if not re.search(r'' + version_preps + version_regex, suggestion):
281- # grab the first phrase with something that may be a version number
282- for p in phrases:
283- if re.search(r'' + version_regex, p):
284- suggestion = p
285- break
286- if re.search(r'^[^,]+\s+for\s+', p):
287- suggestion = p
288- break
289-
290- # try to find a good suggestion from the phrase (ie suggest 'Linux
291- # kernel' from 'the Linux kernel before 2.6.27')
292- suggestion = re.split(r'\s+([a-zA-Z\._\-]*[0-9]+[a-zA-Z\._\-]*)+', suggestion)[0]
293- # "blah in component for Software"
294- if re.search(r'^[^,]+\s+for\s+', suggestion):
295- suggestion = re.split(r'[^,]+\s+for\s+', suggestion)[1]
296-
297- # Chop off action verbs
298- cleanup_regexes = [
299- # clean up leading "in" or "the"
300- r'^\s*([tT]he|[iI]n)\s+',
301- # clean up trailing version prepositions like "before" or "through"
302- # from version details
303- r'' + version_preps + '$',
304- # clean up trailing parens
305- r'\s+\([^\)]+\)\s*$',
306- # action verbs
307- r'\s+(has|creates|allows|could|contains)($|\s+.*)',
308- # "vulnerbale installations of"
309- r'(^|\s+)vulnerable\sinstallations?\sof($|\s+)',
310- # This affects all versions of package
311- r'^This affects all versions of package\s+',
312- # This affects the package
313- r'^This affects the package\s+',
314- ]
315-
316- for regex in cleanup_regexes:
317- if re.search(regex, suggestion):
318- suggestion = re.sub(regex, '', suggestion)
319-
320- # if the phrase is too long, truncate it to max_length, but make
321- # sure we don't have a partial word at the end
322- max_length = 64
323- if len(suggestion) > max_length:
324- suggestion = suggestion[:max_length]
325- suggestion = re.sub(r'\s+\w+$', '', suggestion)
326-
327- return suggestion
328-
329 def display_command_file_usage(self, f, line_prefix=''):
330 f.write('%sThe following commands can be used in this file:\n' % (line_prefix))
331 f.write('%s\n' % (line_prefix))
332@@ -1000,13 +892,13 @@ class CVEHandler(xml.sax.handler.ContentHandler):
333 # no debian info, display possible commented ignore command when
334 # using command file (i.e. wrap_desc is true)
335 if (self.debian[cve]['state'] == 'RESERVED' or self.debian[cve]['state'] == None) and wrap_desc:
336- proposed_ignore = self.get_ignore_suggestion(self.cve_data[cve]['desc'])
337+ proposed_ignore = self.ignore_suggestion.get_ignore_suggestion(self.cve_data[cve]['desc'])
338 print('%s ignore "%s"' % (cve, proposed_ignore))
339 # debian rejected, so offer to reject by ignoring when using command file (i.e. wrap_desc is true)
340 if (self.debian[cve]['state'] == 'REJECTED' and wrap_desc):
341 print('%s ignore "%s"' % (cve, "REJECTED"))
342 else:
343- proposed_ignore = self.get_ignore_suggestion(self.cve_data[cve]['desc'])
344+ proposed_ignore = self.ignore_suggestion.get_ignore_suggestion(self.cve_data[cve]['desc'])
345 print('%s ignore %s' % (cve, proposed_ignore))
346
347 software_hints_from_cve_desc = self.get_software_hints_from_cve_description(self.cve_data[cve]["desc"])
348@@ -1096,7 +988,7 @@ class CVEHandler(xml.sax.handler.ContentHandler):
349 return (action, reason, packages)
350
351 def get_software_hints_from_cve_description(self, cve_description):
352- desc = self.get_ignore_suggestion(cve_description)
353+ desc = self.ignore_suggestion.get_ignore_suggestion(cve_description)
354 # split based on brackets and other chars which aren't going to be
355 # helpful in matching a package name etc
356 words = set(filter(None, re.split("[\\s\\(\\){}\\[\\]!\\?\\\\/]+", desc.lower())))
357@@ -1166,7 +1058,7 @@ class CVEHandler(xml.sax.handler.ContentHandler):
358 # Show debian reason first, then automatically determined
359 # reason, then saved reasons. This makes more sense
360 # than sorting the reasons and is more predictable
361- choices = [reason, self.get_ignore_suggestion(self.cve_data[cve]['desc'])]
362+ choices = [reason, self.ignore_suggestion.get_ignore_suggestion(self.cve_data[cve]['desc'])]
363 choices.extend(self.saved_ignore_cache.get())
364 for choice in choices:
365 if choice != "" and choice not in prompts:
366@@ -1478,113 +1370,7 @@ class CVEHandler(xml.sax.handler.ContentHandler):
367 def skip_cve(self):
368 self.num_skipped += 1
369
370-class CheckCVETest(unittest.TestCase):
371- def test_get_ignore_suggestion(self):
372- '''"Ignore" suggestion text extraction'''
373-
374- # Re-use the global handler
375- h = handler
376-
377- self.assertEqual("Courier-Authlib", h.get_ignore_suggestion('''SQL injection vulnerability in authpgsqllib.c in Courier-Authlib before 0.62.0, when a non-Latin locale Postgres database is used, allows remote attackers to execute arbitrary SQL commands via query parameters containing apostrophes.'''))
378-
379- self.assertEqual("Apple Mac OS X", h.get_ignore_suggestion('''Buffer overflow in the DirectoryService Proxy in DirectoryService in Apple Mac OS X through 10.6.8 allows remote attackers to execute arbitrary code or cause a denial of service (application crash) via unspecified vectors.'''))
380-
381- self.assertEqual("KDE", h.get_ignore_suggestion('''HTMLTokenizer::scriptHandler in Konqueror in KDE 3.5.9 and 3.5.10 allows remote attackers to cause a denial of service (application crash) via an invalid document.load call that triggers use of a deleted object. NOTE: some of these details are obtained from third party information.'''))
382-
383- self.assertEqual("Sun Solaris", h.get_ignore_suggestion('''The name service cache daemon (nscd) in Sun Solaris 10 and OpenSolaris snv_50 through snv_104 does not properly check permissions, which allows local users to gain privileges and obtain sensitive information via unspecified vectors.'''))
384-
385- self.assertEqual("Linux kernel", h.get_ignore_suggestion('''libata in the Linux kernel before 2.6.27.9 does not set minimum timeouts for SG_IO requests, which allows local users to cause a denial of service (Programmed I/O mode on drives) via multiple simultaneous invocations of an unspecified test program.'''))
386-
387- self.assertEqual("iGaming", h.get_ignore_suggestion('''Multiple SQL injection vulnerabilities in iGaming 1.5 and earlier allow remote attackers to execute arbitrary SQL commands via the browse parameter to (1) previews.php and (2) reviews.php, and the (3) id parameter to index.php in a viewarticle action.'''))
388-
389- self.assertEqual("PHP iCalendar", h.get_ignore_suggestion('''PHP iCalendar 2.24 and earlier allows remote attackers to bypass authentication by setting the phpicalendar and phpicalendar_login cookies to 1.'
390-'''))
391-
392- # Test length truncation, tweaked to avoid "has" matcher
393- self.assertEqual("** TEST CVE ** This candidate HAS been reserved by an", h.get_ignore_suggestion('''** TEST CVE ** This candidate HAS been reserved by an organization or individual that will use it when announcing a new security problem. When the candidate has been publicized, the details for this candidate will be provided.'''))
394-
395- self.assertEqual("Sun OpenSolaris", h.get_ignore_suggestion('''Unspecified vulnerability in the root/boot archive tool in Sun OpenSolaris has unknown impact and local attack vectors, related to a "Temporary file vulnerability," aka Bug ID 6653455.'''))
396-
397- self.assertEqual("Red Hat Certificate System", h.get_ignore_suggestion('''Red Hat Certificate System 7.2 uses world-readable permissions for password.conf and unspecified other configuration files, which allows local users to discover passwords by reading these files.'''))
398-
399- self.assertEqual("Microsoft Internet Explorer", h.get_ignore_suggestion('''An unspecified function in the JavaScript implementation in Microsoft Internet Explorer creates and exposes a "temporary footprint" when there is a current login to a web site, which makes it easier for remote attackers to trick a user into acting upon a spoofed pop-up message, aka an "in-session phishing attack." NOTE: as of 20090116, the only disclosure is a vague pre-advisory with no actionable information. However, because it is from a well-known researcher, it is being assigned a CVE identifier for tracking purposes.'''))
400-
401- self.assertEqual("Umer Inc Songs Portal", h.get_ignore_suggestion('''SQL injection vulnerability in albums.php in Umer Inc Songs Portal allows remote attackers to execute arbitrary SQL commands via the id parameter.'''))
402-
403- self.assertEqual("Limbo CMS", h.get_ignore_suggestion('''SQL injection vulnerability in open.php in the Private Messaging (com_privmsg) component for Limbo CMS allows remote attackers to execute arbitrary SQL commands via the id parameter in a pms action to index.php.'''))
404-
405- self.assertEqual("phpscripts Ranking Script", h.get_ignore_suggestion('''phpscripts Ranking Script allows remote attackers to bypass authentication and gain administrative access by sending an admin=ja cookie.'''))
406-
407- self.assertEqual("A4Desk Event Calendar", h.get_ignore_suggestion('''PHP remote file inclusion vulnerability in index.php in A4Desk Event Calendar, when magic_quotes_gpc is disabled, allows remote attackers to execute arbitrary PHP code via a URL in the v parameter.'''))
408-
409- self.assertEqual("Galatolo WebManager", h.get_ignore_suggestion('''Cross-site scripting (XSS) vulnerability in result.php in Galatolo WebManager (GWM) 1.0 allows remote attackers to inject arbitrary web script or HTML via the key parameter.'''))
410-
411- self.assertEqual("Sun OpenSolaris", h.get_ignore_suggestion('''Unspecified vulnerability in the process (aka proc) filesystem in Sun OpenSolaris snv_85 through snv_100 allows local users to gain privileges via vectors related to the contract filesystem.'''))
412-
413- self.assertEqual("Dreampics Gallery Builder", h.get_ignore_suggestion('''SQL injection vulnerability in index.php in Dreampics Gallery Builder allows remote attackers to execute arbitrary SQL commands via the exhibition_id parameter in a gallery.viewPhotos action.'''))
414-
415- self.assertEqual("Omilen Photo Gallery component for Joomla!", h.get_ignore_suggestion('''Directory traversal vulnerability in the Omilen Photo Gallery (com_omphotogallery) component Beta 0.5 for Joomla! allows remote attackers to include and execute arbitrary local files via directory traversal sequences in the controller parameter to index.php.'''))
416-
417- self.assertEqual("Webform module for Drupal", h.get_ignore_suggestion('''Cross-site scripting (XSS) vulnerability in the Webform module 5.x before 5.x-2.7 and 6.x before 6.x-2.7, a module for Drupal, allows remote attackers to inject arbitrary web script or HTML via a submission.'''))
418-
419- self.assertEqual("Itamar Elharar MusicGallery component for Joomla!", h.get_ignore_suggestion('''SQL injection vulnerability in the Itamar Elharar MusicGallery (com_musicgallery) component for Joomla! allows remote attackers to execute arbitrary SQL commands via the id parameter in an itempage action to index.php. NOTE: the provenance of this information is unknown; the details are obtained solely from third party information.'''))
420-
421- self.assertEqual("Kide Shoutbox component for Joomla!", h.get_ignore_suggestion('''The Kide Shoutbox (com_kide) component 0.4.6 for Joomla! does not properly perform authentication, which allows remote attackers to post messages with an arbitrary account name via an insertar action to index.php. NOTE: the provenance of this information is unknown; the details are obtained solely from third party information.'''))
422
423- self.assertEqual("WP-Forum plugin for WordPress", h.get_ignore_suggestion('''Multiple SQL injection vulnerabilities in the WP-Forum plugin before 2.4 for WordPress allow remote attackers to execute arbitrary SQL commands via (1) the search_max parameter in a search action to the default URI, related to wpf.class.php; (2) the forum parameter to an unspecified component, related to wpf.class.php; (3) the topic parameter in a viewforum action to the default URI, related to the remove_topic function in wpf.class.php; or the id parameter in a (4) editpost or (5) viewtopic action to the default URI, related to wpf-post.php.'''))
424-
425- self.assertEqual("ListMan extension for TYPO3", h.get_ignore_suggestion('''Cross-site scripting (XSS) vulnerability in the ListMan (nl_listman) extension 1.2.1 for TYPO3 allows remote attackers to inject arbitrary web script or HTML via unspecified vectors.'''))
426-
427- self.assertEqual("multiple status.net issues", h.get_ignore_suggestion('''ML-Date: 2011-01-25 12:08:05, ML-Subject: Re: CVE request: multiple status.net issues'''))
428- self.assertEqual("multiple status.net issues", h.get_ignore_suggestion('''ML-Date: 2011-01-25 12:08:05, ML-Subject: CVE request: multiple status.net issues'''))
429- self.assertEqual("multiple status.net issues", h.get_ignore_suggestion('''ML-Date: 2011-01-25 12:08:05, ML-Subject: multiple status.net issues'''))
430- self.assertEqual("Mambo CMS", h.get_ignore_suggestion('''ML-Date: 2011-06-28 16:24:28, ML-Subject: Re: CVE Request: Mambo CMS 4.6.x | Multiple Cross Site Scripting Vulnerabilities'''))
431-
432- self.assertEqual("Apple iOS", h.get_ignore_suggestion('''The DNAv4 protocol implementation in the DHCP component in Apple iOS before 6 sends Wi-Fi packets containing a MAC address of a host on a previously used network, which might allow remote attackers to obtain sensitive information about previous device locations by sniffing an unencrypted Wi-Fi network for these packets.'''))
433-
434- self.assertEqual("Conceptronic", h.get_ignore_suggestion('''Multiple open redirect vulnerabilities on the Conceptronic C54APM access point with runtime code 1.26 allow remote attackers to redirect users to arbitrary web sites and conduct phishing attacks via (1) the submit-url parameter in a Refresh action to goform/formWlSiteSurvey or (2) the wlan-url parameter to goform/formWlanSetup.'''))
435-
436- self.assertEqual("Tapjoy library for Android", h.get_ignore_suggestion('''The Tapjoy library for Android does not verify X.509 certificates from SSL servers, which allows man-in-the-middle attackers to spoof servers and obtain sensitive information via a crafted certificate.'''))
437-
438- self.assertEqual("kamkomesan application for Android", h.get_ignore_suggestion('''The kamkomesan (aka com.anek.kamkomesan) application 1.0 for Android does not verify X.509 certificates from SSL servers, which allows man-in-the-middle attackers to spoof servers and obtain sensitive information via a crafted certificate.'''))
439-
440- self.assertEqual("DataTables plugin for jQuery", h.get_ignore_suggestion('''Cross-site scripting (XSS) vulnerability in the DataTables plugin 1.10.8 and earlier for jQuery allows remote attackers to inject arbitrary web script or HTML via the scripts parameter to media/unit_testing/templates/6776.php.'''))
441-
442- self.assertEqual("Cisco Application Policy Infrastructure Controller (APIC)", h.get_ignore_suggestion('''A vulnerability in Cisco Application Policy Infrastructure Controller (APIC) could allow an authenticated, remote attacker to gain higher privileges than the account is assigned.'''))
443-
444- self.assertEqual("Redirection", h.get_ignore_suggestion('''Redirection version 2.7.3 contains a ACE via file inclusion vulnerability in Pass-through mode that can result in allows admins to execute any PHP file in the filesystem.'''))
445-
446- self.assertEqual("topydo", h.get_ignore_suggestion('''topydo contains a CWE-20: Improper Input Validation vulnerability in ListFormatParser::parse, file topydo/lib/ListFormat.py line 292 as of d4f843dac71308b2f29a7c2cdc76f055c3841523 that can result in Injection of arbitrary bytes to the terminal,'''))
447-
448- self.assertEqual("Joplin", h.get_ignore_suggestion('''Joplin version prior to 1.0.90 contains a XSS evolving into code execution due to enabled nodeIntegration.'''))
449-
450- self.assertEqual("Foxit Reader", h.get_ignore_suggestion('''This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Foxit Reader 9.2.0.9297.'''))
451-
452- self.assertEqual("Exquisite Ultimate Newspaper theme for WordPress", h.get_ignore_suggestion('''The Exquisite Ultimate Newspaper theme 1.3.3 for WordPress has XSS via the anchor identifier to assets/js/jquery.foundation.plugins.js.'''))
453- self.assertEqual("Weaver Xtreme Theme for WordPress", h.get_ignore_suggestion('''The Weaver Xtreme Theme for WordPress is vulnerable to stored Cross-Site Scripting due to insufficient escaping of the profile display name in versions up to, and including, 5.0.7.'''))
454- self.assertEqual("Weaver Show Posts Plugin for WordPress", h.get_ignore_suggestion('''The Weaver Show Posts Plugin for WordPress is vulnerable to stored Cross-Site Scripting due to insufficient escaping of the profile display name in versions up to, and including, 1.6.'''))
455- self.assertEqual("WordPress File Upload and WordPress File Upload Pro plugins for WordPress", h.get_ignore_suggestion('''The WordPress File Upload and WordPress File Upload Pro plugins for WordPress are vulnerable to Path Traversal in versions up to, and including, 4.19.1 via the vulnerable parameter wfu_newpath.'''))
456- self.assertEqual("browserless-chrome", h.get_ignore_suggestion('''This affects all versions of package browserless-chrome. User input flowing from the workspace endpoint gets used to create a file path filePath and this is fetched and then sent back to a user. This can be escaped to fetch arbitrary files from a server.'''))
457- self.assertEqual("@absolunet/kafe", h.get_ignore_suggestion('''This affects the package @absolunet/kafe before 3.2.10. It allows cause a denial of service when validating crafted invalid emails.'''))
458- self.assertEqual("Magneticlab Sàrl Homepage Pop-up plugin", h.get_ignore_suggestion('''Cross-Site Request Forgery (CSRF) vulnerability in Magneticlab Sàrl Homepage Pop-up plugin <= 1.2.5 versions.'''))
459-
460- # handle odd whitespace
461- self.assertEqual("PTC Vuforia Studio does not require a token.", h.get_ignore_suggestion('''
462-
463-
464-
465-
466- PTC Vuforia Studio does not require a token.
467- '''))
468- self.assertEqual("zxcvbn-ts", h.get_ignore_suggestion('''zxcvbn-ts is a password strength estimator written in typescript.'''))
469- self.assertEqual("VMware Tools for Windows", h.get_ignore_suggestion('''VMware Tools for Windows (10.x.y prior to 10.3.10, 11.x.y prior to 11.1.5) contains a denial-of-service vulnerability due to NULL pointer dereference in vm3dmp driver. Successful exploitation of this issue may allow attackers with normal user privileges to create a denial-of-service condition on the guest machine where VMware Tools is installed.'''))
470-
471- self.assertEqual("3DPrint WordPress plugin", h.get_ignore_suggestion("The 3DPrint WordPress plugin before 3.5.6.9 does not protect against CSRF attacks in the modified version of Tiny File Manager included with the plugin, allowing an attacker to craft a malicious request that will create an archive of any files or directories on the target server by tricking a logged in admin into submitting a form."))
472-
473- self.assertEqual("Feathersjs", h.get_ignore_suggestion('''Feathersjs is a framework for creating web APIs and real-time applications with TypeScript or JavaScript.'''))
474-
475- self.assertEqual("PaulPrinting CMS 2018", h.get_ignore_suggestion("A vulnerability, which was classified as problematic, was found in PaulPrinting CMS 2018"))
476-
477 ignored_notforus_path = 'ignored/not-for-us.txt'
478 if destdir != './' and destdir != '.':
479 ignored_notforus_path = os.path.join(destdir, ignored_notforus_path)
480@@ -1625,12 +1411,6 @@ parser = xml.sax.make_parser()
481 handler = CVEHandler(CVEIgnoreList)
482 parser.setContentHandler(handler)
483
484-if opt.test:
485- suite = unittest.TestSuite()
486- suite.addTest(unittest.TestLoader().loadTestsFromTestCase(CheckCVETest))
487- unittest.TextTestRunner(verbosity=2).run(suite)
488- sys.exit(0)
489-
490 # if has specified to triage only specific CVEs, check these are not
491 # ignored
492 specific_cves = None
493diff --git a/scripts/check-unreplaced-templates.py b/scripts/check-unreplaced-templates.py
494index 4723688..3e438d9 100755
495--- a/scripts/check-unreplaced-templates.py
496+++ b/scripts/check-unreplaced-templates.py
497@@ -16,6 +16,7 @@ import cve_lib
498 import json
499 import optparse
500 import usn_lib
501+from uct.config import read_uct_config
502
503 parser = optparse.OptionParser()
504 parser.add_option("-d", "--debug", help="Report additional debugging while loading USNs", action='store_true')
505@@ -26,7 +27,7 @@ parser.add_option("-j", "--json", help="Check json instead of default pickle dat
506
507 cves = dict()
508
509-config = cve_lib.read_config()
510+config = read_uct_config()
511
512 changed = False
513 if opt.json:
514diff --git a/scripts/cve_lib.py b/scripts/cve_lib.py
515index a3104fa..e982b55 100755
516--- a/scripts/cve_lib.py
517+++ b/scripts/cve_lib.py
518@@ -24,6 +24,7 @@ import json
519 import yaml
520 import urllib.error
521 import urllib.request
522+from uct.config import read_uct_config
523
524 from functools import reduce
525 from functools import lru_cache
526@@ -1698,24 +1699,16 @@ def read_config_file(config_file):
527
528 return ConfigObj(config_file)
529
530-def read_config():
531- config_file = os.path.join(os.path.expanduser("~"), ".ubuntu-cve-tracker.conf")
532-
533- if not os.path.exists(config_file):
534- raise ValueError("Could not find '%s'" % (config_file))
535
536- # FIXME: Why does this need to be defined as "global" when other globals
537- # like "releases" and "EXIT_OKAY" don't need it??
538+# this function has been deprecated in favor of
539+# uct.config.read_uct_config() and callers should be converted to use
540+# that directly
541+def read_config():
542 global config
543- config = read_config_file(config_file)
544-
545- # Validate required arguments
546- if "plb_authentication" not in config:
547- raise ValueError(("Could not find 'plb_authentication' entry in %s." % (config_file)))
548- if not os.path.exists(config["plb_authentication"]):
549- raise ValueError(("Could not find file specified by 'plb_authentication' in %s." % (config_file)))
550+ config = read_uct_config()
551 return config
552
553+
554 def drop_dup_release(cve, rel):
555 output = codecs.open(cve + ".new", 'w', encoding="utf-8")
556 saw = set()
557diff --git a/scripts/debian-cve-update b/scripts/debian-cve-update
558index 07c1bea..37856fa 100755
559--- a/scripts/debian-cve-update
560+++ b/scripts/debian-cve-update
561@@ -10,12 +10,13 @@ from __future__ import print_function
562
563 from cve_lib import (
564 check_mirror_timestamp, get_cve_list, load_debian_cves,
565- load_ignored_reasons, prepend_debian_cve, read_config,
566+ load_ignored_reasons, prepend_debian_cve,
567 update_debian_todo_cves,
568 )
569 import source_map
570 import optparse
571 import os
572+from uct.config import read_uct_config
573
574 parser = optparse.OptionParser()
575 parser.add_option("-p", "--packages", help="Include known packages in the updates", action='store_true')
576@@ -23,7 +24,7 @@ parser.add_option("-n", "--dry-run", help="Do not actually make changes", action
577 parser.add_option("-q", "--quiet", help="Report actions verbosely", dest="verbose", default=True, action='store_false')
578 (opt, args) = parser.parse_args()
579
580-config = read_config()
581+config = read_uct_config()
582 if len(args) == 1:
583 debian_cve_list = args[0]
584 else:
585diff --git a/scripts/dump-features b/scripts/dump-features
586index 30ca715..5dccc03 100755
587--- a/scripts/dump-features
588+++ b/scripts/dump-features
589@@ -29,8 +29,6 @@ if opt.editmoin:
590 else:
591 sys.exit(1)
592
593-config = cve_lib.read_config()
594-
595 features = dict()
596
597 UNIMPLEMENTED = 0
598diff --git a/scripts/gen-source-map-cache b/scripts/gen-source-map-cache
599index 1c84cc6..d6fd4cf 100755
600--- a/scripts/gen-source-map-cache
601+++ b/scripts/gen-source-map-cache
602@@ -23,10 +23,11 @@ if not "UCT" in os.environ and not os.path.islink('subprojects'):
603
604 import cve_lib
605 import source_map
606+from uct.config import read_uct_config
607
608 def main():
609
610- config = source_map.read_config_file(os.path.expanduser("~/.ubuntu-cve-tracker.conf"))
611+ config = read_uct_config()
612 if 'packages_mirror' not in config:
613 sys.stderr.write("\rERROR: Could not find packages_mirror in config file!\n")
614 sys.exit(1)
615diff --git a/scripts/generate-graphs.py b/scripts/generate-graphs.py
616index e699f04..cceb55c 100755
617--- a/scripts/generate-graphs.py
618+++ b/scripts/generate-graphs.py
619@@ -61,8 +61,6 @@ if opt.target == None:
620 print("Must specify --target", file=sys.stderr)
621 sys.exit(1)
622
623-config = cve_lib.read_config()
624-
625 if not os.path.exists(opt.database):
626 print("Cannot find '%s'" % opt.database, file=sys.stderr)
627 sys.exit(1)
628diff --git a/scripts/plot-usns.py b/scripts/plot-usns.py
629index b6e8e2b..527bb22 100755
630--- a/scripts/plot-usns.py
631+++ b/scripts/plot-usns.py
632@@ -8,6 +8,7 @@
633 # License: GPLv3
634 import sys, time, usn_lib, cve_lib
635 import optparse
636+from uct.config import read_uct_config
637
638 import pprint
639 pp = pprint.PrettyPrinter(indent=4)
640@@ -23,7 +24,7 @@ if opt.target not in ['usn','src','bin','cve']:
641 print("Unknown target '%s'" % (opt.target), file=sys.stderr)
642 sys.exit(1)
643
644-config = cve_lib.read_config()
645+config = read_uct_config()
646 db = None
647 db_filename = config['usn_db_copy']
648 if len(args) > 0:
649@@ -33,9 +34,6 @@ db = usn_lib.load_database(db_filename)
650 columns = ['total', 'untriaged'] + cve_lib.priorities
651
652 cves = dict()
653-if opt.target == 'cve':
654- cve_lib.read_config()
655-
656 months = dict()
657 month_cves = dict()
658 for usn in sorted(db.keys()):
659diff --git a/scripts/process_cves b/scripts/process_cves
660index 97d15cc..334ae9b 100755
661--- a/scripts/process_cves
662+++ b/scripts/process_cves
663@@ -11,6 +11,7 @@
664 set -e
665
666 loc=$(readlink -f "$(dirname "$0")/..")
667+cache_dir="${loc}/cache/"
668
669 uctconf="$HOME/.ubuntu-cve-tracker.conf"
670 if [ -s "$uctconf" ]; then
671@@ -26,6 +27,8 @@ is_http_url() {
672 }
673
674 download() {
675+ local url
676+
677 url="$1"
678 # if http, then download it with wget, otherwise, try to rsync it
679 if is_http_url "$url" ; then
680@@ -35,11 +38,11 @@ download() {
681 file=$(basename "$url")
682
683 echo "wget -N \"$url\""
684- wget -N "$url" ||:
685- gunzip -f "$file"
686+ wget -N "$url" -P "${cache_dir}" ||:
687+ gunzip -f "${cache_dir}/$file"
688 else
689- echo "rsync -avz --progress \"$url\" ."
690- rsync -avz --progress "$url" .
691+ echo "rsync -avz --progress \"$url\" ${cache_dir}"
692+ rsync -avz --progress "$url" "${cache_dir}"
693 fi
694 echo "---"
695 }
696@@ -107,18 +110,18 @@ update_files() {
697
698 full_refresh() {
699 # get PubDate
700- echo "update PubDate (check-cves --refresh nvdcve-*.json) ..."
701- ./scripts/check-cves --refresh nvdcve-*.json
702+ echo "update PubDate (check-cves --refresh ${cache_dir}/nvdcve-*.json) ..."
703+ ./scripts/check-cves --refresh "${cache_dir}"/nvdcve-*.json
704
705 # get authoritative descriptions
706- echo "get authoritative descriptions (check-cves --refresh ./allitems.xml) ..."
707- ./scripts/check-cves --refresh ./allitems.xml
708+ echo "get authoritative descriptions (check-cves --refresh ${cache_dir}/allitems.xml) ..."
709+ ./scripts/check-cves --refresh "${cache_dir}/allitems.xml"
710 }
711
712 refresh_cvss_score() {
713 # get CVSS score from NVD
714- echo "update CVSS score (check-cves --score-refresh nvdcve-*.json) ..."
715- ./scripts/check-cves --score-refresh nvdcve-*.json
716+ echo "update CVSS score (check-cves --score-refresh ${cache_dir}/nvdcve-*.json) ..."
717+ ./scripts/check-cves --score-refresh "${cache_dir}"/nvdcve-*.json
718 }
719
720 kernel_team_merge() {
721@@ -303,7 +306,7 @@ case "$action" in
722 # kernel_team_merge
723 echo "Skipping merge from kernel team"
724 echo "check-cves nvdcve-*.json"
725- ./scripts/check-cves nvdcve-*.json
726+ ./scripts/check-cves "${cache_dir}"/nvdcve-*.json
727 # this will also do missing debian
728 mistriaged
729
730@@ -321,10 +324,10 @@ case "$action" in
731 echo "Skipping merge from kernel team"
732
733 # process new ones
734- echo "check-cves ./nvdcve-1.1-recent.json"
735- ./scripts/check-cves ./nvdcve-1.1-recent.json
736- echo "check-cves ./allitems.xml"
737- ./scripts/check-cves ./allitems.xml
738+ echo "check-cves ${cache_dir}/nvdcve-1.1-recent.json"
739+ ./scripts/check-cves "${cache_dir}/nvdcve-1.1-recent.json"
740+ echo "check-cves ${cache_dir}/allitems.xml"
741+ ./scripts/check-cves "${cache_dir}/allitems.xml"
742
743 process_missing_debian
744 # Disabling this due to https://listman.redhat.com/archives/rhsa-announce/2023-October/012854.html
745@@ -396,7 +399,7 @@ case "$action" in
746 update_files
747 echo "check-cves --cve ${action}"
748 # add all NVD details as well so we can get date etc for it
749- ./scripts/check-cves --cve ${action} nvdcve-*.json
750+ ./scripts/check-cves --cve ${action} "${cache_dir}"/nvdcve-*.json
751 check_syntax
752 ;;
753 *.json)
754@@ -420,8 +423,8 @@ case "$action" in
755 update_files
756 # kernel_team_merge
757 echo "Skipping merge from kernel team"
758- echo "check-cves ./nvdcve-1.1-recent.json"
759- ./scripts/check-cves ./nvdcve-1.1-recent.json
760+ echo "check-cves ${cache_dir}/nvdcve-1.1-recent.json"
761+ ./scripts/check-cves "${cache_dir}/nvdcve-1.1-recent.json"
762 process_missing_debian
763 refresh_cvss_score
764 check_syntax
765diff --git a/scripts/pull-in-progress.py b/scripts/pull-in-progress.py
766index a4a57d9..0426e74 100755
767--- a/scripts/pull-in-progress.py
768+++ b/scripts/pull-in-progress.py
769@@ -7,6 +7,7 @@
770 import sys, os, os.path, re, urllib, tempfile
771 import optparse, glob
772 import cve_lib
773+from utc.config import read_uct_config
774
775 from launchpadlib.launchpad import Launchpad, EDGE_SERVICE_ROOT
776 from launchpadlib.credentials import Credentials
777@@ -57,8 +58,8 @@ def get_cves(bug):
778 #
779 from launchpadbugs.connector import ConnectBugList
780 BugList = ConnectBugList()
781-cve_lib.read_config()
782-BugList.authentication = cve_lib.config["plb_authentication"]
783+config = read_uct_config()
784+BugList.authentication = config["plb_authentication"]
785
786 url="https://bugs.launchpad.net/~ubuntu-security/+bugs?field.searchtext=&orderby=-importance&field.status%3Alist=INPROGRESS&assignee_option=any&field.assignee=&field.bug_reporter=&field.bug_supervisor=&field.bug_commenter=&field.subscriber=&field.status_upstream-empty-marker=1&field.omit_dupes.used=&field.has_patch.used=&field.has_patch=on&field.has_cve.used=&field.tag=&field.tags_combinator=ANY&search=Search"
787 if opt.any:
788diff --git a/scripts/report-bugs b/scripts/report-bugs
789index bb2d78c..de6684a 100755
790--- a/scripts/report-bugs
791+++ b/scripts/report-bugs
792@@ -14,6 +14,7 @@ import re
793 import subprocess
794 import sys
795 import time
796+from uct.config import read_uct_config
797
798 parser = optparse.OptionParser()
799 parser.add_option("--debug", help="Verbose processing output", action='store_true')
800@@ -43,14 +44,14 @@ except:
801 sys.exit(1)
802
803 # Load configuration
804-cve_lib.read_config()
805+config = read_uct_config()
806
807 # API interface
808 debug("Connecting to LP ...\n")
809 lp = lpl_common.connect()
810
811 # Get authenticated URL fetcher
812-opener = lpl_common.opener_with_cookie(cve_lib.config["plb_authentication"])
813+opener = lpl_common.opener_with_cookie(config["plb_authentication"])
814 if not opener:
815 raise ValueError("Could not open cookies")
816
817diff --git a/scripts/report-bugs-by-assignee b/scripts/report-bugs-by-assignee
818index e34ba4b..2fc5d5e 100755
819--- a/scripts/report-bugs-by-assignee
820+++ b/scripts/report-bugs-by-assignee
821@@ -30,9 +30,6 @@ def debug(s):
822 if opt.debug:
823 print( "DEBUG: %s" % s, file=sys.stderr)
824
825-# Load configuration
826-cve_lib.read_config()
827-
828 # API interface
829 debug("Connecting to LP ...")
830 lp = UCTLaunchpad(opt)
831diff --git a/scripts/report-bugs-by-team b/scripts/report-bugs-by-team
832index e3b71ec..da7ae74 100755
833--- a/scripts/report-bugs-by-team
834+++ b/scripts/report-bugs-by-team
835@@ -19,6 +19,7 @@ import shutil
836 import subprocess
837 import sys
838 import time
839+from uct.config import read_uct_config
840
841 try:
842 import pickle
843@@ -377,7 +378,7 @@ if all_bugs == None:
844 sys.exit(1)
845
846 # Load configuration
847- cve_lib.read_config()
848+ config = read_uct_config()
849
850 # API interface
851 debug("Team: %s" % opt.team)
852@@ -385,7 +386,7 @@ if all_bugs == None:
853 lp = lpl_common.connect()
854
855 # Get authenticated URL fetcher
856- opener = lpl_common.opener_with_cookie(cve_lib.config["plb_authentication"])
857+ opener = lpl_common.opener_with_cookie(config["plb_authentication"])
858 if not opener:
859 raise ValueError("Could not open cookies")
860
861diff --git a/scripts/report-bugs-for-eol b/scripts/report-bugs-for-eol
862index 2cc2895..81e3397 100755
863--- a/scripts/report-bugs-for-eol
864+++ b/scripts/report-bugs-for-eol
865@@ -17,6 +17,7 @@ import optparse
866 import os
867 import re
868 import sys
869+from uct.config import read_uct_config
870
871 parser = optparse.OptionParser()
872 parser.add_option("--debug", help="Verbose processing output", action='store_true')
873@@ -138,14 +139,14 @@ if bugs == None:
874 sys.exit(1)
875
876 # Load configuration
877- cve_lib.read_config()
878+ config = read_uct_config()
879
880 # API interface
881 print("Connecting to LP ...", end=' ', file=sys.stderr)
882 lp = lpl_common.connect()
883
884 # Get authenticated URL fetcher
885- opener = lpl_common.opener_with_cookie(cve_lib.config["plb_authentication"])
886+ opener = lpl_common.opener_with_cookie(config["plb_authentication"])
887 if not opener:
888 raise ValueError("Could not open cookies")
889
890diff --git a/scripts/report-cve-age.py b/scripts/report-cve-age.py
891index 26c2925..7d28820 100755
892--- a/scripts/report-cve-age.py
893+++ b/scripts/report-cve-age.py
894@@ -18,11 +18,12 @@ import os
895 import sys
896 import optparse
897 import cve_lib, usn_lib
898+from uct.config import read_uct_config
899
900 import source_map
901 source_map = source_map.load()
902 releases = cve_lib.releases
903-config = cve_lib.read_config()
904+config = read_uct_config()
905
906 default_db = config['usn_db_copy']
907 if '-all' not in default_db:
908diff --git a/scripts/report-mistriaged-cves.py b/scripts/report-mistriaged-cves.py
909index 352a104..90e717f 100755
910--- a/scripts/report-mistriaged-cves.py
911+++ b/scripts/report-mistriaged-cves.py
912@@ -9,6 +9,7 @@
913 # Public License, Version 3 or later. See http://www.gnu.org/copyleft/gpl.html
914 # for details.
915 import sys, os, os.path, optparse, cve_lib, re, subprocess, signal
916+from uct.config import read_uct_config
917
918 def subprocess_setup():
919 # Python installs a SIGPIPE handler by default. This is usually not what
920@@ -63,7 +64,7 @@ def check_apt_cache(package):
921
922
923 # get base config for path to debian security tracker etc
924-config = cve_lib.read_config()
925+config = read_uct_config()
926
927 parser = optparse.OptionParser()
928 parser.add_option("-v", "--verbose", dest="verbose",
929@@ -139,7 +140,7 @@ if len(dups) > 0:
930 print('Duplicate entries in not-for-us.txt: ' + ' '.join(dups))
931
932 # get the list of CVEs which Debian knows about
933-debian = cve_lib.load_debian_cves(cve_lib.config['secure_testing_path'] +
934+debian = cve_lib.load_debian_cves(config['secure_testing_path'] +
935 '/data/CVE/list')
936
937 cache = {}
938diff --git a/scripts/report-packages.py b/scripts/report-packages.py
939index bcc3406..33eb8a8 100755
940--- a/scripts/report-packages.py
941+++ b/scripts/report-packages.py
942@@ -23,9 +23,10 @@ import cve_lib
943 import usn_lib
944
945 import source_map
946+from uct.config import read_uct_config
947 source_map = source_map.load()
948 releases = cve_lib.releases
949-config = cve_lib.read_config()
950+config = read_uct_config()
951
952 parser = optparse.OptionParser()
953 parser.add_option("-S", "--skip-devel", help="Show only those CVEs *not* in the current devel release", action="store_true")
954diff --git a/scripts/report-todo-mir b/scripts/report-todo-mir
955index d598fb6..e820a43 100755
956--- a/scripts/report-todo-mir
957+++ b/scripts/report-todo-mir
958@@ -14,7 +14,6 @@ import argparse
959 import re
960 import sys
961 from launchpadlib.launchpad import Launchpad
962-import cve_lib
963
964 # by default we want all not in Fix Released, Fix Committed, Opinion, Invalid,
965 # Won't Fix & Expired which leaves the following
966@@ -225,9 +224,6 @@ def lookup_tasks_for_assignees(taskdb, assignees):
967 taskdb.add(mir_task)
968
969
970-# Load configuration
971-cve_lib.read_config()
972-
973 # API interface
974 debug("Connecting to LP ...")
975 lp = Launchpad.login_anonymously("report-todo-mir", "production", version="devel")
976diff --git a/scripts/report-todo-sponsoring b/scripts/report-todo-sponsoring
977index 14c87d3..9294a40 100755
978--- a/scripts/report-todo-sponsoring
979+++ b/scripts/report-todo-sponsoring
980@@ -151,9 +151,6 @@ except:
981 print("lpl_common.py seems to be missing. Please create a symlink from $UQT/common/lpl_common.py to $UCT/scripts/", file=sys.stderr)
982 sys.exit(1)
983
984-# Load configuration
985-cve_lib.read_config()
986-
987 # API interface
988 debug("Connecting to LP ...")
989 lp = lpl_common.connect()
990diff --git a/scripts/report-updates.py b/scripts/report-updates.py
991index ed69604..4d939ca 100755
992--- a/scripts/report-updates.py
993+++ b/scripts/report-updates.py
994@@ -13,11 +13,12 @@ import os
995 import sys
996 import time
997 import usn_lib
998+from uct.config import read_uct_config
999
1000 import source_map
1001 source_map = source_map.load()
1002
1003-config = cve_lib.read_config()
1004+config = read_uct_config()
1005 default_db = config['usn_db_copy']
1006 if '-all' not in default_db:
1007 tmp = os.path.splitext(default_db)
1008diff --git a/scripts/report-version.py b/scripts/report-version.py
1009index 381c724..c1fdb7d 100755
1010--- a/scripts/report-version.py
1011+++ b/scripts/report-version.py
1012@@ -18,13 +18,14 @@ import sys
1013 import optparse
1014 import cve_lib
1015 import usn_lib
1016+from uct.config import read_uct_config
1017
1018 import apt_pkg
1019 apt_pkg.init_system()
1020
1021 releases = cve_lib.releases
1022 esm_releases = cve_lib.esm_releases + cve_lib.esm_infra_releases
1023-config = cve_lib.read_config()
1024+config = read_uct_config()
1025
1026 parser = optparse.OptionParser()
1027 parser.add_option("--db", help="Specify the USN database to load", metavar="FILENAME", default=config['usn_db_copy'])
1028diff --git a/scripts/sis-changes b/scripts/sis-changes
1029index d33da15..fc398f4 100755
1030--- a/scripts/sis-changes
1031+++ b/scripts/sis-changes
1032@@ -23,6 +23,7 @@ import urllib.request, urllib.error, urllib.parse
1033 import cve_lib
1034 import json
1035 from source_map import version_compare, load
1036+from uct.config import read_uct_config
1037
1038 try:
1039 import lpl_common
1040@@ -148,13 +149,13 @@ parser.add_option("--distribution", help="Distribution to use (eg, 'ubuntu-rtm')
1041 (opt, args) = parser.parse_args()
1042
1043 # Load configuration
1044-cve_lib.read_config()
1045+config = read_uct_config()
1046
1047 # API interface
1048 lp = lpl_common.connect(beta=opt.beta, uri=opt.uri)
1049
1050 # Get authenticated URL fetcher
1051-opener = lpl_common.opener_with_cookie(cve_lib.config["plb_authentication"])
1052+opener = lpl_common.opener_with_cookie(config["plb_authentication"])
1053 if not opener:
1054 raise ValueError("Could not open cookies")
1055
1056diff --git a/scripts/sis-generate-usn b/scripts/sis-generate-usn
1057index 8947779..9ae7a8b 100755
1058--- a/scripts/sis-generate-usn
1059+++ b/scripts/sis-generate-usn
1060@@ -23,6 +23,7 @@ import subprocess
1061 import sys
1062 import tempfile
1063 import cve_lib
1064+from uct.config import read_uct_config
1065
1066 opter = optparse.OptionParser()
1067 opter.add_option("--debug", help="Verbose processing output", action='store_true')
1068@@ -60,7 +61,7 @@ for rel in cve_lib.releases:
1069 # components (-updates has gotten wrecked by things in -proposed before).
1070 release_suffixes = ['','-security','-updates']
1071
1072-config = cve_lib.read_config()
1073+config = read_uct_config()
1074 archive = config['packages_mirror']
1075 cve_lib.check_mirror_timestamp(config, mirror='packages_mirror')
1076
1077diff --git a/scripts/source_map.py b/scripts/source_map.py
1078index c5ec9ad..b6377fb 100755
1079--- a/scripts/source_map.py
1080+++ b/scripts/source_map.py
1081@@ -19,6 +19,7 @@ import sys
1082 import cve_lib
1083 import yaml
1084 import pickle
1085+from uct.config import read_uct_config
1086
1087 built_using_tags = ["Built-Using", "Static-Built-Using", "X-Cargo-Built-Using"]
1088 apt_pkg.init_system()
1089@@ -27,28 +28,9 @@ apt_pkg.init_system()
1090 def version_compare(one, two):
1091 return apt_pkg.version_compare(one, two)
1092
1093-def read_config_file(config_file):
1094- '''Read in and do basic validation on config file'''
1095- try:
1096- from configobj import ConfigObj
1097- except ImportError:
1098- # Some releases lack this class, so reimplement it quickly
1099- class ConfigObj(dict):
1100- def __init__(self, filepath):
1101- for line in open(filepath).readlines():
1102- line = line.strip()
1103- if line.startswith('#') or len(line) == 0:
1104- continue
1105- name, stuff = line.strip().split('=', 1)
1106- self[name] = eval(stuff)
1107-
1108- def __attr__(self, name):
1109- return self.stuff[name]
1110-
1111- return ConfigObj(config_file)
1112
1113 def _find_sources(pockets=None, releases=None, skip_eol_releases=True, arch='amd64'):
1114- config = read_config_file(os.path.expanduser("~/.ubuntu-cve-tracker.conf"))
1115+ config = read_uct_config()
1116 if 'packages_mirror' in config:
1117 cve_lib.check_mirror_timestamp(config)
1118 return _find_from_mirror(config['packages_mirror'],
1119@@ -62,7 +44,7 @@ def _find_sources(pockets=None, releases=None, skip_eol_releases=True, arch='amd
1120
1121
1122 def _find_packages(pockets=None, releases=None, skip_eol_releases=True, arch='amd64'):
1123- config = read_config_file(os.path.expanduser("~/.ubuntu-cve-tracker.conf"))
1124+ config = read_uct_config()
1125 if 'packages_mirror' in config:
1126 cve_lib.check_mirror_timestamp(config)
1127 return _find_from_mirror(config['packages_mirror'],
1128@@ -218,7 +200,7 @@ def load(data_type='sources', pockets=None, releases=None, skip_eol_releases=Tru
1129 raise ValueError("'data_type' should be either 'sources' or 'packages'")
1130
1131 # Attempt to load from cached pickle files
1132- config = read_config_file(os.path.expanduser("~/.ubuntu-cve-tracker.conf"))
1133+ config = read_uct_config()
1134 if 'packages_mirror' in config:
1135 mirror = config['packages_mirror']
1136
1137diff --git a/scripts/sync-bugs-kernel.py b/scripts/sync-bugs-kernel.py
1138index 67b1bf9..21e1c32 100755
1139--- a/scripts/sync-bugs-kernel.py
1140+++ b/scripts/sync-bugs-kernel.py
1141@@ -29,6 +29,7 @@ import sys
1142 import cve_lib
1143 import urlparse
1144 from lp_lib import UCTLaunchpad
1145+from uct.config import read_uct_config
1146
1147 priority_to_importance = {
1148 'critical': 'Critical',
1149@@ -61,7 +62,7 @@ uctlp = UCTLaunchpad(opt)
1150 # Use devel version so we get the bugtask delete API
1151 uctlp.lp_version = "devel"
1152
1153-config = cve_lib.read_config()
1154+config = read_uct_config()
1155 ktools = config.get('kernel_team_tools_path', None)
1156 if not ktools:
1157 raise ValueError("'kernel_team_tools_path' missing in ~/.ubuntu-cve-tracker.conf")
1158diff --git a/scripts/sync-from-usns.py b/scripts/sync-from-usns.py
1159index c2a2a27..91a2ef3 100755
1160--- a/scripts/sync-from-usns.py
1161+++ b/scripts/sync-from-usns.py
1162@@ -24,6 +24,7 @@ import textwrap
1163
1164 import cve_lib
1165 import usn_lib
1166+from uct.config import read_uct_config
1167
1168 from source_map import version_compare, load
1169
1170@@ -96,7 +97,7 @@ def parse_args():
1171 return args
1172
1173 if __name__ == '__main__':
1174- config = cve_lib.read_config()
1175+ config = read_uct_config()
1176
1177 args = parse_args()
1178 if args.git_stage:
1179diff --git a/scripts/test_uct_config.py b/scripts/test_uct_config.py
1180new file mode 100755
1181index 0000000..c69fbda
1182--- /dev/null
1183+++ b/scripts/test_uct_config.py
1184@@ -0,0 +1,48 @@
1185+#!/usr/bin/env pytest-3
1186+# -*- coding: utf-8 -*-
1187+#
1188+# Test functions for scripts/uct/config.py
1189+#
1190+# Author: Steve Beattie <sbeattie@ubuntu.com>
1191+# Copyright (C) 2005-2024 Canonical Ltd.
1192+#
1193+# This script is distributed under the terms and conditions of the GNU General
1194+# Public License, Version 2 or later. See http://www.gnu.org/copyleft/gpl.html
1195+# for details.
1196+
1197+import os
1198+import pytest
1199+
1200+# _read_config and _read_config_cached are private functions in
1201+# uct/config.py for testing
1202+# users of uct/config.py should only use read_config()
1203+from uct.config import _read_config, _read_config_cached
1204+
1205+TEST_DATA_DIR = "test/uct/config/"
1206+
1207+
1208+class TestUCTConfig:
1209+
1210+ def test_config_file_not_exist(self):
1211+ with pytest.raises(ValueError):
1212+ _read_config(os.path.join(TEST_DATA_DIR, "does-not-exist.conf"))
1213+
1214+ def test_plb_auth_config_not_exist(self):
1215+ with pytest.raises(ValueError):
1216+ _read_config(os.path.join(TEST_DATA_DIR, "plb_auth_does_not_exist.conf"))
1217+
1218+ def test_no_plb_auth_entry(self):
1219+ with pytest.raises(ValueError):
1220+ _read_config(os.path.join(TEST_DATA_DIR, "no_plb_auth_entry.conf"))
1221+
1222+ def test_plb_auth_config_simple(self):
1223+ expected = {
1224+ 'plb_authentication': '/dev/null'
1225+ }
1226+ config = _read_config(os.path.join(TEST_DATA_DIR, "plb_auth_dev_null.conf"))
1227+ assert config == expected
1228+
1229+ def test_returned_config_is_singleton(self):
1230+ first_config = _read_config_cached(os.path.join(TEST_DATA_DIR, "uct.conf"))
1231+ second_config = _read_config_cached(os.path.join(TEST_DATA_DIR, "uct.conf"))
1232+ assert first_config is second_config
1233diff --git a/scripts/test_uct_suggestions.py b/scripts/test_uct_suggestions.py
1234new file mode 100755
1235index 0000000..43cfb75
1236--- /dev/null
1237+++ b/scripts/test_uct_suggestions.py
1238@@ -0,0 +1,124 @@
1239+#!/usr/bin/env pytest-3
1240+# -*- coding: utf-8 -*-
1241+
1242+# Author: Martin Pitt <martin.pitt@ubuntu.com>
1243+# Author: Kees Cook <kees@ubuntu.com>
1244+# Author: Jamie Strandboge <jamie@ubuntu.com>
1245+# Author: Marc Deslauriers <marc.deslauriers@ubuntu.com>
1246+# Author: Steve Beattie <sbeattie@ubuntu.com>
1247+# Copyright (C) 2005-2024 Canonical Ltd.
1248+#
1249+# This script is distributed under the terms and conditions of the GNU General
1250+# Public License, Version 2 or later. See http://www.gnu.org/copyleft/gpl.html
1251+# for details.
1252+
1253+import unittest
1254+from uct.suggestions import IgnoreSuggestions
1255+
1256+class CheckCVETest(unittest.TestCase):
1257+ def test_get_ignore_suggestion(self):
1258+ '''"Ignore" suggestion text extraction'''
1259+
1260+ # instantiate the suggestion handler
1261+ h = IgnoreSuggestions()
1262+
1263+ self.assertEqual("Courier-Authlib", h.get_ignore_suggestion('''SQL injection vulnerability in authpgsqllib.c in Courier-Authlib before 0.62.0, when a non-Latin locale Postgres database is used, allows remote attackers to execute arbitrary SQL commands via query parameters containing apostrophes.'''))
1264+
1265+ self.assertEqual("Apple Mac OS X", h.get_ignore_suggestion('''Buffer overflow in the DirectoryService Proxy in DirectoryService in Apple Mac OS X through 10.6.8 allows remote attackers to execute arbitrary code or cause a denial of service (application crash) via unspecified vectors.'''))
1266+
1267+ self.assertEqual("KDE", h.get_ignore_suggestion('''HTMLTokenizer::scriptHandler in Konqueror in KDE 3.5.9 and 3.5.10 allows remote attackers to cause a denial of service (application crash) via an invalid document.load call that triggers use of a deleted object. NOTE: some of these details are obtained from third party information.'''))
1268+
1269+ self.assertEqual("Sun Solaris", h.get_ignore_suggestion('''The name service cache daemon (nscd) in Sun Solaris 10 and OpenSolaris snv_50 through snv_104 does not properly check permissions, which allows local users to gain privileges and obtain sensitive information via unspecified vectors.'''))
1270+
1271+ self.assertEqual("Linux kernel", h.get_ignore_suggestion('''libata in the Linux kernel before 2.6.27.9 does not set minimum timeouts for SG_IO requests, which allows local users to cause a denial of service (Programmed I/O mode on drives) via multiple simultaneous invocations of an unspecified test program.'''))
1272+
1273+ self.assertEqual("iGaming", h.get_ignore_suggestion('''Multiple SQL injection vulnerabilities in iGaming 1.5 and earlier allow remote attackers to execute arbitrary SQL commands via the browse parameter to (1) previews.php and (2) reviews.php, and the (3) id parameter to index.php in a viewarticle action.'''))
1274+
1275+ self.assertEqual("PHP iCalendar", h.get_ignore_suggestion('''PHP iCalendar 2.24 and earlier allows remote attackers to bypass authentication by setting the phpicalendar and phpicalendar_login cookies to 1.'
1276+'''))
1277+
1278+ # Test length truncation, tweaked to avoid "has" matcher
1279+ self.assertEqual("** TEST CVE ** This candidate HAS been reserved by an", h.get_ignore_suggestion('''** TEST CVE ** This candidate HAS been reserved by an organization or individual that will use it when announcing a new security problem. When the candidate has been publicized, the details for this candidate will be provided.'''))
1280+
1281+ self.assertEqual("Sun OpenSolaris", h.get_ignore_suggestion('''Unspecified vulnerability in the root/boot archive tool in Sun OpenSolaris has unknown impact and local attack vectors, related to a "Temporary file vulnerability," aka Bug ID 6653455.'''))
1282+
1283+ self.assertEqual("Red Hat Certificate System", h.get_ignore_suggestion('''Red Hat Certificate System 7.2 uses world-readable permissions for password.conf and unspecified other configuration files, which allows local users to discover passwords by reading these files.'''))
1284+
1285+ self.assertEqual("Microsoft Internet Explorer", h.get_ignore_suggestion('''An unspecified function in the JavaScript implementation in Microsoft Internet Explorer creates and exposes a "temporary footprint" when there is a current login to a web site, which makes it easier for remote attackers to trick a user into acting upon a spoofed pop-up message, aka an "in-session phishing attack." NOTE: as of 20090116, the only disclosure is a vague pre-advisory with no actionable information. However, because it is from a well-known researcher, it is being assigned a CVE identifier for tracking purposes.'''))
1286+
1287+ self.assertEqual("Umer Inc Songs Portal", h.get_ignore_suggestion('''SQL injection vulnerability in albums.php in Umer Inc Songs Portal allows remote attackers to execute arbitrary SQL commands via the id parameter.'''))
1288+
1289+ self.assertEqual("Limbo CMS", h.get_ignore_suggestion('''SQL injection vulnerability in open.php in the Private Messaging (com_privmsg) component for Limbo CMS allows remote attackers to execute arbitrary SQL commands via the id parameter in a pms action to index.php.'''))
1290+
1291+ self.assertEqual("phpscripts Ranking Script", h.get_ignore_suggestion('''phpscripts Ranking Script allows remote attackers to bypass authentication and gain administrative access by sending an admin=ja cookie.'''))
1292+
1293+ self.assertEqual("A4Desk Event Calendar", h.get_ignore_suggestion('''PHP remote file inclusion vulnerability in index.php in A4Desk Event Calendar, when magic_quotes_gpc is disabled, allows remote attackers to execute arbitrary PHP code via a URL in the v parameter.'''))
1294+
1295+ self.assertEqual("Galatolo WebManager", h.get_ignore_suggestion('''Cross-site scripting (XSS) vulnerability in result.php in Galatolo WebManager (GWM) 1.0 allows remote attackers to inject arbitrary web script or HTML via the key parameter.'''))
1296+
1297+ self.assertEqual("Sun OpenSolaris", h.get_ignore_suggestion('''Unspecified vulnerability in the process (aka proc) filesystem in Sun OpenSolaris snv_85 through snv_100 allows local users to gain privileges via vectors related to the contract filesystem.'''))
1298+
1299+ self.assertEqual("Dreampics Gallery Builder", h.get_ignore_suggestion('''SQL injection vulnerability in index.php in Dreampics Gallery Builder allows remote attackers to execute arbitrary SQL commands via the exhibition_id parameter in a gallery.viewPhotos action.'''))
1300+
1301+ self.assertEqual("Omilen Photo Gallery component for Joomla!", h.get_ignore_suggestion('''Directory traversal vulnerability in the Omilen Photo Gallery (com_omphotogallery) component Beta 0.5 for Joomla! allows remote attackers to include and execute arbitrary local files via directory traversal sequences in the controller parameter to index.php.'''))
1302+
1303+ self.assertEqual("Webform module for Drupal", h.get_ignore_suggestion('''Cross-site scripting (XSS) vulnerability in the Webform module 5.x before 5.x-2.7 and 6.x before 6.x-2.7, a module for Drupal, allows remote attackers to inject arbitrary web script or HTML via a submission.'''))
1304+
1305+ self.assertEqual("Itamar Elharar MusicGallery component for Joomla!", h.get_ignore_suggestion('''SQL injection vulnerability in the Itamar Elharar MusicGallery (com_musicgallery) component for Joomla! allows remote attackers to execute arbitrary SQL commands via the id parameter in an itempage action to index.php. NOTE: the provenance of this information is unknown; the details are obtained solely from third party information.'''))
1306+
1307+ self.assertEqual("Kide Shoutbox component for Joomla!", h.get_ignore_suggestion('''The Kide Shoutbox (com_kide) component 0.4.6 for Joomla! does not properly perform authentication, which allows remote attackers to post messages with an arbitrary account name via an insertar action to index.php. NOTE: the provenance of this information is unknown; the details are obtained solely from third party information.'''))
1308+
1309+ self.assertEqual("WP-Forum plugin for WordPress", h.get_ignore_suggestion('''Multiple SQL injection vulnerabilities in the WP-Forum plugin before 2.4 for WordPress allow remote attackers to execute arbitrary SQL commands via (1) the search_max parameter in a search action to the default URI, related to wpf.class.php; (2) the forum parameter to an unspecified component, related to wpf.class.php; (3) the topic parameter in a viewforum action to the default URI, related to the remove_topic function in wpf.class.php; or the id parameter in a (4) editpost or (5) viewtopic action to the default URI, related to wpf-post.php.'''))
1310+
1311+ self.assertEqual("ListMan extension for TYPO3", h.get_ignore_suggestion('''Cross-site scripting (XSS) vulnerability in the ListMan (nl_listman) extension 1.2.1 for TYPO3 allows remote attackers to inject arbitrary web script or HTML via unspecified vectors.'''))
1312+
1313+ self.assertEqual("multiple status.net issues", h.get_ignore_suggestion('''ML-Date: 2011-01-25 12:08:05, ML-Subject: Re: CVE request: multiple status.net issues'''))
1314+ self.assertEqual("multiple status.net issues", h.get_ignore_suggestion('''ML-Date: 2011-01-25 12:08:05, ML-Subject: CVE request: multiple status.net issues'''))
1315+ self.assertEqual("multiple status.net issues", h.get_ignore_suggestion('''ML-Date: 2011-01-25 12:08:05, ML-Subject: multiple status.net issues'''))
1316+ self.assertEqual("Mambo CMS", h.get_ignore_suggestion('''ML-Date: 2011-06-28 16:24:28, ML-Subject: Re: CVE Request: Mambo CMS 4.6.x | Multiple Cross Site Scripting Vulnerabilities'''))
1317+
1318+ self.assertEqual("Apple iOS", h.get_ignore_suggestion('''The DNAv4 protocol implementation in the DHCP component in Apple iOS before 6 sends Wi-Fi packets containing a MAC address of a host on a previously used network, which might allow remote attackers to obtain sensitive information about previous device locations by sniffing an unencrypted Wi-Fi network for these packets.'''))
1319+
1320+ self.assertEqual("Conceptronic", h.get_ignore_suggestion('''Multiple open redirect vulnerabilities on the Conceptronic C54APM access point with runtime code 1.26 allow remote attackers to redirect users to arbitrary web sites and conduct phishing attacks via (1) the submit-url parameter in a Refresh action to goform/formWlSiteSurvey or (2) the wlan-url parameter to goform/formWlanSetup.'''))
1321+
1322+ self.assertEqual("Tapjoy library for Android", h.get_ignore_suggestion('''The Tapjoy library for Android does not verify X.509 certificates from SSL servers, which allows man-in-the-middle attackers to spoof servers and obtain sensitive information via a crafted certificate.'''))
1323+
1324+ self.assertEqual("kamkomesan application for Android", h.get_ignore_suggestion('''The kamkomesan (aka com.anek.kamkomesan) application 1.0 for Android does not verify X.509 certificates from SSL servers, which allows man-in-the-middle attackers to spoof servers and obtain sensitive information via a crafted certificate.'''))
1325+
1326+ self.assertEqual("DataTables plugin for jQuery", h.get_ignore_suggestion('''Cross-site scripting (XSS) vulnerability in the DataTables plugin 1.10.8 and earlier for jQuery allows remote attackers to inject arbitrary web script or HTML via the scripts parameter to media/unit_testing/templates/6776.php.'''))
1327+
1328+ self.assertEqual("Cisco Application Policy Infrastructure Controller (APIC)", h.get_ignore_suggestion('''A vulnerability in Cisco Application Policy Infrastructure Controller (APIC) could allow an authenticated, remote attacker to gain higher privileges than the account is assigned.'''))
1329+
1330+ self.assertEqual("Redirection", h.get_ignore_suggestion('''Redirection version 2.7.3 contains a ACE via file inclusion vulnerability in Pass-through mode that can result in allows admins to execute any PHP file in the filesystem.'''))
1331+
1332+ self.assertEqual("topydo", h.get_ignore_suggestion('''topydo contains a CWE-20: Improper Input Validation vulnerability in ListFormatParser::parse, file topydo/lib/ListFormat.py line 292 as of d4f843dac71308b2f29a7c2cdc76f055c3841523 that can result in Injection of arbitrary bytes to the terminal,'''))
1333+
1334+ self.assertEqual("Joplin", h.get_ignore_suggestion('''Joplin version prior to 1.0.90 contains a XSS evolving into code execution due to enabled nodeIntegration.'''))
1335+
1336+ self.assertEqual("Foxit Reader", h.get_ignore_suggestion('''This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Foxit Reader 9.2.0.9297.'''))
1337+
1338+ self.assertEqual("Exquisite Ultimate Newspaper theme for WordPress", h.get_ignore_suggestion('''The Exquisite Ultimate Newspaper theme 1.3.3 for WordPress has XSS via the anchor identifier to assets/js/jquery.foundation.plugins.js.'''))
1339+ self.assertEqual("Weaver Xtreme Theme for WordPress", h.get_ignore_suggestion('''The Weaver Xtreme Theme for WordPress is vulnerable to stored Cross-Site Scripting due to insufficient escaping of the profile display name in versions up to, and including, 5.0.7.'''))
1340+ self.assertEqual("Weaver Show Posts Plugin for WordPress", h.get_ignore_suggestion('''The Weaver Show Posts Plugin for WordPress is vulnerable to stored Cross-Site Scripting due to insufficient escaping of the profile display name in versions up to, and including, 1.6.'''))
1341+ self.assertEqual("WordPress File Upload and WordPress File Upload Pro plugins for WordPress", h.get_ignore_suggestion('''The WordPress File Upload and WordPress File Upload Pro plugins for WordPress are vulnerable to Path Traversal in versions up to, and including, 4.19.1 via the vulnerable parameter wfu_newpath.'''))
1342+ self.assertEqual("browserless-chrome", h.get_ignore_suggestion('''This affects all versions of package browserless-chrome. User input flowing from the workspace endpoint gets used to create a file path filePath and this is fetched and then sent back to a user. This can be escaped to fetch arbitrary files from a server.'''))
1343+ self.assertEqual("@absolunet/kafe", h.get_ignore_suggestion('''This affects the package @absolunet/kafe before 3.2.10. It allows cause a denial of service when validating crafted invalid emails.'''))
1344+ self.assertEqual("Magneticlab Sàrl Homepage Pop-up plugin", h.get_ignore_suggestion('''Cross-Site Request Forgery (CSRF) vulnerability in Magneticlab Sàrl Homepage Pop-up plugin <= 1.2.5 versions.'''))
1345+
1346+ # handle odd whitespace
1347+ self.assertEqual("PTC Vuforia Studio does not require a token.", h.get_ignore_suggestion('''
1348+
1349+
1350+
1351+
1352+ PTC Vuforia Studio does not require a token.
1353+ '''))
1354+ self.assertEqual("zxcvbn-ts", h.get_ignore_suggestion('''zxcvbn-ts is a password strength estimator written in typescript.'''))
1355+ self.assertEqual("VMware Tools for Windows", h.get_ignore_suggestion('''VMware Tools for Windows (10.x.y prior to 10.3.10, 11.x.y prior to 11.1.5) contains a denial-of-service vulnerability due to NULL pointer dereference in vm3dmp driver. Successful exploitation of this issue may allow attackers with normal user privileges to create a denial-of-service condition on the guest machine where VMware Tools is installed.'''))
1356+
1357+ self.assertEqual("3DPrint WordPress plugin", h.get_ignore_suggestion("The 3DPrint WordPress plugin before 3.5.6.9 does not protect against CSRF attacks in the modified version of Tiny File Manager included with the plugin, allowing an attacker to craft a malicious request that will create an archive of any files or directories on the target server by tricking a logged in admin into submitting a form."))
1358+
1359+ self.assertEqual("Feathersjs", h.get_ignore_suggestion('''Feathersjs is a framework for creating web APIs and real-time applications with TypeScript or JavaScript.'''))
1360+
1361+ self.assertEqual("PaulPrinting CMS 2018", h.get_ignore_suggestion("A vulnerability, which was classified as problematic, was found in PaulPrinting CMS 2018"))
1362+
1363diff --git a/scripts/uct/config.py b/scripts/uct/config.py
1364new file mode 100644
1365index 0000000..9476199
1366--- /dev/null
1367+++ b/scripts/uct/config.py
1368@@ -0,0 +1,75 @@
1369+#!/usr/bin/env python3
1370+# -*- coding: utf-8 -*-
1371+#
1372+# Copyright (C) 2008-2024 Canonical Ltd.
1373+# Author: Jamie Strandboge <jamie@ubuntu.com>
1374+# Author: Kees Cook <kees@ubuntu.com>
1375+# Author: Steve Beattie <steve.beattie@canonical.com>
1376+#
1377+# This script is distributed under the terms and conditions of the GNU General
1378+# Public License, Version 3 or later. See http://www.gnu.org/copyleft/gpl.html
1379+# for details.
1380+
1381+from __future__ import print_function
1382+
1383+import os
1384+
1385+# nobody should be using ubuntu 14.04 LTS with this code, so we don't
1386+# need a fallback implementation to ConfigObj
1387+from configobj import ConfigObj
1388+
1389+# private singleton object, use read_config() to access
1390+_config = None
1391+
1392+
1393+# _read_config: does the actual work without writing to the global
1394+# singleton. This can be used in tests where read_config() will always
1395+# return the same thing.
1396+def _read_config(_config_file=None):
1397+
1398+ if _config_file:
1399+ config_file = _config_file
1400+ else:
1401+ config_file = os.path.join(
1402+ os.path.expanduser("~"), ".ubuntu-cve-tracker.conf"
1403+ )
1404+
1405+ if not os.path.exists(config_file):
1406+ raise ValueError("Could not find '%s'" % (config_file))
1407+
1408+ config = ConfigObj(config_file)
1409+
1410+ validate_config(config, config_file)
1411+
1412+ return config
1413+
1414+
1415+# _read_config_cached: if the results of parsing the config file have
1416+# not already been cached, save it; otherwise return the singleton.
1417+# Takes an optional config file path as an argument. This can be used in tests
1418+# where read_config() will only use ~/.ubuntu-cve-tracker.conf
1419+def _read_config_cached(_config_file=None):
1420+ global _config
1421+
1422+ # if we've already parsed the config file, return what we already
1423+ # have
1424+ if not _config:
1425+ _config = _read_config()
1426+
1427+ return _config
1428+
1429+
1430+# read_uct_config: call with no arguments to get the contents of
1431+# ~/.ubuntu-cve-tracker.conf
1432+def read_uct_config():
1433+ return _read_config_cached()
1434+
1435+
1436+# XXX-FIXME this should use configobj's Validator stuff
1437+def validate_config(config, config_file):
1438+
1439+ # Validate required arguments
1440+ if "plb_authentication" not in config:
1441+ raise ValueError(("Could not find 'plb_authentication' entry in %s." % (config_file)))
1442+ if not os.path.exists(config["plb_authentication"]):
1443+ raise ValueError(("Could not find file specified by 'plb_authentication' in %s." % (config_file)))
1444diff --git a/scripts/check_cves/ignored_cache.py b/scripts/uct/ignored_cache.py
1445similarity index 100%
1446rename from scripts/check_cves/ignored_cache.py
1447rename to scripts/uct/ignored_cache.py
1448diff --git a/scripts/uct/suggestions.py b/scripts/uct/suggestions.py
1449new file mode 100755
1450index 0000000..fe496fc
1451--- /dev/null
1452+++ b/scripts/uct/suggestions.py
1453@@ -0,0 +1,131 @@
1454+#!/usr/bin/env python3
1455+# -*- coding: utf-8 -*-
1456+
1457+# suggestions module
1458+#
1459+# Author: Martin Pitt <martin.pitt@ubuntu.com>
1460+# Author: Kees Cook <kees@ubuntu.com>
1461+# Author: Jamie Strandboge <jamie@ubuntu.com>
1462+# Author: Marc Deslauriers <marc.deslauriers@ubuntu.com>
1463+# Author: Steve Beattie <sbeattie@ubuntu.com>
1464+# Copyright (C) 2005-2024 Canonical Ltd.
1465+#
1466+# This script is distributed under the terms and conditions of the GNU General
1467+# Public License, Version 2 or later. See http://www.gnu.org/copyleft/gpl.html
1468+# for details.
1469+
1470+import re
1471+
1472+class IgnoreSuggestions:
1473+
1474+ def get_ignore_suggestion(self, text):
1475+ '''Try to find a reasonable suggestion for the user.'''
1476+ suggestion = ""
1477+
1478+ # remove any whitespace
1479+ rev_text = text.strip()
1480+ # strip out the added mailing list stuff (locate_cves.py importing)
1481+ rev_text = re.sub(r'^ML-Date: .* ML-Subject: ', '', rev_text)
1482+ rev_text = re.sub(r'^(|Re: )CVE (r|R)equest: ', '', rev_text)
1483+
1484+ first_sentence = re.split(r'\. ', rev_text)[0]
1485+
1486+ # hunt for module/component
1487+ nouns = ["library", "templates?", "components?", "modules?", "[Pp]lug-?ins?", "extension", "application", "[Tt]hemes?"]
1488+ products = ["Joomla!", "Drupal", "WordPress", "TYPO3", "Mambo", "Android", "jQuery"]
1489+ match = re.search(r'(?: in t|^T)he (.*) (%s) (?:.* )?for (%s)' % ("|".join(nouns), "|".join(products)),
1490+ first_sentence)
1491+ preposition = "for"
1492+ if not match:
1493+ # try swapping order of product and noun in which case there is no preposition
1494+ match = re.search(r'(?: in t|^T)he (.*) (%s) (%s) (?:.* )' % ("|".join(products), "|".join(nouns)),
1495+ first_sentence)
1496+ preposition = ""
1497+ if match:
1498+ module = match.group(1)
1499+ noun = match.group(2)
1500+ product = match.group(3)
1501+ for marker in [" module", " ("]:
1502+ if marker in module:
1503+ module = module.split(marker)[0]
1504+ return " ".join(filter(lambda x: len(x) > 0, [module, noun, preposition, product]))
1505+
1506+ # hunt for a description of a thing written in a language or for creating something else
1507+ match = re.search(r'(.*) is a (?:.*) (written in|for creating) (?:.*)', first_sentence)
1508+ if match:
1509+ return match.group(1)
1510+
1511+ # hunt for a product on a platform
1512+ match = re.search(r'.* for (Windows|Linux|Mac OS|iOS|Android|iPhone|iPad|BlackBerry|Symbian)', first_sentence)
1513+ if match:
1514+ return match.group(0)
1515+
1516+ # look for common pattern
1517+ match = re.search(r'A vulnerability(?:, which was)? classified as (?:.*),? was found in (.*)', first_sentence)
1518+ if match:
1519+ return match.group(1)
1520+
1521+ # drop commas-extensions
1522+ if ',' in first_sentence:
1523+ first_sentence = re.split(r',', first_sentence)[0]
1524+ phrases = re.split(r' [io]n ', first_sentence)
1525+
1526+ # default to the last phrase
1527+ suggestion = phrases[-1]
1528+ # move to earlier phrase if suggestion starts with "a"
1529+ if suggestion.startswith('a ') and len(phrases) > 1:
1530+ suggestion = phrases[-2]
1531+
1532+ version_preps = r'(\s+(before|through|prior to|versions?|[<>]=?))+\s*'
1533+ version_regex = r'\s+([a-zA-Z\._\-]*[0-9]+[a-zA-Z\._\-]*)+'
1534+ # prefer 'Apple iOS before <version>' or 'Apple Mac OS X through
1535+ # <version' in the last phrase over other suggestions
1536+ if not re.search(r'' + version_preps + version_regex, suggestion):
1537+ # grab the first phrase with something that may be a version number
1538+ for p in phrases:
1539+ if re.search(r'' + version_regex, p):
1540+ suggestion = p
1541+ break
1542+ if re.search(r'^[^,]+\s+for\s+', p):
1543+ suggestion = p
1544+ break
1545+
1546+ # try to find a good suggestion from the phrase (ie suggest 'Linux
1547+ # kernel' from 'the Linux kernel before 2.6.27')
1548+ suggestion = re.split(r'\s+([a-zA-Z\._\-]*[0-9]+[a-zA-Z\._\-]*)+', suggestion)[0]
1549+ # "blah in component for Software"
1550+ if re.search(r'^[^,]+\s+for\s+', suggestion):
1551+ suggestion = re.split(r'[^,]+\s+for\s+', suggestion)[1]
1552+
1553+ # Chop off action verbs
1554+ cleanup_regexes = [
1555+ # clean up leading "in" or "the"
1556+ r'^\s*([tT]he|[iI]n)\s+',
1557+ # clean up trailing version prepositions like "before" or "through"
1558+ # from version details
1559+ r'' + version_preps + '$',
1560+ # clean up trailing parens
1561+ r'\s+\([^\)]+\)\s*$',
1562+ # action verbs
1563+ r'\s+(has|creates|allows|could|contains)($|\s+.*)',
1564+ # "vulnerbale installations of"
1565+ r'(^|\s+)vulnerable\sinstallations?\sof($|\s+)',
1566+ # This affects all versions of package
1567+ r'^This affects all versions of package\s+',
1568+ # This affects the package
1569+ r'^This affects the package\s+',
1570+ ]
1571+
1572+ for regex in cleanup_regexes:
1573+ if re.search(regex, suggestion):
1574+ suggestion = re.sub(regex, '', suggestion)
1575+
1576+ # if the phrase is too long, truncate it to max_length, but make
1577+ # sure we don't have a partial word at the end
1578+ max_length = 64
1579+ if len(suggestion) > max_length:
1580+ suggestion = suggestion[:max_length]
1581+ suggestion = re.sub(r'\s+\w+$', '', suggestion)
1582+
1583+ return suggestion
1584+
1585diff --git a/scripts/usn_lib.py b/scripts/usn_lib.py
1586index 3e45239..d8c7622 100644
1587--- a/scripts/usn_lib.py
1588+++ b/scripts/usn_lib.py
1589@@ -16,7 +16,7 @@ import subprocess
1590 import sys
1591 import urllib
1592 from source_map import version_compare
1593-import cve_lib
1594+from uct.config import read_uct_config
1595
1596 try:
1597 import cPickle
1598@@ -164,7 +164,7 @@ class USNdb(object):
1599 if db:
1600 db_name = db
1601 else:
1602- config = cve_lib.read_config()
1603+ config = read_uct_config()
1604 if config['usn_db_copy']:
1605 db_name = config['usn_db_copy']
1606
1607diff --git a/test/uct/config/no_plb_auth_entry.conf b/test/uct/config/no_plb_auth_entry.conf
1608new file mode 100644
1609index 0000000..755ad8c
1610--- /dev/null
1611+++ b/test/uct/config/no_plb_auth_entry.conf
1612@@ -0,0 +1,2 @@
1613+# python-launchpad-bugs authentication cookies file (sis-changes)
1614+not_plb_authentication="/bin/true"
1615diff --git a/test/uct/config/plb_auth_dev_null.conf b/test/uct/config/plb_auth_dev_null.conf
1616new file mode 100644
1617index 0000000..1e33174
1618--- /dev/null
1619+++ b/test/uct/config/plb_auth_dev_null.conf
1620@@ -0,0 +1,2 @@
1621+# python-launchpad-bugs authentication cookies file (sis-changes)
1622+plb_authentication="/dev/null"
1623diff --git a/test/uct/config/plb_auth_does_not_exist.conf b/test/uct/config/plb_auth_does_not_exist.conf
1624new file mode 100644
1625index 0000000..9a81f39
1626--- /dev/null
1627+++ b/test/uct/config/plb_auth_does_not_exist.conf
1628@@ -0,0 +1,2 @@
1629+# python-launchpad-bugs authentication cookies file (sis-changes)
1630+plb_authentication="/does/not/exist"
1631diff --git a/test/uct/config/uct.conf b/test/uct/config/uct.conf
1632new file mode 100644
1633index 0000000..8bf6334
1634--- /dev/null
1635+++ b/test/uct/config/uct.conf
1636@@ -0,0 +1,15 @@
1637+# python-launchpad-bugs authentication cookies file (sis-changes)
1638+plb_authentication="/dev/null"
1639+
1640+# path to Debian "security-tracker" GIT tree (check-cves)
1641+secure_testing_path='/home/ubuntu-security/git/cve_trackers/debian-security-tracker'
1642+
1643+# path to archive-layout mirror of supported architectures
1644+# sis-generate-usn, packages-mirror)
1645+packages_mirror='/home/ubuntu-security/archive-metadata/ubuntu-archive-metadata'
1646+
1647+# same as packages_mirror, but for Debian testing repository
1648+debian_mirror='/home/ubuntu-security/archive-metadata/debian-testing-archive-metadata'
1649+
1650+# path to usn-tool bzr tree, used to manipulate USN databases
1651+usn_tool='/home/ubuntu-security/git/usn-tool'

Subscribers

People subscribed via source and target branches