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 @@
-