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 vardb = portage.db[portage.settings["ROOT"]]["vartree"].dbapi
107 portdb = portage.db["/"]["porttree"].dbapi
110 completelist = get_glsa_list(portage.settings)
112 checklist = get_applied_glsas(portage.settings)
113 todolist = [e for e in completelist if e not in checklist]
121 glsalist = completelist
123 if "affected" in params:
124 # replaced completelist with todolist on request of wschlich
127 myglsa = Glsa(x, portage.settings, vardb, portdb)
128 except (GlsaTypeException, GlsaFormatException) as e:
130 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
132 if myglsa.isVulnerable():
134 params.remove("affected")
136 # remove invalid parameters
138 if not (p in completelist or os.path.exists(p)):
139 sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p))
142 glsalist.extend([g for g in params if g not in glsalist])
144 def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr):
145 fd2.write(white("[A]")+" means this GLSA was already applied,\n")
146 fd2.write(green("[U]")+" means the system is not affected and\n")
147 fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
150 for myid in myglsalist:
152 myglsa = Glsa(myid, portage.settings, vardb, portdb)
153 except (GlsaTypeException, GlsaFormatException) as e:
155 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
157 if myglsa.isApplied():
160 elif myglsa.isVulnerable():
168 access = ("[%-8s] " % myglsa.access)
172 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
174 for pkg in list(myglsa.packages)[:3]:
175 fd1.write(" " + pkg + " ")
176 if len(myglsa.packages) > 3:
179 for pkg in myglsa.packages:
180 mylist = vardb.match(pkg)
182 pkg = color(" ".join(mylist))
183 fd1.write(" " + pkg + " ")
187 fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
192 sys.exit(summarylist(glsalist))
194 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
195 if mode in ["dump", "fix", "inject", "pretend"]:
196 for myid in glsalist:
198 myglsa = Glsa(myid, portage.settings, vardb, portdb)
199 except (GlsaTypeException, GlsaFormatException) as e:
201 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
206 sys.stdout.write("fixing "+myid+"\n")
207 mergelist = myglsa.getMergeList(least_change=least_change)
208 for pkg in mergelist:
209 sys.stdout.write(">>> merging "+pkg+"\n")
210 # using emerge for the actual merging as it contains the dependency
211 # code and we want to be consistent in behaviour. Also this functionality
212 # will be integrated in emerge later, so it shouldn't hurt much.
213 emergecmd = "emerge --oneshot " + portage.settings["EMERGE_OPTS"] + " =" + pkg
215 sys.stderr.write(emergecmd+"\n")
216 exitcode = os.system(emergecmd)
217 # system() returns the exitcode in the high byte of a 16bit integer
223 elif mode == "pretend":
224 sys.stdout.write("Checking GLSA "+myid+"\n")
225 mergelist = myglsa.getMergeList(least_change=least_change)
227 sys.stdout.write("The following updates will be performed for this GLSA:\n")
228 for pkg in mergelist:
230 for x in vardb.match(portage.cpv_getkey(pkg)):
231 if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]):
234 raise ValueError("could not find old version for package %s" % pkg)
235 oldver = oldver[len(portage.cpv_getkey(oldver))+1:]
236 sys.stdout.write(" " + pkg + " (" + oldver + ")\n")
238 sys.stdout.write("Nothing to do for this GLSA\n")
239 elif mode == "inject":
240 sys.stdout.write("injecting " + myid + "\n")
242 sys.stdout.write("\n")
245 # test is a bit different as Glsa.test() produces no output
248 for myid in glsalist:
250 myglsa = Glsa(myid, portage.settings, vardb, portdb)
251 except (GlsaTypeException, GlsaFormatException) as e:
253 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
255 if myglsa.isVulnerable():
256 outputlist.append(str(myglsa.nr))
257 if len(outputlist) > 0:
258 sys.stderr.write("This system is affected by the following GLSAs:\n")
260 summarylist(outputlist)
262 sys.stdout.write("\n".join(outputlist)+"\n")
264 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
267 # mail mode as requested by solar
269 import portage.mail, socket
270 from io import StringIO
271 from email.mime.text import MIMEText
273 # color doesn't make any sense for mail
276 if "PORTAGE_ELOG_MAILURI" in portage.settings:
277 myrecipient = portage.settings["PORTAGE_ELOG_MAILURI"].split()[0]
279 myrecipient = "root@localhost"
281 if "PORTAGE_ELOG_MAILFROM" in portage.settings:
282 myfrom = portage.settings["PORTAGE_ELOG_MAILFROM"]
284 myfrom = "glsa-check"
286 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
288 # need a file object for summarylist()
290 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
291 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
292 summarylist(glsalist, fd1=myfd, fd2=myfd)
293 summary = str(myfd.getvalue())
297 for myid in glsalist:
299 myglsa = Glsa(myid, portage.settings, vardb, portdb)
300 except (GlsaTypeException, GlsaFormatException) as e:
302 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
305 myglsa.dump(outstream=myfd)
306 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
309 mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
310 portage.mail.send_mail(portage.settings, mymessage)
314 # something wrong here, all valid paths are covered with sys.exit()
315 sys.stderr.write("nothing more to do\n")