1 |
This copy of glsa-check is no longer need since it has |
2 |
moved to the portage repository. |
3 |
|
4 |
Bug: https://bugs.gentoo.org/463952 |
5 |
Signed-off-by: Zac Medico <zmedico@g.o> |
6 |
--- |
7 |
README | 1 - |
8 |
bin/glsa-check | 418 ------------------ |
9 |
man/glsa-check.1 | 66 --- |
10 |
pym/gentoolkit/glsa/__init__.py | 741 -------------------------------- |
11 |
setup.py | 2 - |
12 |
5 files changed, 1228 deletions(-) |
13 |
delete mode 100755 bin/glsa-check |
14 |
delete mode 100644 man/glsa-check.1 |
15 |
delete mode 100644 pym/gentoolkit/glsa/__init__.py |
16 |
|
17 |
diff --git a/README b/README |
18 |
index e122086..4ec4fc9 100644 |
19 |
--- a/README |
20 |
+++ b/README |
21 |
@@ -29,7 +29,6 @@ equery - replacement for etcat and qpkg |
22 |
eread - script to read portage log items from einfo, ewarn etc. |
23 |
eshowkw - Display keywords for specified package(s) |
24 |
euse - tool to manage USE flags |
25 |
-glsa-check - tool to manage GLSA's (Gentoo Linux Security Advisory) |
26 |
imlate - Displays candidates for keywords for an architecture... |
27 |
qpkg - convient package query tool (deprecated) |
28 |
revdep-rebuild - scans/fixes broken shared libs and binaries |
29 |
diff --git a/bin/glsa-check b/bin/glsa-check |
30 |
deleted file mode 100755 |
31 |
index 3f691b8..0000000 |
32 |
--- a/bin/glsa-check |
33 |
+++ /dev/null |
34 |
@@ -1,418 +0,0 @@ |
35 |
-#!/usr/bin/python |
36 |
- |
37 |
-# $Header: $ |
38 |
-# This program is licensed under the GPL, version 2 |
39 |
- |
40 |
-import sys |
41 |
-import os |
42 |
-import codecs |
43 |
-from functools import reduce |
44 |
- |
45 |
-import portage |
46 |
-from portage.output import * |
47 |
- |
48 |
-from getopt import getopt, GetoptError |
49 |
- |
50 |
-__program__ = "glsa-check" |
51 |
-__author__ = "Marius Mauch <genone@g.o>" |
52 |
-__version__ = "git" |
53 |
- |
54 |
-optionmap = [ |
55 |
-["-l", "--list", "list the GLSAs"], |
56 |
-["-d", "--dump", "--print", "show all information about the GLSAs"], |
57 |
-["-t", "--test", "test if this system is affected by the GLSAs"], |
58 |
-["-p", "--pretend", "show the necessary steps to apply the GLSAs"], |
59 |
-["-f", "--fix", "try to auto-apply the GLSAs (experimental)"], |
60 |
-["-i", "--inject", "inject the given GLSA into the glsa_injected file"], |
61 |
-["-n", "--nocolor", "disable colors (option)"], |
62 |
-["-e", "--emergelike", "upgrade to latest version (not least-change, option)"], |
63 |
-["-h", "--help", "show this help message"], |
64 |
-["-V", "--version", "some information about this tool"], |
65 |
-["-v", "--verbose", "print more information (option)"], |
66 |
-["-c", "--cve", "show CVE ids in listing mode (option)"], |
67 |
-["-q", "--quiet", "be less verbose and do not send empty mail (option)"], |
68 |
-["-m", "--mail", "send a mail with the given GLSAs to the administrator"], |
69 |
-] |
70 |
- |
71 |
-# print a warning as this is beta code (but proven by now, so no more warning) |
72 |
-#sys.stderr.write("WARNING: This tool is completely new and not very tested, so it should not be\n") |
73 |
-#sys.stderr.write("used on production systems. It's mainly a test tool for the new GLSA release\n") |
74 |
-#sys.stderr.write("and distribution system, it's functionality will later be merged into emerge\n") |
75 |
-#sys.stderr.write("and equery.\n") |
76 |
-#sys.stderr.write("Please read http://www.gentoo.org/proj/en/portage/glsa-integration.xml\n") |
77 |
-#sys.stderr.write("before using this tool AND before reporting a bug.\n\n") |
78 |
- |
79 |
-# option parsing |
80 |
-args = [] |
81 |
-params = [] |
82 |
-try: |
83 |
- args, params = getopt(sys.argv[1:], "".join([o[0][1] for o in optionmap]), \ |
84 |
- [x[2:] for x in reduce(lambda x,y: x+y, [z[1:-1] for z in optionmap])]) |
85 |
- args = [a for a, b in args] |
86 |
- |
87 |
- for option in ["--nocolor", "-n"]: |
88 |
- if option in args: |
89 |
- nocolor() |
90 |
- args.remove(option) |
91 |
- |
92 |
- verbose = False |
93 |
- for option in ["--verbose", "-v"]: |
94 |
- if option in args: |
95 |
- verbose = True |
96 |
- args.remove(option) |
97 |
- |
98 |
- list_cve = False |
99 |
- for option in ["--cve", "-c"]: |
100 |
- if option in args: |
101 |
- list_cve = True |
102 |
- args.remove(option) |
103 |
- |
104 |
- least_change = True |
105 |
- for option in ["--emergelike", "-e"]: |
106 |
- if option in args: |
107 |
- least_change = False |
108 |
- args.remove(option) |
109 |
- |
110 |
- quiet = False |
111 |
- for option in ["--quiet", "-q"]: |
112 |
- if option in args: |
113 |
- quiet = True |
114 |
- args.remove(option) |
115 |
- |
116 |
- |
117 |
- # sanity checking |
118 |
- if len(args) <= 0: |
119 |
- sys.stderr.write("no option given: what should I do ?\n") |
120 |
- mode = "HELP" |
121 |
- elif len(args) > 1: |
122 |
- sys.stderr.write("please use only one command per call\n") |
123 |
- mode = "HELP" |
124 |
- else: |
125 |
- # in what mode are we ? |
126 |
- args = args[0] |
127 |
- for m in optionmap: |
128 |
- if args in [o for o in m[:-1]]: |
129 |
- mode = m[1][2:] |
130 |
- |
131 |
-except GetoptError as e: |
132 |
- sys.stderr.write("unknown option given: ") |
133 |
- sys.stderr.write(str(e)+"\n") |
134 |
- mode = "HELP" |
135 |
- |
136 |
-# we need a set of glsa for most operation modes |
137 |
-if len(params) <= 0 and mode in ["fix", "test", "pretend", "dump", "inject", "mail"]: |
138 |
- sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n") |
139 |
- sys.stderr.write("If you want to run on all GLSA please tell me so \n") |
140 |
- sys.stderr.write("(specify \"all\" as parameter)\n\n") |
141 |
- mode = "HELP" |
142 |
-elif len(params) <= 0 and mode == "list": |
143 |
- params.append("affected") |
144 |
- |
145 |
-# show help message |
146 |
-if mode == "help" or mode == "HELP": |
147 |
- msg = "Syntax: glsa-check <option> [glsa-list]\n\n" |
148 |
- for m in optionmap: |
149 |
- msg += m[0] + "\t" + m[1] + " \t: " + m[-1] + "\n" |
150 |
- for o in m[2:-1]: |
151 |
- msg += "\t" + o + "\n" |
152 |
- msg += "\nglsa-list can contain an arbitrary number of GLSA ids, \n" |
153 |
- msg += "filenames containing GLSAs or the special identifiers \n" |
154 |
- msg += "'all' and 'affected'\n" |
155 |
- if mode == "help": |
156 |
- sys.stdout.write(msg) |
157 |
- sys.exit(0) |
158 |
- else: |
159 |
- sys.stderr.write("\n" + msg) |
160 |
- sys.exit(1) |
161 |
- |
162 |
-# we need root privileges for write access |
163 |
-if mode in ["fix", "inject"] and os.geteuid() != 0: |
164 |
- sys.stderr.write(__program__ + ": root access is needed for \""+mode+"\" mode\n") |
165 |
- sys.exit(2) |
166 |
- |
167 |
-# show version and copyright information |
168 |
-if mode == "version": |
169 |
- sys.stderr.write("%(program)s (%(version)s)\n" % { |
170 |
- "program": __program__, |
171 |
- "version": __version__ |
172 |
- }) |
173 |
- sys.stderr.write("Author: %s\n" % __author__) |
174 |
- sys.stderr.write("This program is licensed under the GPL, version 2\n") |
175 |
- sys.exit(0) |
176 |
- |
177 |
-# delay this for speed increase |
178 |
-from gentoolkit.glsa import * |
179 |
- |
180 |
-glsaconfig = checkconfig(portage.config(clone=portage.settings)) |
181 |
- |
182 |
-if quiet: |
183 |
- glsaconfig["EMERGE_OPTS"] += " --quiet" |
184 |
- |
185 |
-vardb = portage.db[portage.root]["vartree"].dbapi |
186 |
-portdb = portage.db[portage.root]["porttree"].dbapi |
187 |
- |
188 |
-# Check that we really have a glsa dir to work on |
189 |
-if not (os.path.exists(glsaconfig["GLSA_DIR"]) and os.path.isdir(glsaconfig["GLSA_DIR"])): |
190 |
- sys.stderr.write(red("ERROR")+": GLSA_DIR %s doesn't exist. Please fix this.\n" % glsaconfig["GLSA_DIR"]) |
191 |
- sys.exit(1) |
192 |
- |
193 |
-# build glsa lists |
194 |
-completelist = get_glsa_list(glsaconfig["GLSA_DIR"], glsaconfig) |
195 |
- |
196 |
-if os.access(glsaconfig["CHECKFILE"], os.R_OK): |
197 |
- checklist = [line.strip() for line in open(glsaconfig["CHECKFILE"], "r").readlines()] |
198 |
-else: |
199 |
- checklist = [] |
200 |
-todolist = [e for e in completelist if e not in checklist] |
201 |
- |
202 |
-glsalist = [] |
203 |
-if "new" in params: |
204 |
- params.remove("new") |
205 |
- sys.stderr.write("Warning: The 'new' glsa-list target has been removed, using 'affected'.\n") |
206 |
- params.append("affected") |
207 |
- |
208 |
-if "all" in params: |
209 |
- glsalist = completelist |
210 |
- params.remove("all") |
211 |
- |
212 |
-if "affected" in params: |
213 |
- for x in todolist: |
214 |
- try: |
215 |
- myglsa = Glsa(x, glsaconfig) |
216 |
- except (GlsaTypeException, GlsaFormatException) as e: |
217 |
- if verbose: |
218 |
- sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e))) |
219 |
- continue |
220 |
- if myglsa.isVulnerable(): |
221 |
- glsalist.append(x) |
222 |
- params.remove("affected") |
223 |
- |
224 |
-# remove invalid parameters |
225 |
-for p in params[:]: |
226 |
- if not (p in completelist or os.path.exists(p)): |
227 |
- sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p)) |
228 |
- params.remove(p) |
229 |
- |
230 |
-glsalist.extend([g for g in params if g not in glsalist]) |
231 |
- |
232 |
-def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr, encoding="utf-8"): |
233 |
- # Get to the raw streams in py3k before wrapping them with an encoded writer |
234 |
- # to avoid writing bytes to a text stream (stdout/stderr are text streams |
235 |
- # by default in py3k) |
236 |
- if hasattr(fd1, "buffer"): |
237 |
- fd1 = fd1.buffer |
238 |
- if hasattr(fd2, "buffer"): |
239 |
- fd2 = fd2.buffer |
240 |
- fd1 = codecs.getwriter(encoding)(fd1) |
241 |
- fd2 = codecs.getwriter(encoding)(fd2) |
242 |
- if not quiet: |
243 |
- fd2.write(white("[A]")+" means this GLSA was marked as applied (injected),\n") |
244 |
- fd2.write(green("[U]")+" means the system is not affected and\n") |
245 |
- fd2.write(red("[N]")+" indicates that the system might be affected.\n\n") |
246 |
- |
247 |
- myglsalist.sort() |
248 |
- for myid in myglsalist: |
249 |
- try: |
250 |
- myglsa = Glsa(myid, glsaconfig) |
251 |
- except (GlsaTypeException, GlsaFormatException) as e: |
252 |
- if verbose: |
253 |
- fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) |
254 |
- continue |
255 |
- if myglsa.isInjected(): |
256 |
- status = "[A]" |
257 |
- color = white |
258 |
- elif myglsa.isVulnerable(): |
259 |
- status = "[N]" |
260 |
- color = red |
261 |
- else: |
262 |
- status = "[U]" |
263 |
- color = green |
264 |
- |
265 |
- if verbose: |
266 |
- access = ("[%-8s] " % myglsa.access) |
267 |
- else: |
268 |
- access = "" |
269 |
- |
270 |
- fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (") |
271 |
- if not verbose: |
272 |
- for pkg in list(myglsa.packages.keys())[:3]: |
273 |
- fd1.write(" " + pkg + " ") |
274 |
- if len(myglsa.packages) > 3: |
275 |
- fd1.write("... ") |
276 |
- else: |
277 |
- for cpv in myglsa.packages.keys(): |
278 |
- pkg = myglsa.packages[cpv] |
279 |
- for path in pkg: |
280 |
- v_installed = reduce(operator.add, [match(v, "vartree") for v in path["vul_atoms"]], []) |
281 |
- u_installed = reduce(operator.add, [match(u, "vartree") for u in path["unaff_atoms"]], []) |
282 |
- mylist = sorted(set(v_installed).difference(set(u_installed))) |
283 |
- if len(mylist) > 0: |
284 |
- cpv = color(" ".join(mylist)) |
285 |
- fd1.write(" " + cpv + " ") |
286 |
- |
287 |
- fd1.write(")") |
288 |
- if list_cve: |
289 |
- fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]]))) |
290 |
- fd1.write("\n") |
291 |
- return 0 |
292 |
- |
293 |
-if mode == "list": |
294 |
- sys.exit(summarylist(glsalist)) |
295 |
- |
296 |
-# dump, fix, inject and fix are nearly the same code, only the glsa method call differs |
297 |
-if mode in ["dump", "fix", "inject", "pretend"]: |
298 |
- for myid in glsalist: |
299 |
- try: |
300 |
- myglsa = Glsa(myid, glsaconfig) |
301 |
- except (GlsaTypeException, GlsaFormatException) as e: |
302 |
- if verbose: |
303 |
- sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) |
304 |
- continue |
305 |
- if mode == "dump": |
306 |
- myglsa.dump() |
307 |
- elif mode == "fix": |
308 |
- if not quiet: |
309 |
- sys.stdout.write("Fixing GLSA "+myid+"\n") |
310 |
- if not myglsa.isVulnerable(): |
311 |
- if not quiet: |
312 |
- sys.stdout.write(">>> no vulnerable packages installed\n") |
313 |
- else: |
314 |
- if quiet: |
315 |
- sys.stdout.write("Fixing GLSA "+myid+"\n") |
316 |
- mergelist = myglsa.getMergeList(least_change=least_change) |
317 |
- if mergelist == []: |
318 |
- sys.stdout.write(">>> cannot fix GLSA, no unaffected packages available\n") |
319 |
- sys.exit(2) |
320 |
- for pkg in mergelist: |
321 |
- sys.stdout.write(">>> merging "+pkg+"\n") |
322 |
- # using emerge for the actual merging as it contains the dependency |
323 |
- # code and we want to be consistent in behaviour. Also this functionality |
324 |
- # will be integrated in emerge later, so it shouldn't hurt much. |
325 |
- emergecmd = "emerge --oneshot " + glsaconfig["EMERGE_OPTS"] + " =" + pkg |
326 |
- if verbose: |
327 |
- sys.stderr.write(emergecmd+"\n") |
328 |
- exitcode = os.system(emergecmd) |
329 |
- # system() returns the exitcode in the high byte of a 16bit integer |
330 |
- if exitcode >= 1 << 8: |
331 |
- exitcode >>= 8 |
332 |
- if exitcode: |
333 |
- sys.exit(exitcode) |
334 |
- if len(mergelist): |
335 |
- sys.stdout.write("\n") |
336 |
- elif mode == "pretend": |
337 |
- if not quiet: |
338 |
- sys.stdout.write("Checking GLSA "+myid+"\n") |
339 |
- if not myglsa.isVulnerable(): |
340 |
- if not quiet: |
341 |
- sys.stdout.write(">>> no vulnerable packages installed\n") |
342 |
- else: |
343 |
- if quiet: |
344 |
- sys.stdout.write("Checking GLSA "+myid+"\n") |
345 |
- mergedict = {} |
346 |
- for (vuln, update) in myglsa.getAffectionTable(least_change=least_change): |
347 |
- mergedict.setdefault(update, []).append(vuln) |
348 |
- |
349 |
- # first, extract the atoms that cannot be upgraded (where key == "") |
350 |
- no_upgrades = [] |
351 |
- if "" in mergedict: |
352 |
- no_upgrades = mergedict[""] |
353 |
- del mergedict[""] |
354 |
- |
355 |
- # see if anything is left that can be upgraded |
356 |
- if mergedict: |
357 |
- sys.stdout.write(">>> Updates that will be performed:\n") |
358 |
- for (upd, vuln) in mergedict.items(): |
359 |
- sys.stdout.write(" " + green(upd) + " (vulnerable: " + red(", ".join(vuln)) + ")\n") |
360 |
- |
361 |
- if no_upgrades: |
362 |
- sys.stdout.write(">>> No upgrade path exists for these packages:\n") |
363 |
- sys.stdout.write(" " + red(", ".join(no_upgrades)) + "\n") |
364 |
- elif mode == "inject": |
365 |
- sys.stdout.write("injecting " + myid + "\n") |
366 |
- myglsa.inject() |
367 |
- sys.exit(0) |
368 |
- |
369 |
-# test is a bit different as Glsa.test() produces no output |
370 |
-if mode == "test": |
371 |
- outputlist = [] |
372 |
- for myid in glsalist: |
373 |
- try: |
374 |
- myglsa = Glsa(myid, glsaconfig) |
375 |
- except (GlsaTypeException, GlsaFormatException) as e: |
376 |
- if verbose: |
377 |
- sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) |
378 |
- continue |
379 |
- if myglsa.isVulnerable(): |
380 |
- outputlist.append(str(myglsa.nr)) |
381 |
- if len(outputlist) > 0: |
382 |
- sys.stderr.write("This system is affected by the following GLSAs:\n") |
383 |
- if verbose: |
384 |
- summarylist(outputlist) |
385 |
- else: |
386 |
- sys.stdout.write("\n".join(outputlist)+"\n") |
387 |
- else: |
388 |
- sys.stderr.write("This system is not affected by any of the listed GLSAs\n") |
389 |
- sys.exit(0) |
390 |
- |
391 |
-# mail mode as requested by solar |
392 |
-if mode == "mail": |
393 |
- try: |
394 |
- import portage.mail as portage_mail |
395 |
- except ImportError: |
396 |
- import portage_mail |
397 |
- |
398 |
- import socket |
399 |
- from io import BytesIO |
400 |
- try: |
401 |
- from email.mime.text import MIMEText |
402 |
- except ImportError: |
403 |
- from email.MIMEText import MIMEText |
404 |
- |
405 |
- # color doesn't make any sense for mail |
406 |
- nocolor() |
407 |
- |
408 |
- if "PORTAGE_ELOG_MAILURI" in glsaconfig: |
409 |
- myrecipient = glsaconfig["PORTAGE_ELOG_MAILURI"].split()[0] |
410 |
- else: |
411 |
- myrecipient = "root@localhost" |
412 |
- |
413 |
- if "PORTAGE_ELOG_MAILFROM" in glsaconfig: |
414 |
- myfrom = glsaconfig["PORTAGE_ELOG_MAILFROM"] |
415 |
- else: |
416 |
- myfrom = "glsa-check" |
417 |
- |
418 |
- mysubject = "[glsa-check] Summary for %s" % socket.getfqdn() |
419 |
- |
420 |
- # need a file object for summarylist() |
421 |
- myfd = BytesIO() |
422 |
- line = "GLSA Summary report for host %s\n" % socket.getfqdn() |
423 |
- myfd.write(line.encode("utf-8")) |
424 |
- line = "(Command was: %s)\n\n" % " ".join(sys.argv) |
425 |
- myfd.write(line.encode("utf-8")) |
426 |
- summarylist(glsalist, fd1=myfd, fd2=myfd) |
427 |
- summary = myfd.getvalue().decode("utf-8") |
428 |
- myfd.close() |
429 |
- |
430 |
- myattachments = [] |
431 |
- for myid in glsalist: |
432 |
- try: |
433 |
- myglsa = Glsa(myid, glsaconfig) |
434 |
- except (GlsaTypeException, GlsaFormatException) as e: |
435 |
- if verbose: |
436 |
- sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) |
437 |
- continue |
438 |
- myfd = BytesIO() |
439 |
- myglsa.dump(outstream=myfd) |
440 |
- attachment = myfd.getvalue().decode("utf-8") |
441 |
- myattachments.append(MIMEText(attachment, _charset="utf8")) |
442 |
- myfd.close() |
443 |
- |
444 |
- if glsalist or not quiet: |
445 |
- mymessage = portage_mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments) |
446 |
- portage_mail.send_mail(glsaconfig, mymessage) |
447 |
- |
448 |
- sys.exit(0) |
449 |
- |
450 |
-# something wrong here, all valid paths are covered with sys.exit() |
451 |
-sys.stderr.write("nothing more to do\n") |
452 |
-sys.exit(2) |
453 |
diff --git a/man/glsa-check.1 b/man/glsa-check.1 |
454 |
deleted file mode 100644 |
455 |
index 018bbd2..0000000 |
456 |
--- a/man/glsa-check.1 |
457 |
+++ /dev/null |
458 |
@@ -1,66 +0,0 @@ |
459 |
-.TH "GLSA-CHECK" "1" "0.3" "Marius Mauch" "gentoolkit" |
460 |
-.SH "NAME" |
461 |
-.LP |
462 |
-glsa\-check \- Gentoo: Tool to locally monitor and manage GLSAs |
463 |
-.SH "SYNTAX" |
464 |
-.LP |
465 |
-glsa\-check <\fIoption\fP> [\fIglsa\-list\fP] |
466 |
- |
467 |
-[\fIglsa\-list\fR] can contain an arbitrary number of GLSA ids, filenames containing GLSAs or the special identifiers 'all' and 'affected' |
468 |
-.SH "DESCRIPTION" |
469 |
-.LP |
470 |
-This tool is used to locally monitor and manage Gentoo Linux Security Advisories. |
471 |
-Please read: |
472 |
-.br |
473 |
-http://www.gentoo.org/security |
474 |
-.br |
475 |
-before reporting a bug. |
476 |
-.LP |
477 |
-Note: In order for this tool to be effective, you must regularly sync your local portage tree. |
478 |
-.SH "OPTIONS" |
479 |
-.LP |
480 |
-.TP |
481 |
-.B \-l, \-\-list |
482 |
-list the a summary for all GLSAs in glsa\-list and whether they affect the system |
483 |
-.TP |
484 |
-.B \-d, \-\-dump, \-\-print |
485 |
-show all information about the GLSAs in glsa\-list |
486 |
-.TP |
487 |
-.B \-t, \-\-test |
488 |
-test if this system is affected by the GLSAs in glsa\-list and output the GLSA IDs |
489 |
-.TP |
490 |
-.B \-p, \-\-pretend |
491 |
-show the necessary steps to apply the GLSAs in glsa\-list |
492 |
-.TP |
493 |
-.B \-f, \-\-fix |
494 |
-try to auto\-apply the GLSAs in in glsa\-list using emerge. This will only upgrade packages to later version, but not remove packages when no upgrade path exists (experimental) |
495 |
-.TP |
496 |
-.B \-i, \-\-inject |
497 |
-inject the given GLSA into the glsa_injected file |
498 |
-.TP |
499 |
-.B \-n, \-\-nocolor |
500 |
-disable colors (option) |
501 |
-.TP |
502 |
-.B \-h, \-\-help |
503 |
-show this help message |
504 |
-.TP |
505 |
-.B \-V, \-\-version |
506 |
-some information about this tool |
507 |
-.TP |
508 |
-.B \-v, \-\-verbose |
509 |
-print more messages (option) |
510 |
-.TP |
511 |
-.B \-c, \-\-cve |
512 |
-show CVE ids in listing mode (option) |
513 |
-.TP |
514 |
-.B \-q, \-\-quiet |
515 |
-be less verbose and do not send empty mail (option) |
516 |
-.TP |
517 |
-.B \-m, \-\-mail |
518 |
-send a mail with the given GLSAs to the administrator |
519 |
-.SH "FILES" |
520 |
-.LP |
521 |
-.TP |
522 |
-.B /var/lib/portage/glsa_injected |
523 |
-List of GLSA ids that have been injected and will never show up as 'affected' on this system. |
524 |
-The file must contain one GLSA id (e.g. '200804\-02') per line. |
525 |
diff --git a/pym/gentoolkit/glsa/__init__.py b/pym/gentoolkit/glsa/__init__.py |
526 |
deleted file mode 100644 |
527 |
index db71025..0000000 |
528 |
--- a/pym/gentoolkit/glsa/__init__.py |
529 |
+++ /dev/null |
530 |
@@ -1,741 +0,0 @@ |
531 |
-# $Header$ |
532 |
- |
533 |
-# This program is licensed under the GPL, version 2 |
534 |
- |
535 |
-# WARNING: this code is only tested by a few people and should NOT be used |
536 |
-# on production systems at this stage. There are possible security holes and probably |
537 |
-# bugs in this code. If you test it please report ANY success or failure to |
538 |
-# me (genone@g.o). |
539 |
- |
540 |
-# The following planned features are currently on hold: |
541 |
-# - getting GLSAs from http/ftp servers (not really useful without the fixed ebuilds) |
542 |
-# - GPG signing/verification (until key policy is clear) |
543 |
- |
544 |
-from __future__ import unicode_literals |
545 |
- |
546 |
-__author__ = "Marius Mauch <genone@g.o>" |
547 |
- |
548 |
- |
549 |
-import sys |
550 |
-if sys.hexversion < 0x3000000: |
551 |
- from io import open |
552 |
-import os |
553 |
-try: |
554 |
- from urllib import urlopen |
555 |
-except ImportError: |
556 |
- from urllib.request import urlopen |
557 |
-import codecs |
558 |
-import re |
559 |
-import operator |
560 |
-import xml.dom.minidom |
561 |
-from io import StringIO |
562 |
-from functools import reduce |
563 |
- |
564 |
-if sys.version_info[0:2] < (2,3): |
565 |
- raise NotImplementedError("Python versions below 2.3 have broken XML code " \ |
566 |
- +"and are not supported") |
567 |
- |
568 |
-try: |
569 |
- import portage |
570 |
- from portage import _encodings, _unicode_encode |
571 |
-except ImportError: |
572 |
- sys.path.insert(0, "/usr/lib/portage/pym") |
573 |
- import portage |
574 |
- from portage import _encodings, _unicode_encode |
575 |
- |
576 |
- |
577 |
-# Note: the space for rgt and rlt is important !! |
578 |
-opMapping = {"le": "<=", "lt": "<", "eq": "=", "gt": ">", "ge": ">=", |
579 |
- "rge": ">=~", "rle": "<=~", "rgt": " >~", "rlt": " <~"} |
580 |
-NEWLINE_ESCAPE = "!;\\n" # some random string to mark newlines that should be preserved |
581 |
-SPACE_ESCAPE = "!;_" # some random string to mark spaces that should be preserved |
582 |
- |
583 |
-def center(text, width): |
584 |
- """ |
585 |
- Returns a string containing I{text} that is padded with spaces on both |
586 |
- sides. If C{len(text) >= width} I{text} is returned unchanged. |
587 |
- |
588 |
- @type text: String |
589 |
- @param text: the text to be embedded |
590 |
- @type width: Integer |
591 |
- @param width: the minimum length of the returned string |
592 |
- @rtype: String |
593 |
- @return: the expanded string or I{text} |
594 |
- """ |
595 |
- if len(text) >= width: |
596 |
- return text |
597 |
- margin = (width-len(text))//2 |
598 |
- rValue = " "*margin |
599 |
- rValue += text |
600 |
- if 2*margin + len(text) == width: |
601 |
- rValue += " "*margin |
602 |
- elif 2*margin + len(text) + 1 == width: |
603 |
- rValue += " "*(margin+1) |
604 |
- return rValue |
605 |
- |
606 |
- |
607 |
-def wrap(text, width, caption=""): |
608 |
- """ |
609 |
- Wraps the given text at column I{width}, optionally indenting |
610 |
- it so that no text is under I{caption}. It's possible to encode |
611 |
- hard linebreaks in I{text} with L{NEWLINE_ESCAPE}. |
612 |
- |
613 |
- @type text: String |
614 |
- @param text: the text to be wrapped |
615 |
- @type width: Integer |
616 |
- @param width: the column at which the text should be wrapped |
617 |
- @type caption: String |
618 |
- @param caption: this string is inserted at the beginning of the |
619 |
- return value and the paragraph is indented up to |
620 |
- C{len(caption)}. |
621 |
- @rtype: String |
622 |
- @return: the wrapped and indented paragraph |
623 |
- """ |
624 |
- rValue = "" |
625 |
- line = caption |
626 |
- text = text.replace(2*NEWLINE_ESCAPE, NEWLINE_ESCAPE+" "+NEWLINE_ESCAPE) |
627 |
- words = text.split() |
628 |
- indentLevel = len(caption)+1 |
629 |
- |
630 |
- for w in words: |
631 |
- if line[-1] == "\n": |
632 |
- rValue += line |
633 |
- line = " "*indentLevel |
634 |
- if len(line)+len(w.replace(NEWLINE_ESCAPE, ""))+1 > width: |
635 |
- rValue += line+"\n" |
636 |
- line = " "*indentLevel+w.replace(NEWLINE_ESCAPE, "\n") |
637 |
- elif w.find(NEWLINE_ESCAPE) >= 0: |
638 |
- if len(line.strip()) > 0: |
639 |
- rValue += line+" "+w.replace(NEWLINE_ESCAPE, "\n") |
640 |
- else: |
641 |
- rValue += line+w.replace(NEWLINE_ESCAPE, "\n") |
642 |
- line = " "*indentLevel |
643 |
- else: |
644 |
- if len(line.strip()) > 0: |
645 |
- line += " "+w |
646 |
- else: |
647 |
- line += w |
648 |
- if len(line) > 0: |
649 |
- rValue += line.replace(NEWLINE_ESCAPE, "\n") |
650 |
- rValue = rValue.replace(SPACE_ESCAPE, " ") |
651 |
- return rValue |
652 |
- |
653 |
-def checkconfig(myconfig): |
654 |
- """ |
655 |
- takes a portage.config instance and adds GLSA specific keys if |
656 |
- they are not present. TO-BE-REMOVED (should end up in make.*) |
657 |
- """ |
658 |
- mysettings = { |
659 |
- "GLSA_DIR": portage.settings["PORTDIR"]+"/metadata/glsa/", |
660 |
- "GLSA_PREFIX": "glsa-", |
661 |
- "GLSA_SUFFIX": ".xml", |
662 |
- "CHECKFILE": "/var/lib/portage/glsa_injected", |
663 |
- "GLSA_SERVER": "www.gentoo.org/security/en/glsa/", # not completely implemented yet |
664 |
- "CHECKMODE": "local", # not completely implemented yet |
665 |
- "PRINTWIDTH": "76" |
666 |
- } |
667 |
- for k in mysettings.keys(): |
668 |
- if k not in myconfig: |
669 |
- myconfig[k] = mysettings[k] |
670 |
- return myconfig |
671 |
- |
672 |
-def get_glsa_list(repository, myconfig): |
673 |
- """ |
674 |
- Returns a list of all available GLSAs in the given repository |
675 |
- by comparing the filelist there with the pattern described in |
676 |
- the config. |
677 |
- |
678 |
- @type repository: String |
679 |
- @param repository: The directory or an URL that contains GLSA files |
680 |
- (Note: not implemented yet) |
681 |
- @type myconfig: portage.config |
682 |
- @param myconfig: a GLSA aware config instance (see L{checkconfig}) |
683 |
- |
684 |
- @rtype: List of Strings |
685 |
- @return: a list of GLSA IDs in this repository |
686 |
- """ |
687 |
- # TODO: remote fetch code for listing |
688 |
- |
689 |
- rValue = [] |
690 |
- |
691 |
- if not os.access(repository, os.R_OK): |
692 |
- return [] |
693 |
- dirlist = os.listdir(repository) |
694 |
- prefix = myconfig["GLSA_PREFIX"] |
695 |
- suffix = myconfig["GLSA_SUFFIX"] |
696 |
- |
697 |
- for f in dirlist: |
698 |
- try: |
699 |
- if f[:len(prefix)] == prefix and f[-1*len(suffix):] == suffix: |
700 |
- rValue.append(f[len(prefix):-1*len(suffix)]) |
701 |
- except IndexError: |
702 |
- pass |
703 |
- return rValue |
704 |
- |
705 |
-def getListElements(listnode): |
706 |
- """ |
707 |
- Get all <li> elements for a given <ol> or <ul> node. |
708 |
- |
709 |
- @type listnode: xml.dom.Node |
710 |
- @param listnode: <ul> or <ol> list to get the elements for |
711 |
- @rtype: List of Strings |
712 |
- @return: a list that contains the value of the <li> elements |
713 |
- """ |
714 |
- if not listnode.nodeName in ["ul", "ol"]: |
715 |
- raise GlsaFormatException("Invalid function call: listnode is not <ul> or <ol>") |
716 |
- rValue = [getText(li, format="strip") \ |
717 |
- for li in listnode.childNodes \ |
718 |
- if li.nodeType == xml.dom.Node.ELEMENT_NODE] |
719 |
- return rValue |
720 |
- |
721 |
-def getText(node, format, textfd = None): |
722 |
- """ |
723 |
- This is the main parser function. It takes a node and traverses |
724 |
- recursive over the subnodes, getting the text of each (and the |
725 |
- I{link} attribute for <uri> and <mail>). Depending on the I{format} |
726 |
- parameter the text might be formatted by adding/removing newlines, |
727 |
- tabs and spaces. This function is only useful for the GLSA DTD, |
728 |
- it's not applicable for other DTDs. |
729 |
- |
730 |
- @type node: xml.dom.Node |
731 |
- @param node: the root node to start with the parsing |
732 |
- @type format: String |
733 |
- @param format: this should be either I{strip}, I{keep} or I{xml} |
734 |
- I{keep} just gets the text and does no formatting. |
735 |
- I{strip} replaces newlines and tabs with spaces and |
736 |
- replaces multiple spaces with one space. |
737 |
- I{xml} does some more formatting, depending on the |
738 |
- type of the encountered nodes. |
739 |
- @type textfd: writable file-like object |
740 |
- @param textfd: the file-like object to write the output to |
741 |
- @rtype: String |
742 |
- @return: the (formatted) content of the node and its subnodes |
743 |
- except if textfd was not none |
744 |
- """ |
745 |
- if not textfd: |
746 |
- textfd = StringIO() |
747 |
- returnNone = False |
748 |
- else: |
749 |
- returnNone = True |
750 |
- if format in ["strip", "keep"]: |
751 |
- if node.nodeName in ["uri", "mail"]: |
752 |
- textfd.write(node.childNodes[0].data+": "+node.getAttribute("link")) |
753 |
- else: |
754 |
- for subnode in node.childNodes: |
755 |
- if subnode.nodeName == "#text": |
756 |
- textfd.write(subnode.data) |
757 |
- else: |
758 |
- getText(subnode, format, textfd) |
759 |
- else: # format = "xml" |
760 |
- for subnode in node.childNodes: |
761 |
- if subnode.nodeName == "p": |
762 |
- for p_subnode in subnode.childNodes: |
763 |
- if p_subnode.nodeName == "#text": |
764 |
- textfd.write(p_subnode.data.strip()) |
765 |
- elif p_subnode.nodeName in ["uri", "mail"]: |
766 |
- textfd.write(p_subnode.childNodes[0].data) |
767 |
- textfd.write(" ( "+p_subnode.getAttribute("link")+" )") |
768 |
- textfd.write(NEWLINE_ESCAPE) |
769 |
- elif subnode.nodeName == "ul": |
770 |
- for li in getListElements(subnode): |
771 |
- textfd.write("-"+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" ") |
772 |
- elif subnode.nodeName == "ol": |
773 |
- i = 0 |
774 |
- for li in getListElements(subnode): |
775 |
- i = i+1 |
776 |
- textfd.write(str(i)+"."+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" ") |
777 |
- elif subnode.nodeName == "code": |
778 |
- textfd.write(getText(subnode, format="keep").lstrip().replace("\n", NEWLINE_ESCAPE)) |
779 |
- textfd.write(NEWLINE_ESCAPE) |
780 |
- elif subnode.nodeName == "#text": |
781 |
- textfd.write(subnode.data) |
782 |
- else: |
783 |
- raise GlsaFormatException("Invalid Tag found: ", subnode.nodeName) |
784 |
- if returnNone: |
785 |
- return None |
786 |
- rValue = textfd.getvalue() |
787 |
- if format == "strip": |
788 |
- rValue = rValue.strip(" \n\t") |
789 |
- rValue = re.sub("[\s]{2,}", " ", rValue) |
790 |
- return rValue |
791 |
- |
792 |
-def getMultiTagsText(rootnode, tagname, format): |
793 |
- """ |
794 |
- Returns a list with the text of all subnodes of type I{tagname} |
795 |
- under I{rootnode} (which itself is not parsed) using the given I{format}. |
796 |
- |
797 |
- @type rootnode: xml.dom.Node |
798 |
- @param rootnode: the node to search for I{tagname} |
799 |
- @type tagname: String |
800 |
- @param tagname: the name of the tags to search for |
801 |
- @type format: String |
802 |
- @param format: see L{getText} |
803 |
- @rtype: List of Strings |
804 |
- @return: a list containing the text of all I{tagname} childnodes |
805 |
- """ |
806 |
- rValue = [getText(e, format) \ |
807 |
- for e in rootnode.getElementsByTagName(tagname)] |
808 |
- return rValue |
809 |
- |
810 |
-def makeAtom(pkgname, versionNode): |
811 |
- """ |
812 |
- creates from the given package name and information in the |
813 |
- I{versionNode} a (syntactical) valid portage atom. |
814 |
- |
815 |
- @type pkgname: String |
816 |
- @param pkgname: the name of the package for this atom |
817 |
- @type versionNode: xml.dom.Node |
818 |
- @param versionNode: a <vulnerable> or <unaffected> Node that |
819 |
- contains the version information for this atom |
820 |
- @rtype: String |
821 |
- @return: the portage atom |
822 |
- """ |
823 |
- rValue = opMapping[versionNode.getAttribute("range")] \ |
824 |
- + pkgname \ |
825 |
- + "-" + getText(versionNode, format="strip") |
826 |
- try: |
827 |
- slot = versionNode.getAttribute("slot").strip() |
828 |
- except KeyError: |
829 |
- pass |
830 |
- else: |
831 |
- if slot and slot != "*": |
832 |
- rValue += ":" + slot |
833 |
- return str(rValue) |
834 |
- |
835 |
-def makeVersion(versionNode): |
836 |
- """ |
837 |
- creates from the information in the I{versionNode} a |
838 |
- version string (format <op><version>). |
839 |
- |
840 |
- @type versionNode: xml.dom.Node |
841 |
- @param versionNode: a <vulnerable> or <unaffected> Node that |
842 |
- contains the version information for this atom |
843 |
- @rtype: String |
844 |
- @return: the version string |
845 |
- """ |
846 |
- rValue = opMapping[versionNode.getAttribute("range")] \ |
847 |
- +getText(versionNode, format="strip") |
848 |
- try: |
849 |
- slot = versionNode.getAttribute("slot").strip() |
850 |
- except KeyError: |
851 |
- pass |
852 |
- else: |
853 |
- if slot and slot != "*": |
854 |
- rValue += ":" + slot |
855 |
- return rValue |
856 |
- |
857 |
-def match(atom, portdbname, match_type="default"): |
858 |
- """ |
859 |
- wrapper that calls revisionMatch() or portage.dbapi.match() depending on |
860 |
- the given atom. |
861 |
- |
862 |
- @type atom: string |
863 |
- @param atom: a <~ or >~ atom or a normal portage atom that contains the atom to match against |
864 |
- @type portdb: portage.dbapi |
865 |
- @param portdb: one of the portage databases to use as information source |
866 |
- @type match_type: string |
867 |
- @param match_type: if != "default" passed as first argument to dbapi.xmatch |
868 |
- to apply the wanted visibility filters |
869 |
- |
870 |
- @rtype: list of strings |
871 |
- @return: a list with the matching versions |
872 |
- """ |
873 |
- db = portage.db[portage.root][portdbname].dbapi |
874 |
- if atom[2] == "~": |
875 |
- return revisionMatch(atom, db, match_type=match_type) |
876 |
- elif match_type == "default" or not hasattr(db, "xmatch"): |
877 |
- return db.match(atom) |
878 |
- else: |
879 |
- return db.xmatch(match_type, atom) |
880 |
- |
881 |
-def revisionMatch(revisionAtom, portdb, match_type="default"): |
882 |
- """ |
883 |
- handler for the special >~, >=~, <=~ and <~ atoms that are supposed to behave |
884 |
- as > and < except that they are limited to the same version, the range only |
885 |
- applies to the revision part. |
886 |
- |
887 |
- @type revisionAtom: string |
888 |
- @param revisionAtom: a <~ or >~ atom that contains the atom to match against |
889 |
- @type portdb: portage.dbapi |
890 |
- @param portdb: one of the portage databases to use as information source |
891 |
- @type match_type: string |
892 |
- @param match_type: if != "default" passed as first argument to portdb.xmatch |
893 |
- to apply the wanted visibility filters |
894 |
- |
895 |
- @rtype: list of strings |
896 |
- @return: a list with the matching versions |
897 |
- """ |
898 |
- if match_type == "default" or not hasattr(portdb, "xmatch"): |
899 |
- if ":" in revisionAtom: |
900 |
- mylist = portdb.match(re.sub(r'-r[0-9]+(:[^ ]+)?$', r'\1', revisionAtom[2:])) |
901 |
- else: |
902 |
- mylist = portdb.match(re.sub("-r[0-9]+$", "", revisionAtom[2:])) |
903 |
- else: |
904 |
- if ":" in revisionAtom: |
905 |
- mylist = portdb.xmatch(match_type, re.sub(r'-r[0-9]+(:[^ ]+)?$', r'\1', revisionAtom[2:])) |
906 |
- else: |
907 |
- mylist = portdb.xmatch(match_type, re.sub("-r[0-9]+$", "", revisionAtom[2:])) |
908 |
- rValue = [] |
909 |
- for v in mylist: |
910 |
- r1 = portage.pkgsplit(v)[-1][1:] |
911 |
- r2 = portage.pkgsplit(revisionAtom[3:])[-1][1:] |
912 |
- if eval(r1+" "+revisionAtom[0:2]+" "+r2): |
913 |
- rValue.append(v) |
914 |
- return rValue |
915 |
- |
916 |
- |
917 |
-def getMinUpgrade(vulnerableList, unaffectedList, minimize=True): |
918 |
- """ |
919 |
- Checks if the systemstate is matching an atom in |
920 |
- I{vulnerableList} and returns string describing |
921 |
- the lowest version for the package that matches an atom in |
922 |
- I{unaffectedList} and is greater than the currently installed |
923 |
- version. It will return an empty list if the system is affected, |
924 |
- and no upgrade is possible or None if the system is not affected. |
925 |
- Both I{vulnerableList} and I{unaffectedList} should have the |
926 |
- same base package. |
927 |
- |
928 |
- @type vulnerableList: List of Strings |
929 |
- @param vulnerableList: atoms matching vulnerable package versions |
930 |
- @type unaffectedList: List of Strings |
931 |
- @param unaffectedList: atoms matching unaffected package versions |
932 |
- @type minimize: Boolean |
933 |
- @param minimize: True for a least-change upgrade, False for emerge-like algorithm |
934 |
- |
935 |
- @rtype: String | None |
936 |
- @return: the lowest unaffected version that is greater than |
937 |
- the installed version. |
938 |
- """ |
939 |
- v_installed = reduce(operator.add, [match(v, "vartree") for v in vulnerableList], []) |
940 |
- u_installed = reduce(operator.add, [match(u, "vartree") for u in unaffectedList], []) |
941 |
- |
942 |
- # remove all unaffected atoms from vulnerable list |
943 |
- v_installed = list(set(v_installed).difference(set(u_installed))) |
944 |
- |
945 |
- if not v_installed: |
946 |
- return None |
947 |
- |
948 |
- # this tuple holds all vulnerable atoms, and the related upgrade atom |
949 |
- vuln_update = [] |
950 |
- avail_updates = set() |
951 |
- for u in unaffectedList: |
952 |
- # TODO: This had match_type="match-all" before. I don't think it should |
953 |
- # since we disregarded masked items later anyway (match(=rValue, "porttree")) |
954 |
- avail_updates.update(match(u, "porttree")) |
955 |
- # if an atom is already installed, we should not consider it for upgrades |
956 |
- avail_updates.difference_update(u_installed) |
957 |
- |
958 |
- for vuln in v_installed: |
959 |
- update = "" |
960 |
- for c in avail_updates: |
961 |
- c_pv = portage.catpkgsplit(c) |
962 |
- i_pv = portage.catpkgsplit(vuln) |
963 |
- if portage.pkgcmp(c_pv[1:], i_pv[1:]) > 0 \ |
964 |
- and (update == "" \ |
965 |
- or (minimize ^ (portage.pkgcmp(c_pv[1:], portage.catpkgsplit(update)[1:]) > 0))) \ |
966 |
- and portage.db[portage.root]["porttree"].dbapi.aux_get(c, ["SLOT"]) == portage.db[portage.root]["vartree"].dbapi.aux_get(vuln, ["SLOT"]): |
967 |
- update = c_pv[0]+"/"+c_pv[1]+"-"+c_pv[2] |
968 |
- if c_pv[3] != "r0": # we don't like -r0 for display |
969 |
- update += "-"+c_pv[3] |
970 |
- vuln_update.append([vuln, update]) |
971 |
- |
972 |
- return vuln_update |
973 |
- |
974 |
-def format_date(datestr): |
975 |
- """ |
976 |
- Takes a date (announced, revised) date from a GLSA and formats |
977 |
- it as readable text (i.e. "January 1, 2008"). |
978 |
- |
979 |
- @type date: String |
980 |
- @param date: the date string to reformat |
981 |
- @rtype: String |
982 |
- @return: a reformatted string, or the original string |
983 |
- if it cannot be reformatted. |
984 |
- """ |
985 |
- splitdate = datestr.split("-", 2) |
986 |
- if len(splitdate) != 3: |
987 |
- return datestr |
988 |
- |
989 |
- # This cannot raise an error as we use () instead of [] |
990 |
- splitdate = (int(x) for x in splitdate) |
991 |
- |
992 |
- from datetime import date |
993 |
- try: |
994 |
- d = date(*splitdate) |
995 |
- except ValueError: |
996 |
- return datestr |
997 |
- |
998 |
- # TODO We could format to local date format '%x' here? |
999 |
- return d.strftime("%B %d, %Y") |
1000 |
- |
1001 |
-# simple Exception classes to catch specific errors |
1002 |
-class GlsaTypeException(Exception): |
1003 |
- def __init__(self, doctype): |
1004 |
- Exception.__init__(self, "wrong DOCTYPE: %s" % doctype) |
1005 |
- |
1006 |
-class GlsaFormatException(Exception): |
1007 |
- pass |
1008 |
- |
1009 |
-class GlsaArgumentException(Exception): |
1010 |
- pass |
1011 |
- |
1012 |
-# GLSA xml data wrapper class |
1013 |
-class Glsa: |
1014 |
- """ |
1015 |
- This class is a wrapper for the XML data and provides methods to access |
1016 |
- and display the contained data. |
1017 |
- """ |
1018 |
- def __init__(self, myid, myconfig): |
1019 |
- """ |
1020 |
- Simple constructor to set the ID, store the config and gets the |
1021 |
- XML data by calling C{self.read()}. |
1022 |
- |
1023 |
- @type myid: String |
1024 |
- @param myid: String describing the id for the GLSA object (standard |
1025 |
- GLSAs have an ID of the form YYYYMM-nn) or an existing |
1026 |
- filename containing a GLSA. |
1027 |
- @type myconfig: portage.config |
1028 |
- @param myconfig: the config that should be used for this object. |
1029 |
- """ |
1030 |
- if re.match(r'\d{6}-\d{2}', myid): |
1031 |
- self.type = "id" |
1032 |
- elif os.path.exists(myid): |
1033 |
- self.type = "file" |
1034 |
- else: |
1035 |
- raise GlsaArgumentException("Given ID "+myid+" isn't a valid GLSA ID or filename.") |
1036 |
- self.nr = myid |
1037 |
- self.config = myconfig |
1038 |
- self.read() |
1039 |
- |
1040 |
- def read(self): |
1041 |
- """ |
1042 |
- Here we build the filename from the config and the ID and pass |
1043 |
- it to urllib to fetch it from the filesystem or a remote server. |
1044 |
- |
1045 |
- @rtype: None |
1046 |
- @return: None |
1047 |
- """ |
1048 |
- if self.config["CHECKMODE"] == "local": |
1049 |
- repository = "file://" + self.config["GLSA_DIR"] |
1050 |
- else: |
1051 |
- repository = self.config["GLSA_SERVER"] |
1052 |
- if self.type == "file": |
1053 |
- myurl = "file://"+self.nr |
1054 |
- else: |
1055 |
- myurl = repository + self.config["GLSA_PREFIX"] + str(self.nr) + self.config["GLSA_SUFFIX"] |
1056 |
- self.parse(urlopen(myurl)) |
1057 |
- return None |
1058 |
- |
1059 |
- def parse(self, myfile): |
1060 |
- """ |
1061 |
- This method parses the XML file and sets up the internal data |
1062 |
- structures by calling the different helper functions in this |
1063 |
- module. |
1064 |
- |
1065 |
- @type myfile: String |
1066 |
- @param myfile: Filename to grab the XML data from |
1067 |
- @rtype: None |
1068 |
- @returns: None |
1069 |
- """ |
1070 |
- self.DOM = xml.dom.minidom.parse(myfile) |
1071 |
- if not self.DOM.doctype: |
1072 |
- raise GlsaTypeException(None) |
1073 |
- elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa.dtd": |
1074 |
- self.dtdversion = 0 |
1075 |
- elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa-2.dtd": |
1076 |
- self.dtdversion = 2 |
1077 |
- else: |
1078 |
- raise GlsaTypeException(self.DOM.doctype.systemId) |
1079 |
- myroot = self.DOM.getElementsByTagName("glsa")[0] |
1080 |
- if self.type == "id" and myroot.getAttribute("id") != self.nr: |
1081 |
- raise GlsaFormatException("filename and internal id don't match:" + myroot.getAttribute("id") + " != " + self.nr) |
1082 |
- |
1083 |
- # the simple (single, required, top-level, #PCDATA) tags first |
1084 |
- self.title = getText(myroot.getElementsByTagName("title")[0], format="strip") |
1085 |
- self.synopsis = getText(myroot.getElementsByTagName("synopsis")[0], format="strip") |
1086 |
- self.announced = format_date(getText(myroot.getElementsByTagName("announced")[0], format="strip")) |
1087 |
- |
1088 |
- # Support both formats of revised: |
1089 |
- # <revised>December 30, 2007: 02</revised> |
1090 |
- # <revised count="2">2007-12-30</revised> |
1091 |
- revisedEl = myroot.getElementsByTagName("revised")[0] |
1092 |
- self.revised = getText(revisedEl, format="strip") |
1093 |
- count = revisedEl.getAttribute("count") |
1094 |
- if not count: |
1095 |
- if self.revised.find(":") >= 0: |
1096 |
- (self.revised, count) = self.revised.split(":") |
1097 |
- else: |
1098 |
- count = 1 |
1099 |
- |
1100 |
- self.revised = format_date(self.revised) |
1101 |
- |
1102 |
- try: |
1103 |
- self.count = int(count) |
1104 |
- except ValueError: |
1105 |
- # TODO should this rais a GlsaFormatException? |
1106 |
- self.count = 1 |
1107 |
- |
1108 |
- # now the optional and 0-n toplevel, #PCDATA tags and references |
1109 |
- try: |
1110 |
- self.access = getText(myroot.getElementsByTagName("access")[0], format="strip") |
1111 |
- except IndexError: |
1112 |
- self.access = "" |
1113 |
- self.bugs = getMultiTagsText(myroot, "bug", format="strip") |
1114 |
- self.references = getMultiTagsText(myroot.getElementsByTagName("references")[0], "uri", format="keep") |
1115 |
- |
1116 |
- # and now the formatted text elements |
1117 |
- self.description = getText(myroot.getElementsByTagName("description")[0], format="xml") |
1118 |
- self.workaround = getText(myroot.getElementsByTagName("workaround")[0], format="xml") |
1119 |
- self.resolution = getText(myroot.getElementsByTagName("resolution")[0], format="xml") |
1120 |
- self.impact_text = getText(myroot.getElementsByTagName("impact")[0], format="xml") |
1121 |
- self.impact_type = myroot.getElementsByTagName("impact")[0].getAttribute("type") |
1122 |
- try: |
1123 |
- self.background = getText(myroot.getElementsByTagName("background")[0], format="xml") |
1124 |
- except IndexError: |
1125 |
- self.background = "" |
1126 |
- |
1127 |
- # finally the interesting tags (product, affected, package) |
1128 |
- self.glsatype = myroot.getElementsByTagName("product")[0].getAttribute("type") |
1129 |
- self.product = getText(myroot.getElementsByTagName("product")[0], format="strip") |
1130 |
- self.affected = myroot.getElementsByTagName("affected")[0] |
1131 |
- self.packages = {} |
1132 |
- for p in self.affected.getElementsByTagName("package"): |
1133 |
- name = p.getAttribute("name") |
1134 |
- if name not in self.packages: |
1135 |
- self.packages[name] = [] |
1136 |
- tmp = {} |
1137 |
- tmp["arch"] = p.getAttribute("arch") |
1138 |
- tmp["auto"] = (p.getAttribute("auto") == "yes") |
1139 |
- tmp["vul_vers"] = [makeVersion(v) for v in p.getElementsByTagName("vulnerable")] |
1140 |
- tmp["unaff_vers"] = [makeVersion(v) for v in p.getElementsByTagName("unaffected")] |
1141 |
- tmp["vul_atoms"] = [makeAtom(name, v) for v in p.getElementsByTagName("vulnerable")] |
1142 |
- tmp["unaff_atoms"] = [makeAtom(name, v) for v in p.getElementsByTagName("unaffected")] |
1143 |
- self.packages[name].append(tmp) |
1144 |
- # TODO: services aren't really used yet |
1145 |
- self.services = self.affected.getElementsByTagName("service") |
1146 |
- return None |
1147 |
- |
1148 |
- def dump(self, outstream=sys.stdout, encoding="utf-8"): |
1149 |
- """ |
1150 |
- Dumps a plaintext representation of this GLSA to I{outfile} or |
1151 |
- B{stdout} if it is ommitted. You can specify an alternate |
1152 |
- I{encoding} if needed (default is utf-8). |
1153 |
- |
1154 |
- @type outstream: File |
1155 |
- @param outfile: Stream that should be used for writing |
1156 |
- (defaults to sys.stdout) |
1157 |
- """ |
1158 |
- outstream = getattr(outstream, "buffer", outstream) |
1159 |
- outstream = codecs.getwriter(encoding)(outstream) |
1160 |
- width = int(self.config["PRINTWIDTH"]) |
1161 |
- outstream.write(center("GLSA %s: \n%s" % (self.nr, self.title), width)+"\n") |
1162 |
- outstream.write((width*"=")+"\n") |
1163 |
- outstream.write(wrap(self.synopsis, width, caption="Synopsis: ")+"\n") |
1164 |
- outstream.write("Announced on: %s\n" % self.announced) |
1165 |
- outstream.write("Last revised on: %s : %02d\n\n" % (self.revised, self.count)) |
1166 |
- if self.glsatype == "ebuild": |
1167 |
- for k in self.packages.keys(): |
1168 |
- pkg = self.packages[k] |
1169 |
- for path in pkg: |
1170 |
- vul_vers = ", ".join(path["vul_vers"]) |
1171 |
- unaff_vers = ", ".join(path["unaff_vers"]) |
1172 |
- outstream.write("Affected package: %s\n" % k) |
1173 |
- outstream.write("Affected archs: ") |
1174 |
- if path["arch"] == "*": |
1175 |
- outstream.write("All\n") |
1176 |
- else: |
1177 |
- outstream.write("%s\n" % path["arch"]) |
1178 |
- outstream.write("Vulnerable: %s\n" % vul_vers) |
1179 |
- outstream.write("Unaffected: %s\n\n" % unaff_vers) |
1180 |
- elif self.glsatype == "infrastructure": |
1181 |
- pass |
1182 |
- if len(self.bugs) > 0: |
1183 |
- outstream.write("\nRelated bugs: ") |
1184 |
- outstream.write(", ".join(self.bugs)) |
1185 |
- outstream.write("\n") |
1186 |
- if self.background: |
1187 |
- outstream.write("\n"+wrap(self.background, width, caption="Background: ")) |
1188 |
- outstream.write("\n"+wrap(self.description, width, caption="Description: ")) |
1189 |
- outstream.write("\n"+wrap(self.impact_text, width, caption="Impact: ")) |
1190 |
- outstream.write("\n"+wrap(self.workaround, width, caption="Workaround: ")) |
1191 |
- outstream.write("\n"+wrap(self.resolution, width, caption="Resolution: ")) |
1192 |
- myreferences = " ".join(r.replace(" ", SPACE_ESCAPE)+NEWLINE_ESCAPE for r in self.references) |
1193 |
- outstream.write("\n"+wrap(myreferences, width, caption="References: ")) |
1194 |
- outstream.write("\n") |
1195 |
- |
1196 |
- def isVulnerable(self): |
1197 |
- """ |
1198 |
- Tests if the system is affected by this GLSA by checking if any |
1199 |
- vulnerable package versions are installed. Also checks for affected |
1200 |
- architectures. |
1201 |
- |
1202 |
- @rtype: Boolean |
1203 |
- @returns: True if the system is affected, False if not |
1204 |
- """ |
1205 |
- rValue = False |
1206 |
- for k in self.packages.keys(): |
1207 |
- pkg = self.packages[k] |
1208 |
- for path in pkg: |
1209 |
- if path["arch"] == "*" or self.config["ARCH"] in path["arch"].split(): |
1210 |
- for v in path["vul_atoms"]: |
1211 |
- rValue = rValue \ |
1212 |
- or (None != getMinUpgrade([v,], path["unaff_atoms"])) |
1213 |
- return rValue |
1214 |
- |
1215 |
- def isInjected(self): |
1216 |
- """ |
1217 |
- Looks if the GLSA ID is in the GLSA checkfile to check if this |
1218 |
- GLSA should be marked as applied. |
1219 |
- |
1220 |
- @rtype: Boolean |
1221 |
- @returns: True if the GLSA is in the inject file, False if not |
1222 |
- """ |
1223 |
- if not os.access(self.config["CHECKFILE"], os.R_OK): |
1224 |
- return False |
1225 |
- aList = portage.grabfile(self.config["CHECKFILE"]) |
1226 |
- return (self.nr in aList) |
1227 |
- |
1228 |
- def inject(self): |
1229 |
- """ |
1230 |
- Puts the ID of this GLSA into the GLSA checkfile, so it won't |
1231 |
- show up on future checks. Should be called after a GLSA is |
1232 |
- applied or on explicit user request. |
1233 |
- |
1234 |
- @rtype: None |
1235 |
- @returns: None |
1236 |
- """ |
1237 |
- if not self.isInjected(): |
1238 |
- checkfile = open(_unicode_encode(self.config["CHECKFILE"], |
1239 |
- encoding=_encodings['fs']), mode="a+", |
1240 |
- encoding=_encodings['content']) |
1241 |
- checkfile.write(self.nr+"\n") |
1242 |
- checkfile.close() |
1243 |
- return None |
1244 |
- |
1245 |
- def getMergeList(self, least_change=True): |
1246 |
- """ |
1247 |
- Returns the list of package-versions that have to be merged to |
1248 |
- apply this GLSA properly. The versions are as low as possible |
1249 |
- while avoiding downgrades (see L{getMinUpgrade}). |
1250 |
- |
1251 |
- @type least_change: Boolean |
1252 |
- @param least_change: True if the smallest possible upgrade should be selected, |
1253 |
- False for an emerge-like algorithm |
1254 |
- @rtype: List of Strings |
1255 |
- @return: list of package-versions that have to be merged |
1256 |
- """ |
1257 |
- return list(set(update for (vuln, update) in self.getAffectionTable(least_change) if update)) |
1258 |
- |
1259 |
- def getAffectionTable(self, least_change=True): |
1260 |
- """ |
1261 |
- Will initialize the self.systemAffection list of |
1262 |
- atoms installed on the system that are affected |
1263 |
- by this GLSA, and the atoms that are minimal upgrades. |
1264 |
- """ |
1265 |
- systemAffection = [] |
1266 |
- for pkg in self.packages.keys(): |
1267 |
- for path in self.packages[pkg]: |
1268 |
- update = getMinUpgrade(path["vul_atoms"], path["unaff_atoms"], minimize=least_change) |
1269 |
- if update: |
1270 |
- systemAffection.extend(update) |
1271 |
- return systemAffection |
1272 |
diff --git a/setup.py b/setup.py |
1273 |
index 031afd3..777b7f3 100755 |
1274 |
--- a/setup.py |
1275 |
+++ b/setup.py |
1276 |
@@ -42,7 +42,6 @@ python_scripts = [(os.path.join(cwd, path), '__version__ = ') for path in ( |
1277 |
'bin/eclean-dist', |
1278 |
'bin/eclean-pkg', |
1279 |
'bin/epkginfo', |
1280 |
- 'bin/glsa-check', |
1281 |
'pym/gentoolkit/eclean/cli.py', |
1282 |
'pym/gentoolkit/enalyze/__init__.py', |
1283 |
'pym/gentoolkit/ekeyword/ekeyword.py', |
1284 |
@@ -61,7 +60,6 @@ manpages = [(os.path.join(cwd, path[0]), path[1]) for path in ( |
1285 |
('man/eread.1', 'EREAD'), |
1286 |
('man/eshowkw.1', 'ESHOWKW'), |
1287 |
('man/euse.1', 'EUSE'), |
1288 |
- ('man/glsa-check.1', 'GLSA-CHECK'), |
1289 |
('man/imlate.1', 'IMLATE'), |
1290 |
('man/revdep-rebuild.1', 'REVDEP-REBUILD'), |
1291 |
)] |
1292 |
-- |
1293 |
2.21.0 |