2 # Copyright 2008-2013 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 from __future__ import print_function
10 from os import path as osp
11 pym_path = osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")
12 sys.path.insert(0, pym_path)
14 portage._internal_caller = True
15 from portage import os
16 from portage.output import green, red, nocolor, white
17 from portage.util._argparse import ArgumentParser
19 __program__ = "glsa-check"
20 __author__ = "Marius Mauch <genone@gentoo.org>"
24 epilog = "glsa-list can contain an arbitrary number of GLSA ids," \
25 " filenames containing GLSAs or the special identifiers" \
26 " 'all', 'new' and 'affected'"
27 parser = ArgumentParser(usage=__program__ + " <option> [glsa-list]",
30 modes = parser.add_argument_group("Modes")
31 modes.add_argument("-l", "--list", action="store_const",
32 const="list", dest="mode",
33 help="List all unapplied GLSA")
34 modes.add_argument("-d", "--dump", action="store_const",
35 const="dump", dest="mode",
36 help="Show all information about the given GLSA")
37 modes.add_argument("--print", action="store_const",
38 const="dump", dest="mode",
39 help="Alias for --dump")
40 modes.add_argument("-t", "--test", action="store_const",
41 const="test", dest="mode",
42 help="Test if this system is affected by the given GLSA")
43 modes.add_argument("-p", "--pretend", action="store_const",
44 const="pretend", dest="mode",
45 help="Show the necessary commands to apply this GLSA")
46 modes.add_argument("-f", "--fix", action="store_const",
47 const="fix", dest="mode",
48 help="Try to auto-apply this GLSA (experimental)")
49 modes.add_argument("-i", "--inject", action="store_const",
50 const="inject", dest="mode",
51 help="inject the given GLSA into the glsa_injected file")
52 modes.add_argument("-m", "--mail", action="store_const",
53 const="mail", dest="mode",
54 help="Send a mail with the given GLSAs to the administrator")
56 parser.add_argument("-V", "--version", action="store_true",
57 help="Some information about this tool")
58 parser.add_argument("-v", "--verbose", action="store_true", dest="verbose",
59 help="Print more information")
60 parser.add_argument("-n", "--nocolor", action="store_true",
61 help="Disable colors")
62 parser.add_argument("-e", "--emergelike", action="store_false", dest="least_change",
63 help="Do not use a least-change algorithm")
64 parser.add_argument("-c", "--cve", action="store_true", dest="list_cve",
65 help="Show CAN ids in listing mode")
67 options, params = parser.parse_known_args()
73 sys.stderr.write("\n"+ __program__ + ", version " + __version__ + "\n")
74 sys.stderr.write("Author: " + __author__ + "\n")
75 sys.stderr.write("This program is licensed under the GPL, version 2\n\n")
79 least_change = options.least_change
80 list_cve = options.list_cve
81 verbose = options.verbose
85 sys.stderr.write("No mode given: what should I do?\n")
88 elif mode != "list" and not params:
89 sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n")
90 sys.stderr.write("If you want to run on all GLSA please tell me so \n")
91 sys.stderr.write("(specify \"all\" as parameter)\n\n")
94 elif mode in ["fix", "inject"] and os.geteuid() != 0:
95 # we need root privileges for write access
96 sys.stderr.write("\nThis tool needs root access to "+options.mode+" this GLSA\n\n")
98 elif mode == "list" and not params:
101 # delay this for speed increase
102 from portage.glsa import (Glsa, GlsaTypeException, GlsaFormatException,
103 get_applied_glsas, get_glsa_list)
105 eroot = portage.settings['EROOT']
106 vardb = portage.db[eroot]["vartree"].dbapi
107 portdb = portage.db[eroot]["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, encoding="utf-8"):
145 # Get to the raw streams in py3k before wrapping them with an encoded writer
146 # to avoid writing bytes to a text stream (stdout/stderr are text streams
147 # by default in py3k)
148 if hasattr(fd1, "buffer"):
150 if hasattr(fd2, "buffer"):
152 fd1 = codecs.getwriter(encoding)(fd1)
153 fd2 = codecs.getwriter(encoding)(fd2)
154 fd2.write(white("[A]")+" means this GLSA was marked as applied (injected),\n")
155 fd2.write(green("[U]")+" means the system is not affected and\n")
156 fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
159 for myid in myglsalist:
161 myglsa = Glsa(myid, portage.settings, vardb, portdb)
162 except (GlsaTypeException, GlsaFormatException) as e:
164 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
166 if myglsa.isInjected():
169 elif myglsa.isVulnerable():
177 access = ("[%-8s] " % myglsa.access)
181 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
183 for pkg in list(myglsa.packages)[:3]:
184 fd1.write(" " + pkg + " ")
185 if len(myglsa.packages) > 3:
188 for pkg in myglsa.packages:
189 mylist = vardb.match(pkg)
191 pkg = color(" ".join(mylist))
192 fd1.write(" " + pkg + " ")
196 fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
201 sys.exit(summarylist(glsalist))
203 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
204 if mode in ["dump", "fix", "inject", "pretend"]:
205 for myid in glsalist:
207 myglsa = Glsa(myid, portage.settings, vardb, portdb)
208 except (GlsaTypeException, GlsaFormatException) as e:
210 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
215 sys.stdout.write("Fixing GLSA "+myid+"\n")
216 if not myglsa.isVulnerable():
217 sys.stdout.write(">>> no vulnerable packages installed\n")
219 mergelist = myglsa.getMergeList(least_change=least_change)
221 sys.stdout.write(">>> cannot fix GLSA, no unaffected packages available\n")
223 for pkg in mergelist:
224 sys.stdout.write(">>> merging "+pkg+"\n")
225 # using emerge for the actual merging as it contains the dependency
226 # code and we want to be consistent in behaviour. Also this functionality
227 # will be integrated in emerge later, so it shouldn't hurt much.
228 emergecmd = "emerge --oneshot " + " =" + pkg
230 sys.stderr.write(emergecmd+"\n")
231 exitcode = os.system(emergecmd)
232 # system() returns the exitcode in the high byte of a 16bit integer
238 sys.stdout.write("\n")
239 elif mode == "pretend":
240 sys.stdout.write("Checking GLSA "+myid+"\n")
241 if not myglsa.isVulnerable():
242 sys.stdout.write(">>> no vulnerable packages installed\n")
245 for (vuln, update) in myglsa.getAffectionTable(least_change=least_change):
246 mergedict.setdefault(update, []).append(vuln)
248 sys.stdout.write(">>> The following updates will be performed for this GLSA:\n")
249 for pkg in mergedict:
251 sys.stdout.write(" " + pkg + " (vulnerable: " + ", ".join(mergedict[pkg]) + ")\n")
253 sys.stdout.write("\n>>> For the following packages, no upgrade path exists:\n")
254 sys.stdout.write(" " + ", ".join(mergedict[""]))
255 elif mode == "inject":
256 sys.stdout.write("injecting " + myid + "\n")
258 sys.stdout.write("\n")
261 # test is a bit different as Glsa.test() produces no output
264 for myid in glsalist:
266 myglsa = Glsa(myid, portage.settings, vardb, portdb)
267 except (GlsaTypeException, GlsaFormatException) as e:
269 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
271 if myglsa.isVulnerable():
272 outputlist.append(str(myglsa.nr))
273 if len(outputlist) > 0:
274 sys.stderr.write("This system is affected by the following GLSAs:\n")
276 summarylist(outputlist)
278 sys.stdout.write("\n".join(outputlist)+"\n")
280 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
283 # mail mode as requested by solar
285 import portage.mail, socket
286 from io import BytesIO
287 from email.mime.text import MIMEText
289 # color doesn't make any sense for mail
292 if "PORTAGE_ELOG_MAILURI" in portage.settings:
293 myrecipient = portage.settings["PORTAGE_ELOG_MAILURI"].split()[0]
295 myrecipient = "root@localhost"
297 if "PORTAGE_ELOG_MAILFROM" in portage.settings:
298 myfrom = portage.settings["PORTAGE_ELOG_MAILFROM"]
300 myfrom = "glsa-check"
302 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
304 # need a file object for summarylist()
306 line = "GLSA Summary report for host %s\n" % socket.getfqdn()
307 myfd.write(line.encode("utf-8"))
308 line = "(Command was: %s)\n\n" % " ".join(sys.argv)
309 myfd.write(line.encode("utf-8"))
310 summarylist(glsalist, fd1=myfd, fd2=myfd)
311 summary = myfd.getvalue().decode("utf-8")
315 for myid in glsalist:
317 myglsa = Glsa(myid, portage.settings, vardb, portdb)
318 except (GlsaTypeException, GlsaFormatException) as e:
320 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
323 myglsa.dump(outstream=myfd)
324 attachment = myfd.getvalue().decode("utf-8")
325 myattachments.append(MIMEText(attachment, _charset="utf8"))
328 mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
329 portage.mail.send_mail(portage.settings, mymessage)
333 # something wrong here, all valid paths are covered with sys.exit()
334 sys.stderr.write("nothing more to do\n")