From 21b01375b296332f2e226835d7856edac3513b6c Mon Sep 17 00:00:00 2001
From: Christian Heimes <cheimes@redhat.com>
Date: Fri, 26 Jun 2015 14:23:55 +0200
Subject: [PATCH] Create Python config verification method

pkispawn has grown an additional command line argument --strict. In
strict mode the user configuration file is validated for unsupported
sections and options in each section. All unsupported entries are
reported as error.

https://fedorahosted.org/pki/ticket/1413
---
 .../python/pki/server/deployment/pkimessages.py    |  2 +
 .../python/pki/server/deployment/pkiparser.py      | 50 +++++++++++++++++++++-
 base/server/sbin/pkispawn                          |  7 ++-
 3 files changed, 56 insertions(+), 3 deletions(-)

diff --git a/base/server/python/pki/server/deployment/pkimessages.py b/base/server/python/pki/server/deployment/pkimessages.py
index 6528407929bf3f4a212c5f1154f01574141f4943..37a83a62810bbda0c7771d443991497349b64090 100644
--- a/base/server/python/pki/server/deployment/pkimessages.py
+++ b/base/server/python/pki/server/deployment/pkimessages.py
@@ -110,6 +110,8 @@ PKI_SYMLINK_MISSING_OR_NOT_A_SYMLINK_1 = \
     "Symlink '%s' is either missing or is NOT a symbolic link!"
 PKI_UNABLE_TO_PARSE_1 = "'Could not parse:  '%s'"
 PKI_UNABLE_TO_CREATE_LOG_DIRECTORY_1 = "Could not create log directory '%s'!"
+PKI_USER_CONFIG_SECTIONS_1 = "User configuration contains unsupported section(s) %s"
+PKI_USER_CONFIG_OPTIONS_1 = "User configuration contains unsupported option(s)\n%s"
 
 
 # PKI Deployment 'pkispawn' and 'pkidestroy' Messages
diff --git a/base/server/python/pki/server/deployment/pkiparser.py b/base/server/python/pki/server/deployment/pkiparser.py
index 7e1813c75b891466600117998559c6c1c714ef94..2763dcaa39fbee24d93b7a5a22774453455c260c 100644
--- a/base/server/python/pki/server/deployment/pkiparser.py
+++ b/base/server/python/pki/server/deployment/pkiparser.py
@@ -44,6 +44,26 @@ from . import pkimessages as log
 from . import pkilogging
 
 
+class AdditionalSectionsError(ValueError):
+    def __init__(self, sections):
+        super(AdditionalSectionsError, self).__init__(sections)
+        self.sections = sections
+
+    def __str__(self):
+        return log.PKI_USER_CONFIG_SECTIONS_1 % ', '.join(sorted(self.sections))
+
+
+class AdditionalOptionsError(ValueError):
+    def __init__(self, options):
+        super(AdditionalOptionsError, self).__init__(options)
+        self.options = options
+
+    def __str__(self):
+        options = ["    [%s] %s" % (section, option)
+                   for section, option in sorted(self.options)]
+        return log.PKI_USER_CONFIG_OPTIONS_1 % '\n '.join(options)
+
+
 class PKIConfigParser:
 
     COMMENT_CHAR = '#'
@@ -318,7 +338,29 @@ class PKIConfigParser:
 
         return password
 
-    def read_pki_configuration_file(self):
+    def validate_user_config(self):
+        pki_sections = set(self.pki_config.sections()) | {ConfigParser.DEFAULTSECT}
+        user_sections = set(config.user_config.sections()) | {ConfigParser.DEFAULTSECT}
+
+        diff_sections = user_sections.difference(pki_sections)
+        if diff_sections:
+            raise AdditionalSectionsError(diff_sections)
+
+        diff_options = set()
+        for section in user_sections:
+            for option, _ in config.user_config.items(section):
+                if not self.pki_config.has_option(section, option):
+                    if (ConfigParser.DEFAULTSECT, option) in diff_options:
+                        # this has been already reported for the DEFAULT section
+                        continue
+                    diff_options.add((section, option))
+
+        if diff_options:
+            raise AdditionalOptionsError(diff_options)
+
+        return True
+
+    def read_pki_configuration_file(self, strict=False):
         """Read configuration file sections into dictionaries"""
         rv = 0
         try:
@@ -338,8 +380,10 @@ class PKIConfigParser:
 
                 print 'Loading deployment configuration from ' + \
                       config.user_deployment_cfg + '.'
-                self.pki_config.read([config.user_deployment_cfg])
                 config.user_config.read([config.user_deployment_cfg])
+                if strict:
+                    self.validate_user_config()
+                self.pki_config.read([config.user_deployment_cfg])
 
                 # Look through each section and see if any password settings
                 # are present.  If so, escape any '%' characters.
@@ -373,6 +417,8 @@ class PKIConfigParser:
         except ConfigParser.ParsingError, err:
             print err
             rv = err
+        except (AdditionalSectionsError, AdditionalOptionsError) as err:
+            rv = err
         return rv
 
     def flatten_master_dict(self):
diff --git a/base/server/sbin/pkispawn b/base/server/sbin/pkispawn
index 893a22a32f43a9b3638b20f16096040688ace691..2cd0d3fe438d564fbe9224a9eb5c9b545b224495 100755
--- a/base/server/sbin/pkispawn
+++ b/base/server/sbin/pkispawn
@@ -113,6 +113,11 @@ def main(argv):
         help='configuration filename '
         '(MUST specify complete path)')
 
+    parser.optional.add_argument(
+        '--strict',
+        dest='strict_user_deployment_cfg', action='store_true',
+        help='strict validation of configuration file')
+
     args = parser.process_command_line_arguments()
 
     config.default_deployment_cfg = \
@@ -502,7 +507,7 @@ def main(argv):
         sys.exit(1)
 
     # Read the specified PKI configuration file.
-    rv = parser.read_pki_configuration_file()
+    rv = parser.read_pki_configuration_file(strict=args.strict_user_deployment_cfg)
     if rv != 0:
         config.pki_log.error(log.PKI_UNABLE_TO_PARSE_1, rv,
                              extra=config.PKI_INDENTATION_LEVEL_0)
-- 
2.4.3

