2 # Copyright 2008-2009 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
11 from os import path as osp
12 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
15 from portage import os
16 from portage.output import *
18 from optparse import OptionGroup, OptionParser
20 __program__ = "glsa-check"
21 __author__ = "Marius Mauch <genone@gentoo.org>"
24 def cb_version(*args, **kwargs):
25 """Callback for --version"""
26 sys.stderr.write("\n"+ __program__ + ", version " + __version__ + "\n")
27 sys.stderr.write("Author: " + __author__ + "\n")
28 sys.stderr.write("This program is licensed under the GPL, version 2\n\n")
32 parser = OptionParser(usage="%prog <option> [glsa-list]",
33 version="%prog "+ __version__)
34 parser.epilog = "glsa-list can contain an arbitrary number of GLSA ids," \
35 " filenames containing GLSAs or the special identifiers" \
36 " 'all', 'new' and 'affected'"
38 modes = OptionGroup(parser, "Modes")
39 modes.add_option("-l", "--list", action="store_const",
40 const="list", dest="mode",
41 help="List all unapplied GLSA")
42 modes.add_option("-d", "--dump", action="store_const",
43 const="dump", dest="mode",
44 help="Show all information about the given GLSA")
45 modes.add_option("", "--print", action="store_const",
46 const="dump", dest="mode",
47 help="Alias for --dump")
48 modes.add_option("-t", "--test", action="store_const",
49 const="test", dest="mode",
50 help="Test if this system is affected by the given GLSA")
51 modes.add_option("-p", "--pretend", action="store_const",
52 const="pretend", dest="mode",
53 help="Show the necessary commands to apply this GLSA")
54 modes.add_option("-f", "--fix", action="store_const",
55 const="fix", dest="mode",
56 help="Try to auto-apply this GLSA (experimental)")
57 modes.add_option("-i", "--inject", action="store_const", dest="mode",
58 help="Inject the given GLSA into the checkfile")
59 modes.add_option("-m", "--mail", action="store_const",
60 const="mail", dest="mode",
61 help="Send a mail with the given GLSAs to the administrator")
62 parser.add_option_group(modes)
64 parser.remove_option("--version")
65 parser.add_option("-V", "--version", action="callback",
66 callback=cb_version, help="Some information about this tool")
67 parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
68 help="Print more information")
69 parser.add_option("-n", "--nocolor", action="callback",
70 callback=lambda *args, **kwargs: nocolor(),
71 help="Disable colors")
72 parser.add_option("-e", "--emergelike", action="store_false", dest="least_change",
73 help="Do not use a least-change algorithm")
74 parser.add_option("-c", "--cve", action="store_true", dest="list_cve",
75 help="Show CAN ids in listing mode")
77 options, params = parser.parse_args()
80 least_change = options.least_change
81 list_cve = options.list_cve
82 verbose = options.verbose
86 sys.stderr.write("No mode given: what should I do?\n")
89 elif mode != "list" and not params:
90 sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n")
91 sys.stderr.write("If you want to run on all GLSA please tell me so \n")
92 sys.stderr.write("(specify \"all\" as parameter)\n\n")
95 elif mode in ["fix", "inject"] and os.geteuid() != 0:
96 # we need root priviledges for write access
97 sys.stderr.write("\nThis tool needs root access to "+options.mode+" this GLSA\n\n")
99 elif mode == "list" and not params:
102 # delay this for speed increase
103 from portage.glsa import *
105 vardb = portage.db[portage.settings["ROOT"]]["vartree"].dbapi
106 portdb = portage.db["/"]["porttree"].dbapi
109 completelist = get_glsa_list(portage.settings)
111 checklist = get_applied_glsas(portage.settings)
112 todolist = [e for e in completelist if e not in checklist]
120 glsalist = completelist
122 if "affected" in params:
123 # replaced completelist with todolist on request of wschlich
126 myglsa = Glsa(x, portage.settings, vardb, portdb)
127 except (GlsaTypeException, GlsaFormatException) as e:
129 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
131 if myglsa.isVulnerable():
133 params.remove("affected")
135 # remove invalid parameters
137 if not (p in completelist or os.path.exists(p)):
138 sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p))
141 glsalist.extend([g for g in params if g not in glsalist])
143 def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr):
144 fd2.write(white("[A]")+" means this GLSA was already applied,\n")
145 fd2.write(green("[U]")+" means the system is not affected and\n")
146 fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
149 for myid in myglsalist:
151 myglsa = Glsa(myid, portage.settings, vardb, portdb)
152 except (GlsaTypeException, GlsaFormatException) as e:
154 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
156 if myglsa.isApplied():
159 elif myglsa.isVulnerable():
167 access = ("[%-8s] " % myglsa.access)
171 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
173 for pkg in myglsa.packages.keys()[:3]:
174 fd1.write(" " + pkg + " ")
175 if len(myglsa.packages) > 3:
178 for pkg in myglsa.packages.keys():
179 mylist = vardb.match(portage.dep_getkey(str(pkg)))
181 pkg = color(" ".join(mylist))
182 fd1.write(" " + pkg + " ")
186 fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
191 sys.exit(summarylist(glsalist))
193 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
194 if mode in ["dump", "fix", "inject", "pretend"]:
195 for myid in glsalist:
197 myglsa = Glsa(myid, portage.settings, vardb, portdb)
198 except (GlsaTypeException, GlsaFormatException) as e:
200 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
205 sys.stdout.write("fixing "+myid+"\n")
206 mergelist = myglsa.getMergeList(least_change=least_change)
207 for pkg in mergelist:
208 sys.stdout.write(">>> merging "+pkg+"\n")
209 # using emerge for the actual merging as it contains the dependency
210 # code and we want to be consistent in behaviour. Also this functionality
211 # will be integrated in emerge later, so it shouldn't hurt much.
212 emergecmd = "emerge --oneshot " + portage.settings["EMERGE_OPTS"] + " =" + pkg
214 sys.stderr.write(emergecmd+"\n")
215 exitcode = os.system(emergecmd)
216 # system() returns the exitcode in the high byte of a 16bit integer
222 elif mode == "pretend":
223 sys.stdout.write("Checking GLSA "+myid+"\n")
224 mergelist = myglsa.getMergeList(least_change=least_change)
226 sys.stdout.write("The following updates will be performed for this GLSA:\n")
227 for pkg in mergelist:
229 for x in vardb.match(portage.dep_getkey(pkg)):
230 if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]):
233 raise ValueError("could not find old version for package %s" % pkg)
234 oldver = oldver[len(portage.dep_getkey(oldver))+1:]
235 sys.stdout.write(" " + pkg + " (" + oldver + ")\n")
237 sys.stdout.write("Nothing to do for this GLSA\n")
238 elif mode == "inject":
239 sys.stdout.write("injecting " + myid + "\n")
241 sys.stdout.write("\n")
244 # test is a bit different as Glsa.test() produces no output
247 for myid in glsalist:
249 myglsa = Glsa(myid, portage.settings, vardb, portdb)
250 except (GlsaTypeException, GlsaFormatException) as e:
252 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
254 if myglsa.isVulnerable():
255 outputlist.append(str(myglsa.nr))
256 if len(outputlist) > 0:
257 sys.stderr.write("This system is affected by the following GLSAs:\n")
259 summarylist(outputlist)
261 sys.stdout.write("\n".join(outputlist)+"\n")
263 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
266 # mail mode as requested by solar
268 import portage.mail, socket
269 from StringIO import StringIO
270 from email.mime.text import MIMEText
272 # color doesn't make any sense for mail
275 if "PORTAGE_ELOG_MAILURI" in portage.settings:
276 myrecipient = portage.settings["PORTAGE_ELOG_MAILURI"].split()[0]
278 myrecipient = "root@localhost"
280 if "PORTAGE_ELOG_MAILFROM" in portage.settings:
281 myfrom = portage.settings["PORTAGE_ELOG_MAILFROM"]
283 myfrom = "glsa-check"
285 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
287 # need a file object for summarylist()
289 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
290 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
291 summarylist(glsalist, fd1=myfd, fd2=myfd)
292 summary = str(myfd.getvalue())
296 for myid in glsalist:
298 myglsa = Glsa(myid, portage.settings, vardb, portdb)
299 except (GlsaTypeException, GlsaFormatException) as e:
301 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
304 myglsa.dump(outstream=myfd)
305 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
308 mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
309 portage.mail.send_mail(portage.settings, mymessage)
313 # something wrong here, all valid paths are covered with sys.exit()
314 sys.stderr.write("nothing more to do\n")