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 "+myid+"\n")
209 mergelist = myglsa.getMergeList(least_change=least_change)
210 for pkg in mergelist:
211 sys.stdout.write(">>> merging "+pkg+"\n")
212 # using emerge for the actual merging as it contains the dependency
213 # code and we want to be consistent in behaviour. Also this functionality
214 # will be integrated in emerge later, so it shouldn't hurt much.
215 emergecmd = "emerge --oneshot " + portage.settings["EMERGE_OPTS"] + " =" + pkg
217 sys.stderr.write(emergecmd+"\n")
218 exitcode = os.system(emergecmd)
219 # system() returns the exitcode in the high byte of a 16bit integer
225 elif mode == "pretend":
226 sys.stdout.write("Checking GLSA "+myid+"\n")
227 mergelist = myglsa.getMergeList(least_change=least_change)
229 sys.stdout.write("The following updates will be performed for this GLSA:\n")
230 for pkg in mergelist:
232 for x in vardb.match(portage.cpv_getkey(pkg)):
233 if vardb._pkg_str(x, None).slot == portdb._pkg_str(pkg, None).slot:
236 raise ValueError("could not find old version for package %s" % pkg)
237 oldver = oldver[len(portage.cpv_getkey(oldver))+1:]
238 sys.stdout.write(" " + pkg + " (" + oldver + ")\n")
240 sys.stdout.write("Nothing to do for this GLSA\n")
241 elif mode == "inject":
242 sys.stdout.write("injecting " + myid + "\n")
244 sys.stdout.write("\n")
247 # test is a bit different as Glsa.test() produces no output
250 for myid in glsalist:
252 myglsa = Glsa(myid, portage.settings, vardb, portdb)
253 except (GlsaTypeException, GlsaFormatException) as e:
255 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
257 if myglsa.isVulnerable():
258 outputlist.append(str(myglsa.nr))
259 if len(outputlist) > 0:
260 sys.stderr.write("This system is affected by the following GLSAs:\n")
262 summarylist(outputlist)
264 sys.stdout.write("\n".join(outputlist)+"\n")
266 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
269 # mail mode as requested by solar
271 import portage.mail, socket
272 from io import StringIO
273 from email.mime.text import MIMEText
275 # color doesn't make any sense for mail
278 if "PORTAGE_ELOG_MAILURI" in portage.settings:
279 myrecipient = portage.settings["PORTAGE_ELOG_MAILURI"].split()[0]
281 myrecipient = "root@localhost"
283 if "PORTAGE_ELOG_MAILFROM" in portage.settings:
284 myfrom = portage.settings["PORTAGE_ELOG_MAILFROM"]
286 myfrom = "glsa-check"
288 mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
290 # need a file object for summarylist()
292 myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
293 myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
294 summarylist(glsalist, fd1=myfd, fd2=myfd)
295 summary = str(myfd.getvalue())
299 for myid in glsalist:
301 myglsa = Glsa(myid, portage.settings, vardb, portdb)
302 except (GlsaTypeException, GlsaFormatException) as e:
304 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
307 myglsa.dump(outstream=myfd)
308 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
311 mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
312 portage.mail.send_mail(portage.settings, mymessage)
316 # something wrong here, all valid paths are covered with sys.exit()
317 sys.stderr.write("nothing more to do\n")