4 # This program is licensed under the GPL, version 2
11 sys.path.insert(0, "/usr/lib/portage/pym")
15 from portage.output import *
19 from getopt import getopt, GetoptError
21 __program__ = "glsa-check"
22 __author__ = "Marius Mauch <genone@gentoo.org>"
23 __version__ = open("/etc/gentoolkit-version").read().strip()
26 ["-l", "--list", "list all unapplied GLSA"],
27 ["-d", "--dump", "--print", "show all information about the given GLSA"],
28 ["-t", "--test", "test if this system is affected by the given GLSA"],
29 ["-p", "--pretend", "show the necessary commands to apply this GLSA"],
30 ["-f", "--fix", "try to auto-apply this GLSA (experimental)"],
31 ["-i", "--inject", "inject the given GLSA into the checkfile"],
32 ["-n", "--nocolor", "disable colors (option)"],
33 ["-e", "--emergelike", "do not use a least-change algorithm (option)"],
34 ["-h", "--help", "show this help message"],
35 ["-V", "--version", "some information about this tool"],
36 ["-v", "--verbose", "print more information (option)"],
37 ["-c", "--cve", "show CAN ids in listing mode (option)"],
38 ["-m", "--mail", "send a mail with the given GLSAs to the administrator"]
41 # print a warning as this is beta code (but proven by now, so no more warning)
42 #sys.stderr.write("WARNING: This tool is completely new and not very tested, so it should not be\n")
43 #sys.stderr.write("used on production systems. It's mainly a test tool for the new GLSA release\n")
44 #sys.stderr.write("and distribution system, it's functionality will later be merged into emerge\n")
45 #sys.stderr.write("and equery.\n")
46 #sys.stderr.write("Please read http://www.gentoo.org/proj/en/portage/glsa-integration.xml\n")
47 #sys.stderr.write("before using this tool AND before reporting a bug.\n\n")
53 args, params = getopt(sys.argv[1:], "".join([o[0][1] for o in optionmap]), \
54 [x[2:] for x in reduce(lambda x,y: x+y, [z[1:-1] for z in optionmap])])
55 # ["dump", "print", "list", "pretend", "fix", "inject", "help", "verbose", "version", "test", "nocolor", "cve", "mail"])
56 args = [a for a,b in args]
58 for option in ["--nocolor", "-n"]:
64 for option in ["--verbose", "-v"]:
70 for option in ["--cve", "-c"]:
76 for option in ["--emergelike", "-e"]:
83 sys.stderr.write("no option given: what should I do ?\n")
86 sys.stderr.write("please use only one command per call\n")
89 # in what mode are we ?
92 if args in [o for o in m[:-1]]:
95 except GetoptError, e:
96 sys.stderr.write("unknown option given: ")
97 sys.stderr.write(str(e)+"\n")
100 # we need a set of glsa for most operation modes
101 if len(params) <= 0 and mode in ["fix", "test", "pretend", "dump", "inject", "mail"]:
102 sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n")
103 sys.stderr.write("If you want to run on all GLSA please tell me so \n")
104 sys.stderr.write("(specify \"all\" as parameter)\n\n")
106 elif len(params) <= 0 and mode == "list":
110 if mode == "help" or mode == "HELP":
111 msg = "Syntax: glsa-check <option> [glsa-list]\n\n"
113 msg += m[0] + "\t" + m[1] + " \t: " + m[-1] + "\n"
115 msg += "\t" + o + "\n"
116 msg += "\nglsa-list can contain an arbitrary number of GLSA ids, \n"
117 msg += "filenames containing GLSAs or the special identifiers \n"
118 msg += "'all', 'new' and 'affected'\n"
120 sys.stdout.write(msg)
123 sys.stderr.write("\n" + msg)
126 # we need root priviledges for write access
127 if mode in ["fix", "inject"] and os.geteuid() != 0:
128 sys.stderr.write(__program__ + ": root access is needed for \""+mode+"\" mode\n")
131 # show version and copyright information
132 if mode == "version":
133 sys.stderr.write("%(program)s (%(version)s)\n" % {
134 "program": __program__,
135 "version": __version__
137 sys.stderr.write("Author: %s\n" % __author__)
138 sys.stderr.write("This program is licensed under the GPL, version 2\n")
141 # delay this for speed increase
142 from gentoolkit.glsa import *
144 glsaconfig = checkconfig(portage.config(clone=portage.settings))
146 vardb = portage.db["/"]["vartree"].dbapi
147 portdb = portage.db["/"]["porttree"].dbapi
149 # Check that we really have a glsa dir to work on
150 if not (os.path.exists(glsaconfig["GLSA_DIR"]) and os.path.isdir(glsaconfig["GLSA_DIR"])):
151 sys.stderr.write(red("ERROR")+": GLSA_DIR %s doesn't exist. Please fix this.\n" % glsaconfig["GLSA_DIR"])
155 completelist = get_glsa_list(glsaconfig["GLSA_DIR"], glsaconfig)
157 if os.access(glsaconfig["CHECKFILE"], os.R_OK):
158 checklist = [line.strip() for line in open(glsaconfig["CHECKFILE"], "r").readlines()]
161 todolist = [e for e in completelist if e not in checklist]
169 glsalist = completelist
171 if "affected" in params:
172 # replaced completelist with todolist on request of wschlich
175 myglsa = Glsa(x, glsaconfig)
176 except (GlsaTypeException, GlsaFormatException), e:
178 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
180 if myglsa.isVulnerable():
182 params.remove("affected")
184 # remove invalid parameters
186 if not (p in completelist or os.path.exists(p)):
187 sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p))
190 glsalist.extend([g for g in params if g not in glsalist])
192 def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr):
193 fd2.write(white("[A]")+" means this GLSA was already applied,\n")
194 fd2.write(green("[U]")+" means the system is not affected and\n")
195 fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
197 for myid in myglsalist:
199 myglsa = Glsa(myid, glsaconfig)
200 except (GlsaTypeException, GlsaFormatException), e:
202 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
204 if myglsa.isApplied():
207 elif myglsa.isVulnerable():
215 access = ("[%-8s] " % myglsa.access)
219 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
221 for pkg in myglsa.packages.keys()[:3]:
222 fd1.write(" " + pkg + " ")
223 if len(myglsa.packages) > 3:
226 for pkg in myglsa.packages.keys():
227 mylist = vardb.match(portage.dep_getkey(str(pkg)))
229 pkg = color(" ".join(mylist))
230 fd1.write(" " + pkg + " ")
234 fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
239 sys.exit(summarylist(glsalist))
241 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
242 if mode in ["dump", "fix", "inject", "pretend"]:
243 for myid in glsalist:
245 myglsa = Glsa(myid, glsaconfig)
246 except (GlsaTypeException, GlsaFormatException), e:
248 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
253 sys.stdout.write("fixing "+myid+"\n")
254 mergelist = myglsa.getMergeList(least_change=least_change)
255 for pkg in mergelist:
256 sys.stdout.write(">>> merging "+pkg+"\n")
257 # using emerge for the actual merging as it contains the dependency
258 # code and we want to be consistent in behaviour. Also this functionality
259 # will be integrated in emerge later, so it shouldn't hurt much.
260 emergecmd = "emerge --oneshot " + glsaconfig["EMERGE_OPTS"] + " =" + pkg
262 sys.stderr.write(emergecmd+"\n")
263 exitcode = os.system(emergecmd)
264 # system() returns the exitcode in the high byte of a 16bit integer
270 sys.stdout.write("\n")
272 elif mode == "pretend":
273 sys.stdout.write("Checking GLSA "+myid+"\n")
274 mergelist = myglsa.getMergeList(least_change=least_change)
276 sys.stdout.write("The following updates will be performed for this GLSA:\n")
277 for pkg in mergelist:
279 for x in vardb.match(portage.dep_getkey(pkg)):
280 if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]):
283 raise ValueError("could not find old version for package %s" % pkg)
284 oldver = oldver[len(portage.dep_getkey(oldver))+1:]
285 sys.stdout.write(" " + pkg + " (" + oldver + ")\n")
287 sys.stdout.write("Nothing to do for this GLSA\n")
288 sys.stdout.write("\n")
289 elif mode == "inject":
290 sys.stdout.write("injecting " + myid + "\n")
294 # test is a bit different as Glsa.test() produces no output
297 for myid in glsalist:
299 myglsa = Glsa(myid, glsaconfig)
300 except (GlsaTypeException, GlsaFormatException), e:
302 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
304 if myglsa.isVulnerable():
306 outputlist.append(str(myglsa.nr)+" ( "+myglsa.title+" ) ")
308 outputlist.append(str(myglsa.nr))
309 if len(outputlist) > 0:
310 sys.stderr.write("This system is affected by the following GLSAs:\n")
311 sys.stdout.write("\n".join(outputlist)+"\n")
313 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
316 # mail mode as requested by solar
319 import portage.mail as portage_mail
324 from StringIO import StringIO
326 from email.mime.text import MIMEText
328 from email.MIMEText import MIMEText
330 # color doesn't make any sense for mail
333 if "PORTAGE_ELOG_MAILURI" in glsaconfig:
334 myrecipient = glsaconfig["PORTAGE_ELOG_MAILURI"].split()[0]
336 myrecipient = "root@localhost"
338 if "PORTAGE_ELOG_MAILFROM" in glsaconfig:
339 myfrom = glsaconfig["PORTAGE_ELOG_MAILFROM"]
341 myfrom = "glsa-check"
343 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
345 # need a file object for summarylist()
347 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
348 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
349 summarylist(glsalist, fd1=myfd, fd2=myfd)
350 summary = str(myfd.getvalue())
354 for myid in glsalist:
356 myglsa = Glsa(myid, glsaconfig)
357 except (GlsaTypeException, GlsaFormatException), e:
359 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
362 myglsa.dump(outstream=myfd)
363 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
366 mymessage = portage_mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
367 portage_mail.send_mail(glsaconfig, mymessage)
371 # something wrong here, all valid paths are covered with sys.exit()
372 sys.stderr.write("nothing more to do\n")