#!/usr/bin/python
+# Copyright 2008-2013 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
-# $Header: $
-# This program is licensed under the GPL, version 2
+from __future__ import print_function
-import os
import sys
+import codecs
-try:
- import portage
-except ImportError:
- from os import path as osp
- sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
- import portage
+from os import path as osp
+pym_path = osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")
+sys.path.insert(0, pym_path)
+import portage
+portage._internal_caller = True
+from portage import os
+from portage.output import green, red, nocolor, white
-from portage.output import *
-
-from getopt import getopt, GetoptError
+from optparse import OptionGroup, OptionParser
__program__ = "glsa-check"
__author__ = "Marius Mauch <genone@gentoo.org>"
__version__ = "1.0"
-optionmap = [
-["-l", "--list", "list all unapplied GLSA"],
-["-d", "--dump", "--print", "show all information about the given GLSA"],
-["-t", "--test", "test if this system is affected by the given GLSA"],
-["-p", "--pretend", "show the necessary commands to apply this GLSA"],
-["-f", "--fix", "try to auto-apply this GLSA (experimental)"],
-["-i", "--inject", "inject the given GLSA into the checkfile"],
-["-n", "--nocolor", "disable colors (option)"],
-["-e", "--emergelike", "do not use a least-change algorithm (option)"],
-["-h", "--help", "show this help message"],
-["-V", "--version", "some information about this tool"],
-["-v", "--verbose", "print more information (option)"],
-["-c", "--cve", "show CAN ids in listing mode (option)"],
-["-m", "--mail", "send a mail with the given GLSAs to the administrator"]
-]
+def cb_version(*args, **kwargs):
+ """Callback for --version"""
+ sys.stderr.write("\n"+ __program__ + ", version " + __version__ + "\n")
+ sys.stderr.write("Author: " + __author__ + "\n")
+ sys.stderr.write("This program is licensed under the GPL, version 2\n\n")
+ sys.exit(0)
# option parsing
-args = []
-params = []
-try:
- args, params = getopt(sys.argv[1:], "".join([o[0][1] for o in optionmap]), \
- [x[2:] for x in reduce(lambda x,y: x+y, [z[1:-1] for z in optionmap])])
-# ["dump", "print", "list", "pretend", "fix", "inject", "help", "verbose", "version", "test", "nocolor", "cve", "mail"])
- args = [a for a,b in args]
-
- for option in ["--nocolor", "-n"]:
- if option in args:
- nocolor()
- args.remove(option)
-
- verbose = False
- for option in ["--verbose", "-v"]:
- if option in args:
- verbose = True
- args.remove(option)
-
- list_cve = False
- for option in ["--cve", "-c"]:
- if option in args:
- list_cve = True
- args.remove(option)
-
- least_change = True
- for option in ["--emergelike", "-e"]:
- if option in args:
- least_change = False
- args.remove(option)
-
- # sanity checking
- if len(args) <= 0:
- sys.stderr.write("no option given: what should I do ?\n")
- mode="help"
- elif len(args) > 1:
- sys.stderr.write("please use only one command per call\n")
- mode = "help"
- else:
- # in what mode are we ?
- args = args[0]
- for m in optionmap:
- if args in [o for o in m[:-1]]:
- mode = m[1][2:]
-
-except GetoptError, e:
- sys.stderr.write("unknown option given: ")
- sys.stderr.write(str(e)+"\n")
- mode = "help"
-
-# we need a set of glsa for most operation modes
-if len(params) <= 0 and mode in ["fix", "test", "pretend", "dump", "inject", "mail"]:
+parser = OptionParser(usage="%prog <option> [glsa-list]",
+ version="%prog "+ __version__)
+parser.epilog = "glsa-list can contain an arbitrary number of GLSA ids," \
+ " filenames containing GLSAs or the special identifiers" \
+ " 'all', 'new' and 'affected'"
+
+modes = OptionGroup(parser, "Modes")
+modes.add_option("-l", "--list", action="store_const",
+ const="list", dest="mode",
+ help="List all unapplied GLSA")
+modes.add_option("-d", "--dump", action="store_const",
+ const="dump", dest="mode",
+ help="Show all information about the given GLSA")
+modes.add_option("", "--print", action="store_const",
+ const="dump", dest="mode",
+ help="Alias for --dump")
+modes.add_option("-t", "--test", action="store_const",
+ const="test", dest="mode",
+ help="Test if this system is affected by the given GLSA")
+modes.add_option("-p", "--pretend", action="store_const",
+ const="pretend", dest="mode",
+ help="Show the necessary commands to apply this GLSA")
+modes.add_option("-f", "--fix", action="store_const",
+ const="fix", dest="mode",
+ help="Try to auto-apply this GLSA (experimental)")
+modes.add_option("-i", "--inject", action="store_const", dest="mode",
+ help="inject the given GLSA into the glsa_injected file")
+modes.add_option("-m", "--mail", action="store_const",
+ const="mail", dest="mode",
+ help="Send a mail with the given GLSAs to the administrator")
+parser.add_option_group(modes)
+
+parser.remove_option("--version")
+parser.add_option("-V", "--version", action="callback",
+ callback=cb_version, help="Some information about this tool")
+parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
+ help="Print more information")
+parser.add_option("-n", "--nocolor", action="callback",
+ callback=lambda *args, **kwargs: nocolor(),
+ help="Disable colors")
+parser.add_option("-e", "--emergelike", action="store_false", dest="least_change",
+ help="Do not use a least-change algorithm")
+parser.add_option("-c", "--cve", action="store_true", dest="list_cve",
+ help="Show CAN ids in listing mode")
+
+options, params = parser.parse_args()
+
+mode = options.mode
+least_change = options.least_change
+list_cve = options.list_cve
+verbose = options.verbose
+
+# Sanity checking
+if mode is None:
+ sys.stderr.write("No mode given: what should I do?\n")
+ parser.print_help()
+ sys.exit(1)
+elif mode != "list" and not params:
sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n")
sys.stderr.write("If you want to run on all GLSA please tell me so \n")
sys.stderr.write("(specify \"all\" as parameter)\n\n")
- mode = "help"
-elif len(params) <= 0 and mode == "list":
- params.append("new")
-
-# show help message
-if mode == "help":
- sys.stderr.write("\nSyntax: glsa-check <option> [glsa-list]\n\n")
- for m in optionmap:
- sys.stderr.write(m[0] + "\t" + m[1] + " \t: " + m[-1] + "\n")
- for o in m[2:-1]:
- sys.stderr.write("\t" + o + "\n")
- sys.stderr.write("\nglsa-list can contain an arbitrary number of GLSA ids, \n")
- sys.stderr.write("filenames containing GLSAs or the special identifiers \n")
- sys.stderr.write("'all', 'new' and 'affected'\n")
+ parser.print_help()
sys.exit(1)
-
-# we need root priviledges for write access
-if mode in ["fix", "inject"] and os.geteuid() != 0:
- sys.stderr.write("\nThis tool needs root access to "+mode+" this GLSA\n\n")
+elif mode in ["fix", "inject"] and os.geteuid() != 0:
+ # we need root privileges for write access
+ sys.stderr.write("\nThis tool needs root access to "+options.mode+" this GLSA\n\n")
sys.exit(2)
-
-# show version and copyright information
-if mode == "version":
- sys.stderr.write("\n"+ __program__ + ", version " + __version__ + "\n")
- sys.stderr.write("Author: " + __author__ + "\n")
- sys.stderr.write("This program is licensed under the GPL, version 2\n\n")
- sys.exit(0)
+elif mode == "list" and not params:
+ params.append("new")
# delay this for speed increase
-from portage.glsa import *
+from portage.glsa import (Glsa, GlsaTypeException, GlsaFormatException,
+ get_applied_glsas, get_glsa_list)
-vardb = portage.db[portage.settings["ROOT"]]["vartree"].dbapi
-portdb = portage.db["/"]["porttree"].dbapi
+eroot = portage.settings['EROOT']
+vardb = portage.db[eroot]["vartree"].dbapi
+portdb = portage.db[eroot]["porttree"].dbapi
# build glsa lists
completelist = get_glsa_list(portage.settings)
if "new" in params:
glsalist = todolist
params.remove("new")
-
+
if "all" in params:
glsalist = completelist
params.remove("all")
for x in todolist:
try:
myglsa = Glsa(x, portage.settings, vardb, portdb)
- except (GlsaTypeException, GlsaFormatException), e:
+ except (GlsaTypeException, GlsaFormatException) as e:
if verbose:
sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
continue
glsalist.extend([g for g in params if g not in glsalist])
-def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr):
- fd2.write(white("[A]")+" means this GLSA was already applied,\n")
+def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr, encoding="utf-8"):
+ # Get to the raw streams in py3k before wrapping them with an encoded writer
+ # to avoid writing bytes to a text stream (stdout/stderr are text streams
+ # by default in py3k)
+ if hasattr(fd1, "buffer"):
+ fd1 = fd1.buffer
+ if hasattr(fd2, "buffer"):
+ fd2 = fd2.buffer
+ fd1 = codecs.getwriter(encoding)(fd1)
+ fd2 = codecs.getwriter(encoding)(fd2)
+ fd2.write(white("[A]")+" means this GLSA was marked as applied (injected),\n")
fd2.write(green("[U]")+" means the system is not affected and\n")
fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
for myid in myglsalist:
try:
myglsa = Glsa(myid, portage.settings, vardb, portdb)
- except (GlsaTypeException, GlsaFormatException), e:
+ except (GlsaTypeException, GlsaFormatException) as e:
if verbose:
fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
continue
- if myglsa.isApplied():
+ if myglsa.isInjected():
status = "[A]"
color = white
elif myglsa.isVulnerable():
fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
if not verbose:
- for pkg in myglsa.packages.keys()[:3]:
+ for pkg in list(myglsa.packages)[:3]:
fd1.write(" " + pkg + " ")
if len(myglsa.packages) > 3:
fd1.write("... ")
else:
- for pkg in myglsa.packages.keys():
- mylist = vardb.match(portage.dep_getkey(pkg))
+ for pkg in myglsa.packages:
+ mylist = vardb.match(pkg)
if len(mylist) > 0:
pkg = color(" ".join(mylist))
fd1.write(" " + pkg + " ")
fd1.write(")")
if list_cve:
fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
- fd1.write("\n")
+ fd1.write("\n")
return 0
if mode == "list":
for myid in glsalist:
try:
myglsa = Glsa(myid, portage.settings, vardb, portdb)
- except (GlsaTypeException, GlsaFormatException), e:
+ except (GlsaTypeException, GlsaFormatException) as e:
if verbose:
sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
continue
if mode == "dump":
myglsa.dump()
elif mode == "fix":
- sys.stdout.write("fixing "+myid+"\n")
- mergelist = myglsa.getMergeList(least_change=least_change)
- for pkg in mergelist:
- sys.stdout.write(">>> merging "+pkg+"\n")
- # using emerge for the actual merging as it contains the dependency
- # code and we want to be consistent in behaviour. Also this functionality
- # will be integrated in emerge later, so it shouldn't hurt much.
- emergecmd = "emerge --oneshot " + portage.settings["EMERGE_OPTS"] + " =" + pkg
- if verbose:
- sys.stderr.write(emergecmd+"\n")
- exitcode = os.system(emergecmd)
- # system() returns the exitcode in the high byte of a 16bit integer
- if exitcode >= 1<<8:
- exitcode >>= 8
- if exitcode:
- sys.exit(exitcode)
- myglsa.inject()
+ sys.stdout.write("Fixing GLSA "+myid+"\n")
+ if not myglsa.isVulnerable():
+ sys.stdout.write(">>> no vulnerable packages installed\n")
+ else:
+ mergelist = myglsa.getMergeList(least_change=least_change)
+ if mergelist == []:
+ sys.stdout.write(">>> cannot fix GLSA, no unaffected packages available\n")
+ sys.exit(2)
+ for pkg in mergelist:
+ sys.stdout.write(">>> merging "+pkg+"\n")
+ # using emerge for the actual merging as it contains the dependency
+ # code and we want to be consistent in behaviour. Also this functionality
+ # will be integrated in emerge later, so it shouldn't hurt much.
+ emergecmd = "emerge --oneshot " + " =" + pkg
+ if verbose:
+ sys.stderr.write(emergecmd+"\n")
+ exitcode = os.system(emergecmd)
+ # system() returns the exitcode in the high byte of a 16bit integer
+ if exitcode >= 1<<8:
+ exitcode >>= 8
+ if exitcode:
+ sys.exit(exitcode)
+ if len(mergelist):
+ sys.stdout.write("\n")
elif mode == "pretend":
sys.stdout.write("Checking GLSA "+myid+"\n")
- mergelist = myglsa.getMergeList(least_change=least_change)
- if mergelist:
- sys.stdout.write("The following updates will be performed for this GLSA:\n")
- for pkg in mergelist:
- oldver = None
- for x in vardb.match(portage.dep_getkey(pkg)):
- if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]):
- oldver = x
- if oldver == None:
- raise ValueError("could not find old version for package %s" % pkg)
- oldver = oldver[len(portage.dep_getkey(oldver))+1:]
- sys.stdout.write(" " + pkg + " (" + oldver + ")\n")
+ if not myglsa.isVulnerable():
+ sys.stdout.write(">>> no vulnerable packages installed\n")
else:
- sys.stdout.write("Nothing to do for this GLSA\n")
+ mergedict = {}
+ for (vuln, update) in myglsa.getAffectionTable(least_change=least_change):
+ mergedict.setdefault(update, []).append(vuln)
+
+ sys.stdout.write(">>> The following updates will be performed for this GLSA:\n")
+ for pkg in mergedict:
+ if pkg != "":
+ sys.stdout.write(" " + pkg + " (vulnerable: " + ", ".join(mergedict[pkg]) + ")\n")
+ if "" in mergedict:
+ sys.stdout.write("\n>>> For the following packages, no upgrade path exists:\n")
+ sys.stdout.write(" " + ", ".join(mergedict[""]))
elif mode == "inject":
sys.stdout.write("injecting " + myid + "\n")
myglsa.inject()
for myid in glsalist:
try:
myglsa = Glsa(myid, portage.settings, vardb, portdb)
- except (GlsaTypeException, GlsaFormatException), e:
+ except (GlsaTypeException, GlsaFormatException) as e:
if verbose:
sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
continue
if myglsa.isVulnerable():
- if verbose:
- outputlist.append(str(myglsa.nr)+" ( "+myglsa.title+" ) ")
- else:
- outputlist.append(str(myglsa.nr))
+ outputlist.append(str(myglsa.nr))
if len(outputlist) > 0:
sys.stderr.write("This system is affected by the following GLSAs:\n")
- sys.stdout.write("\n".join(outputlist)+"\n")
+ if verbose:
+ summarylist(outputlist)
+ else:
+ sys.stdout.write("\n".join(outputlist)+"\n")
else:
sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
sys.exit(0)
# mail mode as requested by solar
if mode == "mail":
import portage.mail, socket
- from StringIO import StringIO
+ from io import BytesIO
from email.mime.text import MIMEText
-
+
# color doesn't make any sense for mail
nocolor()
myrecipient = portage.settings["PORTAGE_ELOG_MAILURI"].split()[0]
else:
myrecipient = "root@localhost"
-
+
if "PORTAGE_ELOG_MAILFROM" in portage.settings:
myfrom = portage.settings["PORTAGE_ELOG_MAILFROM"]
else:
mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
# need a file object for summarylist()
- myfd = StringIO()
- myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
- myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
+ myfd = BytesIO()
+ line = "GLSA Summary report for host %s\n" % socket.getfqdn()
+ myfd.write(line.encode("utf-8"))
+ line = "(Command was: %s)\n\n" % " ".join(sys.argv)
+ myfd.write(line.encode("utf-8"))
summarylist(glsalist, fd1=myfd, fd2=myfd)
- summary = str(myfd.getvalue())
+ summary = myfd.getvalue().decode("utf-8")
myfd.close()
myattachments = []
for myid in glsalist:
try:
myglsa = Glsa(myid, portage.settings, vardb, portdb)
- except (GlsaTypeException, GlsaFormatException), e:
+ except (GlsaTypeException, GlsaFormatException) as e:
if verbose:
sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
continue
- myfd = StringIO()
+ myfd = BytesIO()
myglsa.dump(outstream=myfd)
- myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
+ attachment = myfd.getvalue().decode("utf-8")
+ myattachments.append(MIMEText(attachment, _charset="utf8"))
myfd.close()
-
+
mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
portage.mail.send_mail(portage.settings, mymessage)
-
+
sys.exit(0)
-
+
# something wrong here, all valid paths are covered with sys.exit()
sys.stderr.write("nothing more to do\n")
sys.exit(2)