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("/etc/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 checkfile"],
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 already applied,\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.isApplied():
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 "+myid+"\n")
269 mergelist = myglsa.getMergeList(least_change=least_change)
270 for pkg in mergelist:
271 sys.stdout.write(">>> merging "+pkg+"\n")
272 # using emerge for the actual merging as it contains the dependency
273 # code and we want to be consistent in behaviour. Also this functionality
274 # will be integrated in emerge later, so it shouldn't hurt much.
275 emergecmd = "emerge --oneshot " + glsaconfig["EMERGE_OPTS"] + " =" + pkg
277 sys.stderr.write(emergecmd+"\n")
278 exitcode = os.system(emergecmd)
279 # system() returns the exitcode in the high byte of a 16bit integer
285 sys.stdout.write("\n")
287 elif mode == "pretend":
288 sys.stdout.write("Checking GLSA "+myid+"\n")
289 mergelist = myglsa.getMergeList(least_change=least_change)
291 sys.stdout.write("The following updates will be performed for this GLSA:\n")
292 for pkg in mergelist:
294 for x in vardb.match(portage.dep_getkey(pkg)):
295 if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]):
298 raise ValueError("could not find old version for package %s" % pkg)
299 oldver = oldver[len(portage.dep_getkey(oldver))+1:]
300 sys.stdout.write(" " + pkg + " (" + oldver + ")\n")
302 sys.stdout.write("Nothing to do for this GLSA\n")
303 sys.stdout.write("\n")
304 elif mode == "inject":
305 sys.stdout.write("injecting " + myid + "\n")
309 # test is a bit different as Glsa.test() produces no output
312 for myid in glsalist:
314 myglsa = Glsa(myid, glsaconfig)
315 except (GlsaTypeException, GlsaFormatException), e:
317 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
319 if myglsa.isVulnerable():
320 outputlist.append(str(myglsa.nr))
321 if len(outputlist) > 0:
322 sys.stderr.write("This system is affected by the following GLSAs:\n")
324 summarylist(outputlist)
326 sys.stdout.write("\n".join(outputlist)+"\n")
328 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
331 # mail mode as requested by solar
334 import portage.mail as portage_mail
339 from StringIO import StringIO
341 from email.mime.text import MIMEText
343 from email.MIMEText import MIMEText
345 # color doesn't make any sense for mail
348 if "PORTAGE_ELOG_MAILURI" in glsaconfig:
349 myrecipient = glsaconfig["PORTAGE_ELOG_MAILURI"].split()[0]
351 myrecipient = "root@localhost"
353 if "PORTAGE_ELOG_MAILFROM" in glsaconfig:
354 myfrom = glsaconfig["PORTAGE_ELOG_MAILFROM"]
356 myfrom = "glsa-check"
358 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
360 # need a file object for summarylist()
362 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
363 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
364 summarylist(glsalist, fd1=myfd, fd2=myfd)
365 summary = str(myfd.getvalue())
369 for myid in glsalist:
371 myglsa = Glsa(myid, glsaconfig)
372 except (GlsaTypeException, GlsaFormatException), e:
374 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
377 myglsa.dump(outstream=myfd)
378 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
381 if glsalist or not quiet:
382 mymessage = portage_mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
383 portage_mail.send_mail(glsaconfig, mymessage)
387 # something wrong here, all valid paths are covered with sys.exit()
388 sys.stderr.write("nothing more to do\n")