# This program is licensed under the GPL, version 2 import os, urllib, xml.dom.minidom from time import asctime, strptime # read config file and provide default config def getconfig(configfile): rValue = dict([ ("GLSA_DIR", "/usr/portage/glsa/"), ("GLSA_PREFIX", "glsa-"), ("CHECKFILE", "/var/cache/edb/glsa"), ("GLSA_SERVER", "http://gentoo.devel-net.org/glsa/"), ("CHECKMODE", "local") ]) if not os.access(configfile, os.R_OK): return rValue filecontent = [line for line in open(configfile).readlines() if line[1] != '#'] for line in filecontent: if '=' in line: name, value = line.split('=') value = value.strip("\n") rValue[name] = value return rValue # get a list of all available GLSA in the given repository def get_glsa_list(p_repository): if not os.access(p_repository, os.R_OK): return [] dirlist = os.listdir(p_repository) prefix = config["GLSA_PREFIX"] return [f[len(prefix):] for f in dirlist if f[:len(prefix)] == prefix] config = getconfig("/etc/portage/glsa.conf") # helper function for xml parser, copied from api doc def getText(node): rc = "" for node in node.childNodes: if node.nodeType == node.TEXT_NODE: rc = rc + node.data return rc # helper function for xml parser: transform range and including to >, <=, ... def parseVersions(node): versions = node.getElementsByTagName("version") rValue = [] for v in versions: mytype = v.getAttribute("range") if mytype == "equal": prefix = "=" elif mytype == "below_equal": prefix = "<=" elif mytype == "above_equal": prefix = ">=" elif mytype == "below": prefix = "<" elif mytype == "above": prefix = ">" rValue.append((prefix, getText(v))) return rValue # GLSA xml data wrapper class class glsa: # set the id and read the xml file def __init__(self, p_id): self.nr = p_id self.read() # read the xml file for this glsa using GLSA_DIR if CHECKMODE=local or GLSA_SERVER otherwise def read(self): if config["CHECKMODE"] == "local": repository = "file://" + config["GLSA_DIR"] else: repository = config["GLSA_SERVER"] myurl = repository + config["GLSA_PREFIX"] + str(self.nr) self.parse(urllib.urlopen(myurl)) # helper function: transform version information from glsa to portage format def getVersionsAsPortage(self,node): versions = parseVersions(node) rValue = [] for prefix, ver in versions: prefix = prefix.replace("<", "\<") prefix = prefix.replace(">", "\>") rValue.append(prefix + self.package + "-" + ver) return rValue # helper function: transform version information from glsa to portage format def getVersionsAsText(self,node): versions = parseVersions(node) rValue = [] for prefix, ver in versions: rValue.append(prefix + ver) return rValue # parse the XML document for this glsa (using xml.dom.minidom) def parse(self, p_file): myroot = xml.dom.minidom.parse(p_file).getElementsByTagName("glsa")[0] if myroot.getAttribute("id") != self.nr: raise Exception("filename and internal id don't match") self.package = getText(myroot.getElementsByTagName("package")[0]) self.date = strptime(getText(myroot.getElementsByTagName("date")[0]), "%Y-%m-%d %H:%M") self.summary = getText(myroot.getElementsByTagName("summary")[0]) self.severity = myroot.getAttribute("severity") tmp = myroot.getElementsByTagName("exploit") self.exploits = [] for e in tmp: self.exploits.append(getText(e)) self.affected_text = self.getVersionsAsText(myroot.getElementsByTagName("affected")[0]) self.fixed_text = self.getVersionsAsText(myroot.getElementsByTagName("fixed")[0]) self.affected_portage = self.getVersionsAsPortage(myroot.getElementsByTagName("affected")[0]) self.fixed_portage = self.getVersionsAsPortage(myroot.getElementsByTagName("fixed")[0]) self.affected = [b for a,b in parseVersions(myroot.getElementsByTagName("affected")[0])] self.fixed = [b for a,b in parseVersions(myroot.getElementsByTagName("fixed")[0])] tmp = myroot.getElementsByTagName("cve") self.cve = [] for c in tmp: self.cve.append({"id": getText(c), "url":c.getAttribute("url")}) self.description = getText(myroot.getElementsByTagName("description")[0]) self.solution_description = \ getText(myroot.getElementsByTagName("solution")[0].getElementsByTagName("description")[0]) tmp = myroot.getElementsByTagName("solution")[0].getElementsByTagName("command") self.precommands = [] self.postcommands = [] for c in tmp: if c.getAttribute("phase") == "before": self.precommands.append(getText(c).strip()) else: self.postcommands.append(getText(c).strip()) self.status = "not applied" checkfile = open(config["CHECKFILE"], "r") for line in checkfile.readlines(): if line.strip() == self.nr: self.status = "applied" checkfile.close() # print a info page about this glsa (like the old announcements) def dump(self): print "GLSA: ", self.nr print "package: ", self.package print "summary: ", self.summary print "severity: ", self.severity print "date: ", asctime(self.date), "UTC" print "status: ", self.status for cve in self.cve: if cve["url"]: print "CVE: ", cve["id"], "(" , cve["url"] , ")" else: print "CVE: ", cve["id"] for e in self.exploits: print "exploit: ", e for v in self.affected_text: print "affected: ", v for v in self.fixed_text: print "fixed: ", v print print 20*'=' + " DESCRIPTION " + 20*'=' print self.description if self.solution_description: print 22*'=' + " SOLUTION " + 21*'=' print self.solution_description # put this glsa into the checkfile, so it is not cheked on later runs def check_in(self): already_in = False checkfile = open(config["CHECKFILE"], "r+") for line in checkfile.readlines(): if line.strip() == self.nr: already_in = True if not already_in: checkfile.write(self.nr) checkfile.close() # do all steps to apply this glsa def fix(self): if self.test(): print "this system is affected" print "fixing not implemented yet, please apply this GLSA manually" else: print "this system is not affected, adding to checklist" self.check_in() # check if the system is affected def test(self): rValue = False for v in self.affected_portage: output = os.popen("portageq match / " + v, "r") if len(output.read().strip()) > 0: rValue = True return rValue # show all steps necessary to apply this glsa def pretend(self): print "to apply this GLSA run the following commands:\n" print print "# check if the system is affected:" print "# (if the commands return nothing you're not affected)" for v in self.affected_portage: print "portageq match / " + v print print "# pre-emerge commands:" for c in self.precommands: print c print print "# emerge commands:" print "emerge sync" if len(self.fixed_portage) > 1: print "# (only one of the following is needed):" for v in self.fixed_portage: print "emerge " + v print print "# post-emerge commands:" for c in self.postcommands: print c