2 # Copyright 2008-2011 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 from __future__ import print_function
12 from os import path as osp
13 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
16 from portage import os
17 from portage.output import *
19 from optparse import OptionGroup, OptionParser
21 __program__ = "glsa-check"
22 __author__ = "Marius Mauch <genone@gentoo.org>"
25 def cb_version(*args, **kwargs):
26 """Callback for --version"""
27 sys.stderr.write("\n"+ __program__ + ", version " + __version__ + "\n")
28 sys.stderr.write("Author: " + __author__ + "\n")
29 sys.stderr.write("This program is licensed under the GPL, version 2\n\n")
33 parser = OptionParser(usage="%prog <option> [glsa-list]",
34 version="%prog "+ __version__)
35 parser.epilog = "glsa-list can contain an arbitrary number of GLSA ids," \
36 " filenames containing GLSAs or the special identifiers" \
37 " 'all', 'new' and 'affected'"
39 modes = OptionGroup(parser, "Modes")
40 modes.add_option("-l", "--list", action="store_const",
41 const="list", dest="mode",
42 help="List all unapplied GLSA")
43 modes.add_option("-d", "--dump", action="store_const",
44 const="dump", dest="mode",
45 help="Show all information about the given GLSA")
46 modes.add_option("", "--print", action="store_const",
47 const="dump", dest="mode",
48 help="Alias for --dump")
49 modes.add_option("-t", "--test", action="store_const",
50 const="test", dest="mode",
51 help="Test if this system is affected by the given GLSA")
52 modes.add_option("-p", "--pretend", action="store_const",
53 const="pretend", dest="mode",
54 help="Show the necessary commands to apply this GLSA")
55 modes.add_option("-f", "--fix", action="store_const",
56 const="fix", dest="mode",
57 help="Try to auto-apply this GLSA (experimental)")
58 modes.add_option("-i", "--inject", action="store_const", dest="mode",
59 help="Inject the given GLSA into the checkfile")
60 modes.add_option("-m", "--mail", action="store_const",
61 const="mail", dest="mode",
62 help="Send a mail with the given GLSAs to the administrator")
63 parser.add_option_group(modes)
65 parser.remove_option("--version")
66 parser.add_option("-V", "--version", action="callback",
67 callback=cb_version, help="Some information about this tool")
68 parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
69 help="Print more information")
70 parser.add_option("-n", "--nocolor", action="callback",
71 callback=lambda *args, **kwargs: nocolor(),
72 help="Disable colors")
73 parser.add_option("-e", "--emergelike", action="store_false", dest="least_change",
74 help="Do not use a least-change algorithm")
75 parser.add_option("-c", "--cve", action="store_true", dest="list_cve",
76 help="Show CAN ids in listing mode")
78 options, params = parser.parse_args()
81 least_change = options.least_change
82 list_cve = options.list_cve
83 verbose = options.verbose
87 sys.stderr.write("No mode given: what should I do?\n")
90 elif mode != "list" and not params:
91 sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n")
92 sys.stderr.write("If you want to run on all GLSA please tell me so \n")
93 sys.stderr.write("(specify \"all\" as parameter)\n\n")
96 elif mode in ["fix", "inject"] and os.geteuid() != 0:
97 # we need root privileges for write access
98 sys.stderr.write("\nThis tool needs root access to "+options.mode+" this GLSA\n\n")
100 elif mode == "list" and not params:
103 # delay this for speed increase
104 from portage.glsa import *
106 eroot = portage.settings['EROOT']
107 vardb = portage.db[eroot]["vartree"].dbapi
108 portdb = portage.db[eroot]["porttree"].dbapi
111 completelist = get_glsa_list(portage.settings)
113 checklist = get_applied_glsas(portage.settings)
114 todolist = [e for e in completelist if e not in checklist]
122 glsalist = completelist
124 if "affected" in params:
125 # replaced completelist with todolist on request of wschlich
128 myglsa = Glsa(x, portage.settings, vardb, portdb)
129 except (GlsaTypeException, GlsaFormatException) as e:
131 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
133 if myglsa.isVulnerable():
135 params.remove("affected")
137 # remove invalid parameters
139 if not (p in completelist or os.path.exists(p)):
140 sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p))
143 glsalist.extend([g for g in params if g not in glsalist])
145 def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr):
146 fd2.write(white("[A]")+" means this GLSA was already applied,\n")
147 fd2.write(green("[U]")+" means the system is not affected and\n")
148 fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
151 for myid in myglsalist:
153 myglsa = Glsa(myid, portage.settings, vardb, portdb)
154 except (GlsaTypeException, GlsaFormatException) as e:
156 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
158 if myglsa.isApplied():
161 elif myglsa.isVulnerable():
169 access = ("[%-8s] " % myglsa.access)
173 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
175 for pkg in list(myglsa.packages)[:3]:
176 fd1.write(" " + pkg + " ")
177 if len(myglsa.packages) > 3:
180 for pkg in myglsa.packages:
181 mylist = vardb.match(pkg)
183 pkg = color(" ".join(mylist))
184 fd1.write(" " + pkg + " ")
188 fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
193 sys.exit(summarylist(glsalist))
195 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
196 if mode in ["dump", "fix", "inject", "pretend"]:
197 for myid in glsalist:
199 myglsa = Glsa(myid, portage.settings, vardb, portdb)
200 except (GlsaTypeException, GlsaFormatException) as e:
202 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
207 sys.stdout.write("fixing "+myid+"\n")
208 mergelist = myglsa.getMergeList(least_change=least_change)
209 for pkg in mergelist:
210 sys.stdout.write(">>> merging "+pkg+"\n")
211 # using emerge for the actual merging as it contains the dependency
212 # code and we want to be consistent in behaviour. Also this functionality
213 # will be integrated in emerge later, so it shouldn't hurt much.
214 emergecmd = "emerge --oneshot " + portage.settings["EMERGE_OPTS"] + " =" + pkg
216 sys.stderr.write(emergecmd+"\n")
217 exitcode = os.system(emergecmd)
218 # system() returns the exitcode in the high byte of a 16bit integer
224 elif mode == "pretend":
225 sys.stdout.write("Checking GLSA "+myid+"\n")
226 mergelist = myglsa.getMergeList(least_change=least_change)
228 sys.stdout.write("The following updates will be performed for this GLSA:\n")
229 for pkg in mergelist:
231 for x in vardb.match(portage.cpv_getkey(pkg)):
232 if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]):
235 raise ValueError("could not find old version for package %s" % pkg)
236 oldver = oldver[len(portage.cpv_getkey(oldver))+1:]
237 sys.stdout.write(" " + pkg + " (" + oldver + ")\n")
239 sys.stdout.write("Nothing to do for this GLSA\n")
240 elif mode == "inject":
241 sys.stdout.write("injecting " + myid + "\n")
243 sys.stdout.write("\n")
246 # test is a bit different as Glsa.test() produces no output
249 for myid in glsalist:
251 myglsa = Glsa(myid, portage.settings, vardb, portdb)
252 except (GlsaTypeException, GlsaFormatException) as e:
254 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
256 if myglsa.isVulnerable():
257 outputlist.append(str(myglsa.nr))
258 if len(outputlist) > 0:
259 sys.stderr.write("This system is affected by the following GLSAs:\n")
261 summarylist(outputlist)
263 sys.stdout.write("\n".join(outputlist)+"\n")
265 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
268 # mail mode as requested by solar
270 import portage.mail, socket
271 from io import StringIO
272 from email.mime.text import MIMEText
274 # color doesn't make any sense for mail
277 if "PORTAGE_ELOG_MAILURI" in portage.settings:
278 myrecipient = portage.settings["PORTAGE_ELOG_MAILURI"].split()[0]
280 myrecipient = "root@localhost"
282 if "PORTAGE_ELOG_MAILFROM" in portage.settings:
283 myfrom = portage.settings["PORTAGE_ELOG_MAILFROM"]
285 myfrom = "glsa-check"
287 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
289 # need a file object for summarylist()
291 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
292 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
293 summarylist(glsalist, fd1=myfd, fd2=myfd)
294 summary = str(myfd.getvalue())
298 for myid in glsalist:
300 myglsa = Glsa(myid, portage.settings, vardb, portdb)
301 except (GlsaTypeException, GlsaFormatException) as e:
303 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
306 myglsa.dump(outstream=myfd)
307 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
310 mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
311 portage.mail.send_mail(portage.settings, mymessage)
315 # something wrong here, all valid paths are covered with sys.exit()
316 sys.stderr.write("nothing more to do\n")