From 0682137b2181fa35aaa4066f838135b2bb53c8f7 Mon Sep 17 00:00:00 2001 From: r00t Date: Wed, 30 May 2018 13:43:30 -0400 Subject: [PATCH] parsing, but need to finish extended value validations for confparser and then get to work actually making the stuff go --- bdisk/bdisk.xsd | 7 ++++ bdisk/confparse.py | 61 ++++++++++++++++++++++++++------ bdisk/utils.py | 33 +++++++++++++---- bin/xmllint.sh | 2 +- docs/examples/single_profile.xml | 6 ++-- 5 files changed, 88 insertions(+), 21 deletions(-) diff --git a/bdisk/bdisk.xsd b/bdisk/bdisk.xsd index 90c6c26..d06f213 100644 --- a/bdisk/bdisk.xsd +++ b/bdisk/bdisk.xsd @@ -645,6 +645,13 @@ + + + + + + + diff --git a/bdisk/confparse.py b/bdisk/confparse.py index 39874b9..d21df53 100644 --- a/bdisk/confparse.py +++ b/bdisk/confparse.py @@ -56,7 +56,7 @@ class Conf(object): self.cfg = {} if validate_cfg: # Validation post-substitution - self.validate() + self.validate(parsed = False) def get_pki_obj(self, pki, pki_type): elem = {} @@ -82,8 +82,7 @@ class Conf(object): return(elem) def get_source(self, source, item, _source): - _source_item = {'flags': [], - 'fname': None} + _source_item = {'flags': [], 'fname': None} elem = source.xpath('./{0}'.format(item))[0] if item == 'checksum': if elem.get('explicit', False): @@ -132,6 +131,8 @@ class Conf(object): return(_source_item) def get_xsd(self): + if isinstance(self.xsd, lxml.etree.XMLSchema): + return(self.xsd) if not self.xsd: path = os.path.join(os.path.dirname(__file__), 'bdisk.xsd') else: @@ -231,10 +232,12 @@ class Conf(object): for e in self.profile.xpath(_xpath): for se in e: if not isinstance(se, lxml.etree._Comment): - self.cfg[t][se.tag] = se.text + self.cfg[t][se.tag] = transform.xml2py(se.text, + attrib = False) for e in ('desc', 'uri', 'ver', 'max_recurse'): _xpath = './meta/{0}/text()'.format(e) - self.cfg[e] = self.profile.xpath(_xpath)[0] + self.cfg[e] = transform.xml2py(self.profile.xpath(_xpath)[0], + attrib = False) # HERE is where we would handle regex patterns. # But we don't, because they're in self.xml_suppl.btags['regex']. #self.cfg['regexes'] = {} @@ -249,7 +252,7 @@ class Conf(object): self.cfg['pki'] = {'clients': []} elem = self.profile.xpath('./pki')[0] self.cfg['pki']['overwrite'] = transform.xml2py( - elem.get('overwrite', 'no')) + elem.get('overwrite', 'false')) ca = elem.xpath('./ca')[0] clients = elem.xpath('./client') self.cfg['pki']['ca'] = self.get_pki_obj(ca, 'ca') @@ -266,7 +269,9 @@ class Conf(object): 'uuid': None} for a in self.cfg['profile']: if a in self.profile.attrib: - self.cfg['profile'][a] = self.profile.attrib[a] + self.cfg['profile'][a] = transform.xml2py( + self.profile.attrib[a], + attrib = True) return() def parse_sources(self): @@ -306,13 +311,49 @@ class Conf(object): attrib = False) return() - def validate(self): - # TODO: perform further validations that we can't do in XSD. + def validate(self, parsed = False): xsd = self.get_xsd() - self.xsd = etree.XMLSchema(xsd) + if not isinstance(xsd, lxml.etree.XMLSchema): + self.xsd = etree.XMLSchema(xsd) + else: + pass # This would return a bool if it validates or not. #self.xsd.validate(self.xml) # We want to get a more detailed exception. xml = etree.fromstring(self.xml_suppl.return_full()) self.xsd.assertValid(xml) + if parsed: + # TODO: perform further validations that we can't do in XSD. + # We wait until after it's parsed to evaluate because otherwise we + # can't use utils.valid(). + # We only bother with stuff that would hinder building, though - + # e.g. we don't check that profile's UUID is a valid UUID4. + # URLs + for url in (self.cfg['uri'], self.cfg['dev']['website']): + if not valid.url(url): + raise ValueError('{0} is not a valid URL.'.format(url)) + # Emails + for k in self.cfg['gpg']['keys']: + if not valid.email(k['email']): + raise ValueError( + 'GPG key {0}: {1} is not a valid email ' + 'address'.format(k['name'], k['email'])) + if not valid.email(self.cfg['dev']['email']): + raise ValueError('{0} is not a valid email address'.format( + self.cfg['dev']['email'])) + if self.cfg['pki']: + if 'subject' in self.cfg['pki']['ca']: + if not valid.email( + self.cfg['pki']['ca']['subject']['emailAddress']): + raise ValueError('{0} is not a valid email ' + 'address'.format( + self.cfg['pki']['ca']['subject']['emailAddress'])) + + if not self.cfg['pki'][x]['subject']: + continue + if not valid.email( + self.cfg['pki'][x]['subject']['emailAddress']): + raise ValueError('{0} is not a valid email ' + 'address'.format( + self.cfg['pki'][x]['subject']['email'])) return() diff --git a/bdisk/utils.py b/bdisk/utils.py index 63045da..416516e 100644 --- a/bdisk/utils.py +++ b/bdisk/utils.py @@ -77,6 +77,8 @@ class detect(object): def password_hash_salt(self, salted_hash): _hash_list = salted_hash.split('$') + if len(_hash_list) < 3: + return(None) salt = _hash_list[2] return(salt) @@ -630,10 +632,28 @@ class transform(object): if _hash: acct['hash_algo'] = _hash else: - raise ValueError( - 'Invalid salted password hash: {0}'.format( - elem.text) - ) + acct['hash_algo'] = None + # We no longer raise ValueError. Per shadow(5): + ####################################################### + # If the password field contains some string that is + # not a valid result of crypt(3), for instance ! or *, + # the user will not be able to use a unix password to + # log in (but the user may log in the system by other + # means). + # This field may be empty, in which case no passwords + # are required to authenticate as the specified login + # name. However, some applications which read the + # /etc/shadow file may decide not to permit any access + # at all if the password field is empty. + # A password field which starts with an exclamation + # mark means that the password is locked. The remaining + # characters on the line represent the password field + # before the password was locked. + ####################################################### + # raise ValueError( + # 'Invalid salted password hash: {0}'.format( + # elem.text) + # ) acct['salt_hash'] = elem.text acct['passphrase'] = None else: @@ -873,13 +893,13 @@ class xml_supplicant(object): return(cfg) def _parse_regexes(self): - for regex in self.profile.xpath('//meta/regexes/pattern'): + for regex in self.profile.xpath('./meta/regexes/pattern'): _key = 'regex%{0}'.format(regex.attrib['id']) self.btags['regex'][_key] = regex.text return() def _parse_variables(self): - for variable in self.profile.xpath('//meta/variables/variable'): + for variable in self.profile.xpath('./meta/variables/variable'): self.btags['variable'][ 'variable%{0}'.format(variable.attrib['id']) ] = variable.text @@ -976,7 +996,6 @@ class xml_supplicant(object): return(path) def return_full(self): - #nsmap = self.return_naked_ns() # https://stackoverflow.com/a/22553145/733214 local_xml = lxml.etree.Element('bdisk', nsmap = self.orig_xml.nsmap, diff --git a/bin/xmllint.sh b/bin/xmllint.sh index d9ff037..525d62f 100755 --- a/bin/xmllint.sh +++ b/bin/xmllint.sh @@ -1,3 +1,3 @@ #!/bin/bash -xmllint -schema ../bdisk/bdisk.xsd ../docs/examples/multi_profile.xml --noout +xmllint -schema /opt/dev/bdisk/bdisk/bdisk.xsd /opt/dev/bdisk/docs/examples/multi_profile.xml --noout diff --git a/docs/examples/single_profile.xml b/docs/examples/single_profile.xml index 38e05d6..02a51b4 100644 --- a/docs/examples/single_profile.xml +++ b/docs/examples/single_profile.xml @@ -43,10 +43,10 @@ $6$7KfIdtHTcXwVrZAC$LZGNeMNz7v5o/cYuA48FAxtZynpIwO5B1CPGXnOW5kCTVpXVt4SypRqfM.AoKkFt/O7MZZ8ySXJmxpELKmdlF1 - {xpath%//meta/names/uxname/text()} + {xpath%../../../meta/names/uxname/text()} - {xpath%//meta/dev/author/text()} + {xpath%../../../meta/dev/author/text()} testpassword @@ -153,7 +153,7 @@ -