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 *
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 privileges 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 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 fd1 = codecs.getwriter(encoding)(fd1)
146 fd2 = codecs.getwriter(encoding)(fd2)
147 fd2.write(white("[A]")+" means this GLSA was already applied,\n")
148 fd2.write(green("[U]")+" means the system is not affected and\n")
149 fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
152 for myid in myglsalist:
154 myglsa = Glsa(myid, portage.settings, vardb, portdb)
155 except (GlsaTypeException, GlsaFormatException) as e:
157 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
159 if myglsa.isApplied():
162 elif myglsa.isVulnerable():
170 access = ("[%-8s] " % myglsa.access)
174 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
176 for pkg in list(myglsa.packages)[:3]:
177 fd1.write(" " + pkg + " ")
178 if len(myglsa.packages) > 3:
181 for pkg in myglsa.packages:
182 mylist = vardb.match(pkg)
184 pkg = color(" ".join(mylist))
185 fd1.write(" " + pkg + " ")
189 fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
194 sys.exit(summarylist(glsalist))
196 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
197 if mode in ["dump", "fix", "inject", "pretend"]:
198 for myid in glsalist:
200 myglsa = Glsa(myid, portage.settings, vardb, portdb)
201 except (GlsaTypeException, GlsaFormatException) as e:
203 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
208 sys.stdout.write("Fixing GLSA "+myid+"\n")
209 if not myglsa.isVulnerable():
210 sys.stdout.write(">>> no vulnerable packages installed\n")
212 mergelist = myglsa.getMergeList(least_change=least_change)
214 sys.stdout.write(">>> cannot fix GLSA, no unaffected packages available\n")
216 for pkg in mergelist:
217 sys.stdout.write(">>> merging "+pkg+"\n")
218 # using emerge for the actual merging as it contains the dependency
219 # code and we want to be consistent in behaviour. Also this functionality
220 # will be integrated in emerge later, so it shouldn't hurt much.
221 emergecmd = "emerge --oneshot " + glsaconfig["EMERGE_OPTS"] + " =" + pkg
223 sys.stderr.write(emergecmd+"\n")
224 exitcode = os.system(emergecmd)
225 # system() returns the exitcode in the high byte of a 16bit integer
231 sys.stdout.write("\n")
233 elif mode == "pretend":
234 sys.stdout.write("Checking GLSA "+myid+"\n")
235 if not myglsa.isVulnerable():
236 sys.stdout.write(">>> no vulnerable packages installed\n")
239 for (vuln, update) in myglsa.getAffectionTable(least_change=least_change):
240 mergedict.setdefault(update, []).append(vuln)
242 sys.stdout.write(">>> The following updates will be performed for this GLSA:\n")
243 for pkg in mergedict:
245 sys.stdout.write(" " + pkg + " (vulnerable: " + ", ".join(mergedict[pkg]) + ")\n")
247 sys.stdout.write("\n>>> For the following packages, no upgrade path exists:\n")
248 sys.stdout.write(" " + ", ".join(mergedict[""]))
249 elif mode == "inject":
250 sys.stdout.write("injecting " + myid + "\n")
252 sys.stdout.write("\n")
255 # test is a bit different as Glsa.test() produces no output
258 for myid in glsalist:
260 myglsa = Glsa(myid, portage.settings, vardb, portdb)
261 except (GlsaTypeException, GlsaFormatException) as e:
263 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
265 if myglsa.isVulnerable():
266 outputlist.append(str(myglsa.nr))
267 if len(outputlist) > 0:
268 sys.stderr.write("This system is affected by the following GLSAs:\n")
270 summarylist(outputlist)
272 sys.stdout.write("\n".join(outputlist)+"\n")
274 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
277 # mail mode as requested by solar
279 import portage.mail, socket
280 from io import StringIO
281 from email.mime.text import MIMEText
283 # color doesn't make any sense for mail
286 if "PORTAGE_ELOG_MAILURI" in portage.settings:
287 myrecipient = portage.settings["PORTAGE_ELOG_MAILURI"].split()[0]
289 myrecipient = "root@localhost"
291 if "PORTAGE_ELOG_MAILFROM" in portage.settings:
292 myfrom = portage.settings["PORTAGE_ELOG_MAILFROM"]
294 myfrom = "glsa-check"
296 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
298 # need a file object for summarylist()
300 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
301 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
302 summarylist(glsalist, fd1=myfd, fd2=myfd)
303 summary = str(myfd.getvalue())
307 for myid in glsalist:
309 myglsa = Glsa(myid, portage.settings, vardb, portdb)
310 except (GlsaTypeException, GlsaFormatException) as e:
312 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
315 myglsa.dump(outstream=myfd)
316 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
319 mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
320 portage.mail.send_mail(portage.settings, mymessage)
324 # something wrong here, all valid paths are covered with sys.exit()
325 sys.stderr.write("nothing more to do\n")