4 # This program is licensed under the GPL, version 2
8 sys.path.insert(0, "/usr/lib/gentoolkit/pym")
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>"
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 CAN ids in listing mode (option)"],
39 ["-m", "--mail", "send a mail with the given GLSAs to the administrator"]
42 # print a warning as this is beta code (but proven by now, so no more warning)
43 #sys.stderr.write("WARNING: This tool is completely new and not very tested, so it should not be\n")
44 #sys.stderr.write("used on production systems. It's mainly a test tool for the new GLSA release\n")
45 #sys.stderr.write("and distribution system, it's functionality will later be merged into emerge\n")
46 #sys.stderr.write("and equery.\n")
47 #sys.stderr.write("Please read http://www.gentoo.org/proj/en/portage/glsa-integration.xml\n")
48 #sys.stderr.write("before using this tool AND before reporting a bug.\n\n")
54 args, params = getopt(sys.argv[1:], "".join([o[0][1] for o in optionmap]), \
55 [x[2:] for x in reduce(lambda x,y: x+y, [z[1:-1] for z in optionmap])])
56 # ["dump", "print", "list", "pretend", "fix", "inject", "help", "verbose", "version", "test", "nocolor", "cve", "mail"])
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"]:
84 sys.stderr.write("no option given: what should I do ?\n")
87 sys.stderr.write("please use only one command per call\n")
90 # in what mode are we ?
93 if args in [o for o in m[:-1]]:
96 except GetoptError, e:
97 sys.stderr.write("unknown option given: ")
98 sys.stderr.write(str(e)+"\n")
101 # we need a set of glsa for most operation modes
102 if len(params) <= 0 and mode in ["fix", "test", "pretend", "dump", "inject", "mail"]:
103 sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n")
104 sys.stderr.write("If you want to run on all GLSA please tell me so \n")
105 sys.stderr.write("(specify \"all\" as parameter)\n\n")
107 elif len(params) <= 0 and mode == "list":
112 sys.stderr.write("\nSyntax: glsa-check <option> [glsa-list]\n\n")
114 sys.stderr.write(m[0] + "\t" + m[1] + " \t: " + m[-1] + "\n")
116 sys.stderr.write("\t" + o + "\n")
117 sys.stderr.write("\nglsa-list can contain an arbitrary number of GLSA ids, \n")
118 sys.stderr.write("filenames containing GLSAs or the special identifiers \n")
119 sys.stderr.write("'all', 'new' and 'affected'\n")
122 # we need root priviledges for write access
123 if mode in ["fix", "inject"] and os.geteuid() != 0:
124 sys.stderr.write("\nThis tool needs root access to "+mode+" this GLSA\n\n")
127 # show version and copyright information
128 if mode == "version":
129 sys.stderr.write("\n"+ __program__ + ", version " + __version__ + "\n")
130 sys.stderr.write("Author: " + __author__ + "\n")
131 sys.stderr.write("This program is licensed under the GPL, version 2\n\n")
134 # delay this for speed increase
137 glsaconfig = checkconfig(portage.config(clone=portage.settings))
139 vardb = portage.db["/"]["vartree"].dbapi
140 portdb = portage.db["/"]["porttree"].dbapi
142 # Check that we really have a glsa dir to work on
143 if not (os.path.exists(glsaconfig["GLSA_DIR"]) and os.path.isdir(glsaconfig["GLSA_DIR"])):
144 sys.stderr.write(red("ERROR")+": GLSA_DIR %s doesn't exist. Please fix this.\n" % glsaconfig["GLSA_DIR"])
148 completelist = get_glsa_list(glsaconfig["GLSA_DIR"], glsaconfig)
150 if os.access(glsaconfig["CHECKFILE"], os.R_OK):
151 checklist = [line.strip() for line in open(glsaconfig["CHECKFILE"], "r").readlines()]
154 todolist = [e for e in completelist if e not in checklist]
162 glsalist = completelist
164 if "affected" in params:
165 # replaced completelist with todolist on request of wschlich
168 myglsa = Glsa(x, glsaconfig)
169 except (GlsaTypeException, GlsaFormatException), e:
171 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
173 if myglsa.isVulnerable():
175 params.remove("affected")
177 # remove invalid parameters
179 if not (p in completelist or os.path.exists(p)):
180 sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p))
183 glsalist.extend([g for g in params if g not in glsalist])
185 def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr):
186 fd2.write(white("[A]")+" means this GLSA was already applied,\n")
187 fd2.write(green("[U]")+" means the system is not affected and\n")
188 fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
190 for myid in myglsalist:
192 myglsa = Glsa(myid, glsaconfig)
193 except (GlsaTypeException, GlsaFormatException), e:
195 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
197 if myglsa.isApplied():
200 elif myglsa.isVulnerable():
208 access = ("[%-8s] " % myglsa.access)
212 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
214 for pkg in myglsa.packages.keys()[:3]:
215 fd1.write(" " + pkg + " ")
216 if len(myglsa.packages) > 3:
219 for pkg in myglsa.packages.keys():
220 mylist = vardb.match(portage.dep_getkey(pkg))
222 pkg = color(" ".join(mylist))
223 fd1.write(" " + pkg + " ")
227 fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
232 sys.exit(summarylist(glsalist))
234 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
235 if mode in ["dump", "fix", "inject", "pretend"]:
236 for myid in glsalist:
238 myglsa = Glsa(myid, glsaconfig)
239 except (GlsaTypeException, GlsaFormatException), e:
241 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
246 sys.stdout.write("fixing "+myid+"\n")
247 mergelist = myglsa.getMergeList(least_change=least_change)
248 for pkg in mergelist:
249 sys.stdout.write(">>> merging "+pkg+"\n")
250 # using emerge for the actual merging as it contains the dependency
251 # code and we want to be consistent in behaviour. Also this functionality
252 # will be integrated in emerge later, so it shouldn't hurt much.
253 emergecmd = "emerge --oneshot " + glsaconfig["EMERGE_OPTS"] + " =" + pkg
255 sys.stderr.write(emergecmd+"\n")
256 exitcode = os.system(emergecmd)
257 # system() returns the exitcode in the high byte of a 16bit integer
263 elif mode == "pretend":
264 sys.stdout.write("Checking GLSA "+myid+"\n")
265 mergelist = myglsa.getMergeList(least_change=least_change)
267 sys.stdout.write("The following updates will be performed for this GLSA:\n")
268 for pkg in mergelist:
270 for x in vardb.match(portage.dep_getkey(pkg)):
271 if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]):
274 raise ValueError("could not find old version for package %s" % pkg)
275 oldver = oldver[len(portage.dep_getkey(oldver))+1:]
276 sys.stdout.write(" " + pkg + " (" + oldver + ")\n")
278 sys.stdout.write("Nothing to do for this GLSA\n")
279 elif mode == "inject":
280 sys.stdout.write("injecting " + myid + "\n")
282 sys.stdout.write("\n")
285 # test is a bit different as Glsa.test() produces no output
288 for myid in glsalist:
290 myglsa = Glsa(myid, glsaconfig)
291 except (GlsaTypeException, GlsaFormatException), e:
293 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
295 if myglsa.isVulnerable():
297 outputlist.append(str(myglsa.nr)+" ( "+myglsa.title+" ) ")
299 outputlist.append(str(myglsa.nr))
300 if len(outputlist) > 0:
301 sys.stderr.write("This system is affected by the following GLSAs:\n")
302 sys.stdout.write("\n".join(outputlist)+"\n")
304 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
307 # mail mode as requested by solar
310 import portage.mail as portage_mail
315 from StringIO import StringIO
317 from email.mime.text import MIMEText
319 from email.MIMEText import MIMEText
321 # color doesn't make any sense for mail
324 if glsaconfig.has_key("PORTAGE_ELOG_MAILURI"):
325 myrecipient = glsaconfig["PORTAGE_ELOG_MAILURI"].split()[0]
327 myrecipient = "root@localhost"
329 if glsaconfig.has_key("PORTAGE_ELOG_MAILFROM"):
330 myfrom = glsaconfig["PORTAGE_ELOG_MAILFROM"]
332 myfrom = "glsa-check"
334 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
336 # need a file object for summarylist()
338 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
339 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
340 summarylist(glsalist, fd1=myfd, fd2=myfd)
341 summary = str(myfd.getvalue())
345 for myid in glsalist:
347 myglsa = Glsa(myid, glsaconfig)
348 except (GlsaTypeException, GlsaFormatException), e:
350 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
353 myglsa.dump(outstream=myfd)
354 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
357 mymessage = portage_mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
358 portage_mail.send_mail(glsaconfig, mymessage)
362 # something wrong here, all valid paths are covered with sys.exit()
363 sys.stderr.write("nothing more to do\n")