4 # This program is licensed under the GPL, version 2
12 sys.path.insert(0, "/usr/lib/portage/pym")
16 from portage.output import *
20 from getopt import getopt, GetoptError
22 __program__ = "glsa-check"
23 __author__ = "Marius Mauch <genone@gentoo.org>"
24 __version__ = open("/usr/share/gentoolkit/VERSION").read().strip()
27 ["-l", "--list", "list the GLSAs"],
28 ["-d", "--dump", "--print", "show all information about the GLSAs"],
29 ["-t", "--test", "test if this system is affected by the GLSAs"],
30 ["-p", "--pretend", "show the necessary steps to apply the GLSAs"],
31 ["-f", "--fix", "try to auto-apply the GLSAs (experimental)"],
32 ["-i", "--inject", "inject the given GLSA into the glsa_injected file"],
33 ["-n", "--nocolor", "disable colors (option)"],
34 ["-e", "--emergelike", "upgrade to latest version (not least-change, option)"],
35 ["-h", "--help", "show this help message"],
36 ["-V", "--version", "some information about this tool"],
37 ["-v", "--verbose", "print more information (option)"],
38 ["-c", "--cve", "show CVE ids in listing mode (option)"],
39 ["-q", "--quiet", "be less verbose and do not send empty mail (option)"],
40 ["-m", "--mail", "send a mail with the given GLSAs to the administrator"],
43 # print a warning as this is beta code (but proven by now, so no more warning)
44 #sys.stderr.write("WARNING: This tool is completely new and not very tested, so it should not be\n")
45 #sys.stderr.write("used on production systems. It's mainly a test tool for the new GLSA release\n")
46 #sys.stderr.write("and distribution system, it's functionality will later be merged into emerge\n")
47 #sys.stderr.write("and equery.\n")
48 #sys.stderr.write("Please read http://www.gentoo.org/proj/en/portage/glsa-integration.xml\n")
49 #sys.stderr.write("before using this tool AND before reporting a bug.\n\n")
55 args, params = getopt(sys.argv[1:], "".join([o[0][1] for o in optionmap]), \
56 [x[2:] for x in reduce(lambda x,y: x+y, [z[1:-1] for z in optionmap])])
57 args = [a for a,b in args]
59 for option in ["--nocolor", "-n"]:
65 for option in ["--verbose", "-v"]:
71 for option in ["--cve", "-c"]:
77 for option in ["--emergelike", "-e"]:
83 for option in ["--quiet", "-q"]:
91 sys.stderr.write("no option given: what should I do ?\n")
94 sys.stderr.write("please use only one command per call\n")
97 # in what mode are we ?
100 if args in [o for o in m[:-1]]:
103 except GetoptError, e:
104 sys.stderr.write("unknown option given: ")
105 sys.stderr.write(str(e)+"\n")
108 # we need a set of glsa for most operation modes
109 if len(params) <= 0 and mode in ["fix", "test", "pretend", "dump", "inject", "mail"]:
110 sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n")
111 sys.stderr.write("If you want to run on all GLSA please tell me so \n")
112 sys.stderr.write("(specify \"all\" as parameter)\n\n")
114 elif len(params) <= 0 and mode == "list":
115 params.append("affected")
118 if mode == "help" or mode == "HELP":
119 msg = "Syntax: glsa-check <option> [glsa-list]\n\n"
121 msg += m[0] + "\t" + m[1] + " \t: " + m[-1] + "\n"
123 msg += "\t" + o + "\n"
124 msg += "\nglsa-list can contain an arbitrary number of GLSA ids, \n"
125 msg += "filenames containing GLSAs or the special identifiers \n"
126 msg += "'all' and 'affected'\n"
128 sys.stdout.write(msg)
131 sys.stderr.write("\n" + msg)
134 # we need root privileges for write access
135 if mode in ["fix", "inject"] and os.geteuid() != 0:
136 sys.stderr.write(__program__ + ": root access is needed for \""+mode+"\" mode\n")
139 # show version and copyright information
140 if mode == "version":
141 sys.stderr.write("%(program)s (%(version)s)\n" % {
142 "program": __program__,
143 "version": __version__
145 sys.stderr.write("Author: %s\n" % __author__)
146 sys.stderr.write("This program is licensed under the GPL, version 2\n")
149 # delay this for speed increase
150 from gentoolkit.glsa import *
152 glsaconfig = checkconfig(portage.config(clone=portage.settings))
155 glsaconfig["EMERGE_OPTS"] += " --quiet"
157 vardb = portage.db["/"]["vartree"].dbapi
158 portdb = portage.db["/"]["porttree"].dbapi
160 # Check that we really have a glsa dir to work on
161 if not (os.path.exists(glsaconfig["GLSA_DIR"]) and os.path.isdir(glsaconfig["GLSA_DIR"])):
162 sys.stderr.write(red("ERROR")+": GLSA_DIR %s doesn't exist. Please fix this.\n" % glsaconfig["GLSA_DIR"])
166 completelist = get_glsa_list(glsaconfig["GLSA_DIR"], glsaconfig)
168 if os.access(glsaconfig["CHECKFILE"], os.R_OK):
169 checklist = [line.strip() for line in open(glsaconfig["CHECKFILE"], "r").readlines()]
172 todolist = [e for e in completelist if e not in checklist]
177 sys.stderr.write("Warning: The 'new' glsa-list target has been removed, using 'affected'.\n")
178 params.append("affected")
181 glsalist = completelist
184 if "affected" in params:
187 myglsa = Glsa(x, glsaconfig)
188 except (GlsaTypeException, GlsaFormatException), e:
190 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
192 if myglsa.isVulnerable():
194 params.remove("affected")
196 # remove invalid parameters
198 if not (p in completelist or os.path.exists(p)):
199 sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p))
202 glsalist.extend([g for g in params if g not in glsalist])
204 def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr, encoding="utf-8"):
205 fd1 = codecs.getwriter(encoding)(fd1)
206 fd2 = codecs.getwriter(encoding)(fd2)
208 fd2.write(white("[A]")+" means this GLSA was marked as applied (injected),\n")
209 fd2.write(green("[U]")+" means the system is not affected and\n")
210 fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
213 for myid in myglsalist:
215 myglsa = Glsa(myid, glsaconfig)
216 except (GlsaTypeException, GlsaFormatException), e:
218 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
220 if myglsa.isInjected():
223 elif myglsa.isVulnerable():
231 access = ("[%-8s] " % myglsa.access)
235 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
237 for pkg in myglsa.packages.keys()[:3]:
238 fd1.write(" " + pkg + " ")
239 if len(myglsa.packages) > 3:
242 for pkg in myglsa.packages.keys():
243 mylist = vardb.match(portage.dep_getkey(str(pkg)))
245 pkg = color(" ".join(mylist))
246 fd1.write(" " + pkg + " ")
250 fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
255 sys.exit(summarylist(glsalist))
257 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
258 if mode in ["dump", "fix", "inject", "pretend"]:
259 for myid in glsalist:
261 myglsa = Glsa(myid, glsaconfig)
262 except (GlsaTypeException, GlsaFormatException), e:
264 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
269 sys.stdout.write("Fixing GLSA "+myid+"\n")
270 if not myglsa.isVulnerable():
271 sys.stdout.write(">>> no vulnerable packages installed\n")
273 mergelist = myglsa.getMergeList(least_change=least_change)
275 sys.stdout.write(">>> cannot fix GLSA, no unaffected packages available\n")
277 for pkg in mergelist:
278 sys.stdout.write(">>> merging "+pkg+"\n")
279 # using emerge for the actual merging as it contains the dependency
280 # code and we want to be consistent in behaviour. Also this functionality
281 # will be integrated in emerge later, so it shouldn't hurt much.
282 emergecmd = "emerge --oneshot " + glsaconfig["EMERGE_OPTS"] + " =" + pkg
284 sys.stderr.write(emergecmd+"\n")
285 exitcode = os.system(emergecmd)
286 # system() returns the exitcode in the high byte of a 16bit integer
292 sys.stdout.write("\n")
293 elif mode == "pretend":
294 sys.stdout.write("Checking GLSA "+myid+"\n")
295 if not myglsa.isVulnerable():
296 sys.stdout.write(">>> no vulnerable packages installed\n")
299 for (vuln, update) in myglsa.getAffectionTable(least_change=least_change):
300 mergedict.setdefault(update, []).append(vuln)
302 # first, extract the atoms that cannot be upgraded (where key == "")
305 no_upgrades = mergedict[""]
308 # see if anything is left that can be upgraded
310 sys.stdout.write(">>> Updates that will be performed:\n")
311 for (upd, vuln) in mergedict.iteritems():
312 sys.stdout.write(" " + green(upd) + " (vulnerable: " + red(", ".join(vuln)) + ")\n")
315 sys.stdout.write(">>> No upgrade path exists for these packages:\n")
316 sys.stdout.write(" " + red(", ".join(no_upgrades)) + "\n")
317 sys.stdout.write("\n")
318 elif mode == "inject":
319 sys.stdout.write("injecting " + myid + "\n")
323 # test is a bit different as Glsa.test() produces no output
326 for myid in glsalist:
328 myglsa = Glsa(myid, glsaconfig)
329 except (GlsaTypeException, GlsaFormatException), e:
331 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
333 if myglsa.isVulnerable():
334 outputlist.append(str(myglsa.nr))
335 if len(outputlist) > 0:
336 sys.stderr.write("This system is affected by the following GLSAs:\n")
338 summarylist(outputlist)
340 sys.stdout.write("\n".join(outputlist)+"\n")
342 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
345 # mail mode as requested by solar
348 import portage.mail as portage_mail
353 from StringIO import StringIO
355 from email.mime.text import MIMEText
357 from email.MIMEText import MIMEText
359 # color doesn't make any sense for mail
362 if "PORTAGE_ELOG_MAILURI" in glsaconfig:
363 myrecipient = glsaconfig["PORTAGE_ELOG_MAILURI"].split()[0]
365 myrecipient = "root@localhost"
367 if "PORTAGE_ELOG_MAILFROM" in glsaconfig:
368 myfrom = glsaconfig["PORTAGE_ELOG_MAILFROM"]
370 myfrom = "glsa-check"
372 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
374 # need a file object for summarylist()
376 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
377 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
378 summarylist(glsalist, fd1=myfd, fd2=myfd)
379 summary = str(myfd.getvalue())
383 for myid in glsalist:
385 myglsa = Glsa(myid, glsaconfig)
386 except (GlsaTypeException, GlsaFormatException), e:
388 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
391 myglsa.dump(outstream=myfd)
392 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
395 if glsalist or not quiet:
396 mymessage = portage_mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
397 portage_mail.send_mail(glsaconfig, mymessage)
401 # something wrong here, all valid paths are covered with sys.exit()
402 sys.stderr.write("nothing more to do\n")