Mercurial > farol > farolluz
view farolluz/parsers/cpe.py @ 48:3826f2701ff2
CPE: Add the possibility to add ourself integrally to the product tree
author | Benoît Allard <benoit.allard@greenbone.net> |
---|---|
date | Tue, 30 Dec 2014 12:30:19 +0100 |
parents | bb1dd2a55643 |
children |
line wrap: on
line source
# -*- coding: utf-8 -*- # Description: # Methods for parsing CPEs # # Authors: # BenoƮt Allard <benoit.allard@greenbone.net> # # Copyright: # Copyright (C) 2014 Greenbone Networks GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. """\ a cpe class to ease the creation of a producttree based on cpe This is based on: NIST Interagency Report 7695 Common Platform Enumeration: Naming Specification Version 2.3 CPE is a trademark of The MITRE Corporation. """ import re from ..producttree import CVRFFullProductName, CVRFRelationship def capitalize(s): """ A custom version of string.capwords that split on _, and join on ' ' """ s = s.replace('\\', '') return ' '.join(c.capitalize() for c in s.split('_')) PCT_MAP ={'!': "%21", '"': "%22", '#': "%23", '$': "%24", '%': "%25", '&': "%26", "'": "%27", '(': "%28", ')': "%29", '*': "%2a", '+': "%2b", ',': "%2c", '/': "%2f", ':': "%3a", ';': "%3b", '<': "%3c", "=": "%3d", '>': "%3e", '?': "%3f", '@': "%40", '[': "%5b", '\\': "%5c","]": "%5d", '^': "%5e", '`': "%60", '{': "%7b", '|': "%7c", '}': "%7d", "~": "%7e"} PCT_MAP_i = dict((v, k) for k, v in PCT_MAP.iteritems()) def pct_encode(c): """ Returns the right percent-encoding of c """ if c in "-.": return c return PCT_MAP[c] return {'!': "%21", '"': "%22", '#': "%23", '$': "%24", '%': "%25", '&': "%26", "'": "%27", '(': "%28", ')': "%29", '*': "%2a", '+': "%2b", ',': "%2c", "-": c, '.': c, '/': "%2f", ':': "%3a", ';': "%3b", '<': "%3c", "=": "%3d", '>': "%3e", '?': "%3f", '@': "%40", '[': "%5b", '\\': "%5c", "]": "%5d", '^': "%5e", '`': "%60", '{': "%7b", '|': "%7c", '}': "%7d", "~": "%7e"}[c] def decode(s): if s == '': return ANY if s == '-': return NA s = s.lower() res = "" idx = 0 embedded = False while idx < len(s): c = s[idx] if c in ".-~": res += "\\" + c embedded = True elif c != '%': res += c embedded = True else: form = s[idx:idx+3] if form == "%01": if (((idx == 0) or (idx == (len(s) - 3))) or ( not embedded and (s[idx-4:idx-1] == "%01")) or (embedded and (len(s) > idx + 6) and (s[idx+3:idx+6] == "%01"))): res += '?' else: raise ValueError elif form == "%02": if (idx == 0) or (idx == len(s) - 3): res += '*' else: raise ValueError else: res += '\\' + PCT_MAP_i[form] embedded = True idx += 2 idx += 1 return CPEAttribute(res) def unbind_value_fs(s): if s == '*': return ANY if s == '-': return NA res = "" idx = 0 embedded = False while idx < len(s): c = s[idx] if re.match("[a-zA-Z0-9_]", c) is not None: res += c embedded = True elif c == "\\": res += s[idx:idx+2] embedded = True idx += 1 elif c == "*": if (idx == 0) or (idx == (len(s) - 1)): res += c embedded = True else: raise ValueError elif c == "?": if (((idx == 0) or (idx == (len(s) - 1))) or (not embedded and (s[idx - 1] == "?")) or (embedded and (s[idx + 1] == "?"))): res += c embedded = False else: raise ValueError else: res += "\\" + c embedded = True idx += 1 return CPEAttribute(res) class CPEAttribute(object): """ We need a special class to deal with ANY / NA / "string" """ def __init__(self, value=None, any=False, na=False): self.any = any self.na = na self.value = value def bind_for_URI(self): # print self.any, self.na, self.value if self.any: return "" if self.na: return '-' return self.transform_for_uri() def transform_for_uri(self): res = "" idx = 0 while idx < len(self.value): c = self.value[idx] if re.match("[a-zA-Z0-9_]", c) is not None: res += c elif c == '\\': idx += 1 c = self.value[idx] res += pct_encode(c) elif c == '?': res += "%01" elif c == '*': res += "%02" idx += 1 return res def bind_for_fs(self): if self.any: return "*" if self.na: return "-" return self.process_quoted_chars() def process_quoted_chars(self): res = "" idx = 0 while idx < len(self.value): c = self.value[idx] if c != '\\': res += c else: idx += 1 c = self.value[idx] if c in ".-_": res += c else: res += '\\' + c idx += 1 return res ANY = CPEAttribute(any=True) NA = CPEAttribute(na=True) class CPE(object): def __init__(self, part=None, vendor=None, product=None, version=None, update=None, edition=None, language=None, sw_edition=None, target_sw=None, target_hw=None, other=None): self.part = part or CPEAttribute(any=True) self.vendor = vendor or CPEAttribute(any=True) self.product = product or CPEAttribute(any=True) self.version = version or CPEAttribute(any=True) self.update = update or CPEAttribute(any=True) self.edition = edition or CPEAttribute(any=True) self.language = language or CPEAttribute(any=True) # Extended attributes: self.sw_edition = sw_edition or CPEAttribute(any=True) self.target_sw = target_sw or CPEAttribute(any=True) self.target_hw = target_hw or CPEAttribute(any=True) self.other = other or CPEAttribute(any=True) def bind_to_URI(self): uri = 'cpe:/' uri += ':'.join(a.bind_for_URI() for a in (self.part, self.vendor, self.product, self.version, self.update)) # Special handling for edition ed = self.edition.bind_for_URI() sw_ed = self.sw_edition.bind_for_URI() t_sw = self.target_sw.bind_for_URI() t_hw = self.target_hw.bind_for_URI() oth = self.other.bind_for_URI() if sw_ed == "" and t_sw == "" and t_hw == "" and oth == "": uri += ":" + ed else: uri += ":~" + '~'.join([ed, sw_ed, t_sw, t_hw, oth]) uri += ':' + self.language.bind_for_URI() return uri.rstrip(':') def unbind_URI(self, uri): for idx, comp in enumerate(uri.split(':')): if idx == 0: continue elif idx == 1: self.part = decode(comp[1:]) elif idx == 2: self.vendor = decode(comp) elif idx == 3: self.product = decode(comp) elif idx == 4: self.version = decode(comp) elif idx == 5: self.update = decode(comp) elif idx == 6: if comp == "" or comp[0] != '~': self.edition = decode(comp) else: ed, sw_ed, t_sw, t_hw, oth = comp[1:].split('~') self.edition = decode(ed) self.sw_edition = decode(sw_ed) self.target_sw = decode(t_sw) self.target_hw = decode(t_hw) self.other = decode(oth) elif idx == 7: self.language = decode(comp) def bind_to_fs(self): fs = 'cpe:2.3:' fs += ':'.join(a.bind_for_fs() for a in (self.part, self.vendor, self.product, self.version, self.update, self.edition, self.language, self.sw_edition, self.target_sw, self.target_hw, self.other)) return fs def unbind_fs(self, fs): for idx, v in enumerate(fs.split(':')): v = unbind_value_fs(v) if idx == 2: self.part = v elif idx == 3: self.vendor = v elif idx == 4: self.product = v elif idx == 5: self.version = v elif idx == 6: self.update = v elif idx == 7: self.edition = v elif idx == 8: self.language = v elif idx == 9: self.sw_edition = v elif idx == 10: self.target_sw = v elif idx == 11: self.target_hw = v elif idx == 12: self.other = v def addToDoc(self, document, finalProduct=True): """ Add the CPE value as full producttree in the document If finalProduct is false, only the elements leading to the product will be added. """ ptree = document._producttree if ptree is None: ptree = document.createProductTree() def next_prodid(): """ A handy function to generate the next available productid """ prods = document._producttree._products if len(prods) > 0: last_prodid = prods[-1]._productid numlen = 0 while last_prodid[- (numlen + 1)] in "0123456789": numlen += 1 if numlen != 0: return last_prodid[:-numlen] + str(int(last_prodid[-numlen:]) + 1) return document.getDocId() + '-P0' # Create the main product tree tree = [] for value, valtype in [(self.vendor, 'Vendor'), (self.product, 'Product Name'), (self.version, 'Product Version'), (self.update, 'Patch Level'), (self.language, 'Language'), (self.target_hw, 'Architecture')]: if value.value is not None: tree.append((valtype, capitalize(value.value))) # Import it last_branch = ptree.importTree(tree) # Add a product there if self.target_sw.value is None: if not finalProduct: return last_branch product = CVRFFullProductName(next_prodid(), str(self), last_branch, self.bind_to_fs()) ptree.addProduct(product) return product else: product = CVRFFullProductName(next_prodid(), str(self), last_branch) ptree.addProduct(product) # We do have a target software, we need to create a relationship ! os = CVRFFullProductName(next_prodid(), self.target_sw.value, ptree) ptree.addProduct(os) rel = CVRFRelationship(product._productid, 'Installed On', os._productid) ptree.addRelationship(rel) if not finalProduct: return rel final_prod = CVRFFullProductName(next_prodid(), ptree.getNameOfRelationship(rel), rel, self.bind_to_fs()) ptree.addProduct(final_prod) return final_prod def __str__(self): res = [] if self.product.value: res.append(capitalize(self.product.value)) if self.version.value: res.append(capitalize(self.version.value)) if not res: return capitalize(self.vendor.value) return ' '.join(res) def parse(s): cpe = CPE() if s[:5] == 'cpe:/': cpe.unbind_URI(s) elif s[:8] == 'cpe:2.3:': cpe.unbind_fs(s) else: raise ValueError(s) return cpe