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 all unapplied GLSA"],
28 ["-d", "--dump", "--print", "show all information about the given GLSA"],
29 ["-t", "--test", "test if this system is affected by the given GLSA"],
30 ["-p", "--pretend", "show the necessary commands to apply this GLSA"],
31 ["-f", "--fix", "try to auto-apply this GLSA (experimental)"],
32 ["-i", "--inject", "inject the given GLSA into the glsa_injected file"],
33 ["-n", "--nocolor", "disable colors (option)"],
34 ["-e", "--emergelike", "do not use a least-change algorithm (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":
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', 'new' 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]
180 glsalist = completelist
182 if "affected" in params:
183 # replaced completelist with todolist on request of wschlich
186 myglsa = Glsa(x, glsaconfig)
187 except (GlsaTypeException, GlsaFormatException), e:
189 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
191 if myglsa.isVulnerable():
193 params.remove("affected")
195 # remove invalid parameters
197 if not (p in completelist or os.path.exists(p)):
198 sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p))
201 glsalist.extend([g for g in params if g not in glsalist])
203 def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr, encoding="utf-8"):
204 fd1 = codecs.getwriter(encoding)(fd1)
205 fd2 = codecs.getwriter(encoding)(fd2)
207 fd2.write(white("[A]")+" means this GLSA was marked as applied (injected),\n")
208 fd2.write(green("[U]")+" means the system is not affected and\n")
209 fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
212 for myid in myglsalist:
214 myglsa = Glsa(myid, glsaconfig)
215 except (GlsaTypeException, GlsaFormatException), e:
217 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
219 if myglsa.isInjected():
222 elif myglsa.isVulnerable():
230 access = ("[%-8s] " % myglsa.access)
234 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
236 for pkg in myglsa.packages.keys()[:3]:
237 fd1.write(" " + pkg + " ")
238 if len(myglsa.packages) > 3:
241 for pkg in myglsa.packages.keys():
242 mylist = vardb.match(portage.dep_getkey(str(pkg)))
244 pkg = color(" ".join(mylist))
245 fd1.write(" " + pkg + " ")
249 fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
254 sys.exit(summarylist(glsalist))
256 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
257 if mode in ["dump", "fix", "inject", "pretend"]:
258 for myid in glsalist:
260 myglsa = Glsa(myid, glsaconfig)
261 except (GlsaTypeException, GlsaFormatException), e:
263 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
268 sys.stdout.write("Fixing GLSA "+myid+"\n")
269 if not myglsa.isVulnerable():
270 sys.stdout.write(">>> no vulnerable packages installed\n")
272 mergelist = myglsa.getMergeList(least_change=least_change)
274 sys.stdout.write(">>> cannot fix GLSA, no unaffected packages available\n")
276 for pkg in mergelist:
277 sys.stdout.write(">>> merging "+pkg+"\n")
278 # using emerge for the actual merging as it contains the dependency
279 # code and we want to be consistent in behaviour. Also this functionality
280 # will be integrated in emerge later, so it shouldn't hurt much.
281 emergecmd = "emerge --oneshot " + glsaconfig["EMERGE_OPTS"] + " =" + pkg
283 sys.stderr.write(emergecmd+"\n")
284 exitcode = os.system(emergecmd)
285 # system() returns the exitcode in the high byte of a 16bit integer
291 sys.stdout.write("\n")
292 elif mode == "pretend":
293 sys.stdout.write("Checking GLSA "+myid+"\n")
294 if not myglsa.isVulnerable():
295 sys.stdout.write(">>> no vulnerable packages installed\n")
298 for (vuln, update) in myglsa.getAffectionTable(least_change=least_change):
299 mergedict.setdefault(update, []).append(vuln)
301 # first, extract the atoms that cannot be upgraded (where key == "")
304 no_upgrades = mergedict[""]
307 # see if anything is left that can be upgraded
309 sys.stdout.write(">>> Updates that will be performed:\n")
310 for (upd, vuln) in mergedict.iteritems():
311 sys.stdout.write(" " + green(upd) + " (vulnerable: " + red(", ".join(vuln)) + ")\n")
314 sys.stdout.write(">>> No upgrade path exists for these packages:\n")
315 sys.stdout.write(" " + red(", ".join(no_upgrades)) + "\n")
316 sys.stdout.write("\n")
317 elif mode == "inject":
318 sys.stdout.write("injecting " + myid + "\n")
322 # test is a bit different as Glsa.test() produces no output
325 for myid in glsalist:
327 myglsa = Glsa(myid, glsaconfig)
328 except (GlsaTypeException, GlsaFormatException), e:
330 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
332 if myglsa.isVulnerable():
333 outputlist.append(str(myglsa.nr))
334 if len(outputlist) > 0:
335 sys.stderr.write("This system is affected by the following GLSAs:\n")
337 summarylist(outputlist)
339 sys.stdout.write("\n".join(outputlist)+"\n")
341 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
344 # mail mode as requested by solar
347 import portage.mail as portage_mail
352 from StringIO import StringIO
354 from email.mime.text import MIMEText
356 from email.MIMEText import MIMEText
358 # color doesn't make any sense for mail
361 if "PORTAGE_ELOG_MAILURI" in glsaconfig:
362 myrecipient = glsaconfig["PORTAGE_ELOG_MAILURI"].split()[0]
364 myrecipient = "root@localhost"
366 if "PORTAGE_ELOG_MAILFROM" in glsaconfig:
367 myfrom = glsaconfig["PORTAGE_ELOG_MAILFROM"]
369 myfrom = "glsa-check"
371 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
373 # need a file object for summarylist()
375 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
376 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
377 summarylist(glsalist, fd1=myfd, fd2=myfd)
378 summary = str(myfd.getvalue())
382 for myid in glsalist:
384 myglsa = Glsa(myid, glsaconfig)
385 except (GlsaTypeException, GlsaFormatException), e:
387 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
390 myglsa.dump(outstream=myfd)
391 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
394 if glsalist or not quiet:
395 mymessage = portage_mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
396 portage_mail.send_mail(glsaconfig, mymessage)
400 # something wrong here, all valid paths are covered with sys.exit()
401 sys.stderr.write("nothing more to do\n")