summaryrefslogtreecommitdiffstats
path: root/src/config
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
commit74aa0bc6779af38018a03fd2cf4419fe85917904 (patch)
tree9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/config
parentInitial commit. (diff)
downloadsssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz
sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/config')
-rw-r--r--src/config/SSSDConfig/__init__.py1752
-rw-r--r--src/config/SSSDConfig/__init__.py.in1752
-rw-r--r--src/config/SSSDConfig/ipachangeconf.py584
-rw-r--r--src/config/SSSDConfig/sssdoptions.py578
-rwxr-xr-xsrc/config/SSSDConfigTest.py2160
-rwxr-xr-xsrc/config/SSSDConfigTest.py2.sh5
-rwxr-xr-xsrc/config/SSSDConfigTest.py3.sh5
-rw-r--r--src/config/cfg_rules.ini840
-rw-r--r--src/config/etc/sssd.api.conf228
-rw-r--r--src/config/etc/sssd.api.d/crash_test_dummy1
-rw-r--r--src/config/etc/sssd.api.d/sssd-ad.conf212
-rw-r--r--src/config/etc/sssd.api.d/sssd-files.conf3
-rw-r--r--src/config/etc/sssd.api.d/sssd-ipa.conf286
-rw-r--r--src/config/etc/sssd.api.d/sssd-krb5.conf31
-rw-r--r--src/config/etc/sssd.api.d/sssd-ldap.conf195
-rw-r--r--src/config/etc/sssd.api.d/sssd-proxy.conf12
-rw-r--r--src/config/etc/sssd.api.d/sssd-simple.conf7
-rw-r--r--src/config/setup.py33
-rw-r--r--src/config/setup.py.in33
-rw-r--r--src/config/testconfigs/noparse.api.conf7
-rw-r--r--src/config/testconfigs/sssd-badversion.conf37
-rw-r--r--src/config/testconfigs/sssd-enabled-option.conf34
-rw-r--r--src/config/testconfigs/sssd-invalid-badbool.conf38
-rw-r--r--src/config/testconfigs/sssd-invalid.conf3
-rw-r--r--src/config/testconfigs/sssd-nonexisting-services-domains.conf13
-rw-r--r--src/config/testconfigs/sssd-noversion.conf58
-rw-r--r--src/config/testconfigs/sssd-test-parse.conf12
-rw-r--r--src/config/testconfigs/sssd-valid.conf57
28 files changed, 8976 insertions, 0 deletions
diff --git a/src/config/SSSDConfig/__init__.py b/src/config/SSSDConfig/__init__.py
new file mode 100644
index 0000000..e7c6284
--- /dev/null
+++ b/src/config/SSSDConfig/__init__.py
@@ -0,0 +1,1752 @@
+'''
+Created on Sep 18, 2009
+
+@author: sgallagh
+'''
+
+import os
+import re
+import sys
+from .sssdoptions import SSSDOptions
+from .ipachangeconf import SSSDChangeConf
+
+# Exceptions
+class SSSDConfigException(Exception): pass
+class ParsingError(Exception): pass
+class AlreadyInitializedError(SSSDConfigException): pass
+class NotInitializedError(SSSDConfigException): pass
+class NoOutputFileError(SSSDConfigException): pass
+class NoServiceError(SSSDConfigException): pass
+class NoSectionError(SSSDConfigException): pass
+class NoOptionError(SSSDConfigException): pass
+class ServiceNotRecognizedError(SSSDConfigException): pass
+class ServiceAlreadyExists(SSSDConfigException): pass
+class NoDomainError(SSSDConfigException): pass
+class DomainNotRecognized(SSSDConfigException): pass
+class DomainAlreadyExistsError(SSSDConfigException): pass
+class NoSuchProviderError(SSSDConfigException): pass
+class NoSuchProviderSubtypeError(SSSDConfigException): pass
+class ProviderSubtypeInUse(SSSDConfigException): pass
+
+def striplist(l):
+ return([x.strip() for x in l])
+
+def options_overlap(options1, options2):
+ overlap = []
+ for option in options1:
+ if option in options2:
+ overlap.append(option)
+ return overlap
+
+class SSSDConfigSchema(SSSDChangeConf):
+ def __init__(self, schemafile, schemaplugindir):
+ SSSDChangeConf.__init__(self)
+ #TODO: get these from a global setting
+ if not schemafile:
+ schemafile = '${prefix}/share/sssd/sssd.api.conf'
+ if not schemaplugindir:
+ schemaplugindir = '${prefix}/share/sssd/sssd.api.d'
+
+ try:
+ # Read the primary config file
+ with open(schemafile, 'r') as fd:
+ self.readfp(fd)
+ # Read in the provider files
+ for file in filter(lambda f: re.search(r'^sssd-.*\.conf$', f),
+ os.listdir(schemaplugindir)):
+ with open(schemaplugindir+ "/" + file) as fd:
+ self.readfp(fd)
+ except IOError:
+ raise
+ except SyntaxError: # can be raised with readfp
+ raise ParsingError
+
+ # Set up lookup table for types
+ self.type_lookup = {
+ 'bool' : bool,
+ 'int' : int,
+ 'long' : long if sys.version_info[0] == 2 else int,
+ 'float': float,
+ 'str' : str,
+ 'list' : list,
+ 'None' : None
+ }
+
+ # Lookup table for acceptable boolean values
+ self.bool_lookup = {
+ 'false' : False,
+ 'true' : True,
+ }
+
+ def get_options(self, section):
+ if not self.has_section(section):
+ raise NoSectionError
+ options = self.options(section)
+
+ # Indexes
+ PRIMARY_TYPE = 0
+ SUBTYPE = 1
+ MANDATORY = 2
+ DEFAULT = 3
+
+ # Parse values
+ parsed_options = {}
+ for option in self.strip_comments_empty(options):
+ unparsed_option = option['value']
+ split_option = striplist(unparsed_option.split(','))
+ optionlen = len(split_option)
+
+ primarytype = self.type_lookup[split_option[PRIMARY_TYPE]]
+ subtype = self.type_lookup[split_option[SUBTYPE]]
+ mandatory = self.bool_lookup[split_option[MANDATORY]]
+
+ if option['name'] in SSSDOptions.option_strings:
+ desc = SSSDOptions.option_strings[option['name']]
+ else:
+ desc = None
+
+ if optionlen == 3:
+ # This option has no defaults
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ None)
+ elif optionlen == 4:
+ if type(split_option[DEFAULT]) == primarytype:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ split_option[DEFAULT])
+ elif primarytype == list:
+ if (type(split_option[DEFAULT]) == subtype):
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ [split_option[DEFAULT]])
+ else:
+ try:
+ if subtype == bool and \
+ type(split_option[DEFAULT]) == str:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ [self.bool_lookup[split_option[DEFAULT].lower()]])
+ else:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ [subtype(split_option[DEFAULT])])
+ except ValueError:
+ raise ParsingError
+ else:
+ try:
+ if primarytype == bool and \
+ type(split_option[DEFAULT]) == str:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ self.bool_lookup[split_option[DEFAULT].lower()])
+ else:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ primarytype(split_option[DEFAULT]))
+ except ValueError:
+ raise ParsingError
+
+ elif optionlen > 4:
+ if (primarytype != list):
+ raise ParsingError
+ fixed_options = []
+ for x in split_option[DEFAULT:]:
+ if type(x) != subtype:
+ try:
+ if (subtype == bool and type(x) == str):
+ newvalue = self.bool_lookup[x.lower()]
+ else:
+ newvalue = subtype(x)
+ fixed_options.extend([newvalue])
+ except ValueError:
+ raise ParsingError
+ else:
+ fixed_options.extend([x])
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ fixed_options)
+ else:
+ # Bad config file
+ raise ParsingError
+
+ return parsed_options
+
+ def get_option(self, section, option):
+ if not self.has_section(section):
+ raise NoSectionError(section)
+ if not self.has_option(section, option):
+ raise NoOptionError("Section [%s] has no option [%s]" %
+ (section, option))
+
+ return self.get_options(section)[option]
+
+ def get_defaults(self, section):
+ if not self.has_section(section):
+ raise NoSectionError(section)
+
+ schema_options = self.get_options(section)
+ defaults = dict([(x,schema_options[x][4])
+ for x in schema_options.keys()
+ if schema_options[x][4] != None])
+
+ return defaults
+
+ def get_services(self):
+ service_list = [x['name'] for x in self.sections()
+ if x['name'] != 'service' and
+ not x['name'].startswith('domain') and
+ not x['name'].startswith('provider')]
+ return service_list
+
+ def get_providers(self):
+ providers = {}
+ for section in self.sections():
+ splitsection = section['name'].split('/')
+ if (splitsection[0] == 'provider'):
+ if(len(splitsection) == 3):
+ if splitsection[1] not in providers:
+ providers[splitsection[1]] = []
+ providers[splitsection[1]].extend([splitsection[2]])
+ for key in providers.keys():
+ providers[key] = tuple(providers[key])
+ return providers
+
+class SSSDConfigObject(object):
+ def __init__(self):
+ self.name = None
+ self.options = {}
+
+ def get_name(self):
+ """
+ Return the name of the object
+
+ === Returns ===
+ The domain name
+
+ === Errors ===
+ No errors
+ """
+ return self.name
+
+ def get_option(self, optionname):
+ """
+ Return the value of an service option
+
+ optionname:
+ The option to get.
+
+ === Returns ===
+ The value for the requested option.
+
+ === Errors ===
+ NoOptionError:
+ The specified option was not listed in the service
+ """
+ if optionname in self.options.keys():
+ return self.options[optionname]
+ raise NoOptionError(optionname)
+
+ def get_all_options(self):
+ """
+ Return a dictionary of name/value pairs for this object
+
+ === Returns ===
+ A dictionary of name/value pairs currently in use for this object
+
+ === Errors ===
+ No errors
+ """
+ return self.options
+
+ def remove_option(self, optionname):
+ """
+ Remove an option from the object. If the option does not exist, it is ignored.
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ No errors
+ """
+ if optionname in self.options:
+ del self.options[optionname]
+
+class SSSDService(SSSDConfigObject):
+ '''
+ Object to manipulate SSSD service options
+ '''
+
+ def __init__(self, servicename, apischema):
+ """
+ Create a new SSSDService, setting its defaults to those found in the
+ schema. This constructor should not be used directly. Use
+ SSSDConfig.new_service() instead.
+
+ name:
+ The service name
+ apischema:
+ An SSSDConfigSchema? object created by SSSDConfig.__init__()
+
+ === Returns ===
+ The newly-created SSSDService object.
+
+ === Errors ===
+ TypeError:
+ The API schema passed in was unusable or the name was not a string.
+ ServiceNotRecognizedError:
+ The service was not listed in the schema
+ """
+ SSSDConfigObject.__init__(self)
+
+ if not isinstance(apischema, SSSDConfigSchema) or type(servicename) != str:
+ raise TypeError
+
+ if not apischema.has_section(servicename):
+ raise ServiceNotRecognizedError(servicename)
+
+ self.name = servicename
+ self.schema = apischema
+
+ # Set up the service object with any known defaults
+ self.options = {}
+
+ # Include a list of hidden options
+ self.hidden_options = []
+
+ # Set up default options for all services
+ self.options.update(self.schema.get_defaults('service'))
+
+ # Set up default options for this service
+ self.options.update(self.schema.get_defaults(self.name))
+
+ def list_options_with_mandatory(self):
+ """
+ List options for the service, including the mandatory flag.
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), whether it is mandatory, the
+ translated option description, and the default value (or 'None') as
+ the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, False, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = {}
+
+ # Get the list of available options for all services
+ schema_options = self.schema.get_options('service')
+ options.update(schema_options)
+
+ schema_options = self.schema.get_options(self.name)
+ options.update(schema_options)
+
+ return options
+
+ def list_options(self):
+ """
+ List all options that apply to this service
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'services' :
+ (list, str, u'SSSD Services to start', ['nss', 'pam']) }
+
+ === Errors ===
+ No Errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def list_mandatory_options(self):
+ """
+ List all mandatory options that apply to this service
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'services' :
+ (list, str, u'SSSD Services to start', ['nss', 'pam']) }
+
+ === Errors ===
+ No Errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ if options[key][2]:
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def set_option(self, optionname, value):
+ """
+ Set a service option to the specified value (or values)
+
+ optionname:
+ The option to change
+ value:
+ The value to set. This may be a single value or a list of values. If
+ it is set to None, it resets the option to its default.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NoOptionError:
+ The specified option is not listed in the schema
+ TypeError:
+ The value specified was not of the expected type
+ """
+ if self.schema.has_option(self.name, optionname):
+ option_schema = self.schema.get_option(self.name, optionname)
+ elif self.schema.has_option('service', optionname):
+ option_schema = self.schema.get_option('service', optionname)
+ elif optionname in self.hidden_options:
+ # Set this option and do not add it to the list of changeable values
+ self.options[optionname] = value
+ return
+ else:
+ raise NoOptionError('Section [%s] has no option [%s]' % (self.name, optionname))
+
+ if value == None:
+ self.remove_option(optionname)
+ return
+
+ raise_error = False
+
+ # If we were expecting a list and didn't get one,
+ # Create a list with a single entry. If it's the
+ # wrong subtype, it will fail below
+ if option_schema[0] == list and type(value) != list:
+ if type(value) == str:
+ value = striplist(value.split(','))
+ else:
+ value = [value]
+
+ if type(value) != option_schema[0]:
+ # If it's possible to convert it, do so
+ try:
+ if option_schema[0] == bool and type(value) == str:
+ value = self.schema.bool_lookup[value.lower()]
+ elif option_schema[0] == int and type(value) == str:
+ # Make sure we handle any reasonable base
+ value = int(value, 0)
+ else:
+ value = option_schema[0](value)
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s for %s, received %s' %
+ (option_schema[0], optionname, type(value)))
+
+ if type(value) == list:
+ # Iterate through the list an ensure that all members
+ # are of the appropriate subtype
+ try:
+ newvalue = []
+ for x in value:
+ if option_schema[1] == bool and \
+ type(x) == str:
+ newvalue.extend([self.schema.bool_lookup[x.lower()]])
+ else:
+ newvalue.extend([option_schema[1](x)])
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s' % option_schema[1])
+
+ value = newvalue
+
+ self.options[optionname] = value
+
+class SSSDDomain(SSSDConfigObject):
+ """
+ Object to manipulate SSSD domain options
+ """
+ def __init__(self, domainname, apischema):
+ """
+ Creates a new, empty SSSDDomain. This domain is inactive by default.
+ This constructor should not be used directly. Use
+ SSSDConfig.new_domain() instead.
+
+ name:
+ The domain name.
+ apischema:
+ An SSSDConfigSchema object created by SSSDConfig.__init__()
+
+ === Returns ===
+ The newly-created SSSDDomain object.
+
+ === Errors ===
+ TypeError:
+ apischema was not an SSSDConfigSchema object or domainname was not
+ a string
+ """
+ SSSDConfigObject.__init__(self)
+
+ if not isinstance(apischema, SSSDConfigSchema) or type(domainname) != str:
+ raise TypeError
+
+ self.name = domainname
+ self.schema = apischema
+ self.active = False
+ self.oldname = None
+ self.providers = []
+
+ # Set up the domain object with any known defaults
+ self.options = {}
+
+ # Set up default options for all domains
+ self.options.update(self.schema.get_defaults('provider'))
+ self.options.update(self.schema.get_defaults('domain'))
+
+ def set_active(self, active):
+ """
+ Enable or disable this domain
+
+ active:
+ Boolean value. If True, this domain will be added to the active
+ domains list when it is saved. If False, it will be removed from the
+ active domains list when it is saved.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ No errors
+ """
+ self.active = bool(active)
+
+ def list_options_with_mandatory(self):
+ """
+ List options for the currently-configured providers, including the
+ mandatory flag
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), whether it is mandatory, the
+ translated option description, and the default value (or 'None') as
+ the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, False, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = {}
+ # Get the list of available options for all domains
+ options.update(self.schema.get_options('provider'))
+
+ options.update(self.schema.get_options('domain'))
+
+ # Candidate for future optimization: will update primary type
+ # for each subtype
+ for (provider, providertype) in self.providers:
+ schema_options = self.schema.get_options('provider/%s'
+ % provider)
+ options.update(schema_options)
+ schema_options = self.schema.get_options('provider/%s/%s'
+ % (provider, providertype))
+ options.update(schema_options)
+ return options
+
+ def list_options(self):
+ """
+ List options available for the currently-configured providers.
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def list_mandatory_options(self):
+ """
+ List mandatory options for the currently-configured providers.
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ if options[key][2]:
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def list_provider_options(self, provider, provider_type=None):
+ """
+ If provider_type is specified, list all options applicable to that
+ target, otherwise list all possible options available for a provider.
+
+ type:
+ Provider backend type. (e.g. local, ldap, krb5, etc.)
+ provider_type:
+ Subtype of the backend type. (e.g. id, auth, access, chpass)
+
+ === Returns ===
+
+ A dictionary of configurable options for the specified provider type.
+ This dictionary is keyed on the option name with a tuple of the
+ variable type, subtype ('None' if the type is not a collection type),
+ the translated option description, and the default value (or 'None')
+ as the value.
+
+ === Errors ===
+
+ NoSuchProviderError:
+ The specified provider is not listed in the schema or plugins
+ NoSuchProviderSubtypeError:
+ The specified provider subtype is not listed in the schema
+ """
+ #TODO section checking
+
+ options = self.schema.get_options('provider/%s' % provider)
+ if(provider_type):
+ options.update(self.schema.get_options('provider/%s/%s' %
+ (provider, provider_type)))
+ else:
+ # Add options from all provider subtypes
+ known_providers = self.list_providers()
+ for provider_type in known_providers[provider]:
+ options.update(self.list_provider_options(provider,
+ provider_type))
+ return options
+
+ def list_providers(self):
+ """
+ Return a dictionary of providers.
+
+ === Returns ===
+ Returns a dictionary of providers, keyed on the primary type, with the
+ value being a tuple of the subtypes it supports.
+
+ Example:
+ { 'ldap' : ('id', 'auth', 'chpass') }
+
+ === Errors ===
+ No Errors
+ """
+ return self.schema.get_providers()
+
+ def set_option(self, option, value):
+ """
+ Set a domain option to the specified value (or values)
+
+ option:
+ The option to change.
+ value:
+ The value to set. This may be a single value or a list of values.
+ If it is set to None, it resets the option to its default.
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ NoOptionError:
+ The specified option is not listed in the schema
+ TypeError:
+ The value specified was not of the expected type
+ """
+ options = self.list_options()
+ if (option not in options.keys()):
+ raise NoOptionError('Section [%s] has no option [%s]' %
+ (self.name, option))
+
+ if value == None:
+ self.remove_option(option)
+ return
+
+ option_schema = options[option]
+ raise_error = False
+
+ # If we were expecting a list and didn't get one,
+ # Create a list with a single entry. If it's the
+ # wrong subtype, it will fail below
+ if option_schema[0] == list and type(value) != list:
+ if type(value) == str:
+ value = striplist(value.split(','))
+ else:
+ value = [value]
+
+ if type(value) != option_schema[0]:
+ # If it's possible to convert it, do so
+ try:
+ if option_schema[0] == bool and \
+ type(value) == str:
+ value = self.schema.bool_lookup[value.lower()]
+ elif option_schema[0] == int and type(value) == str:
+ # Make sure we handle any reasonable base
+ value = int(value, 0)
+ else:
+ value = option_schema[0](value)
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s for %s, received %s' %
+ (option_schema[0], option, type(value)))
+
+ if type(value) == list:
+ # Iterate through the list an ensure that all members
+ # are of the appropriate subtype
+ try:
+ newvalue = []
+ for x in value:
+ if option_schema[1] == bool and \
+ type(x) == str:
+ newvalue.extend([self.schema.bool_lookup[x.lower()]])
+ else:
+ newvalue.extend([option_schema[1](x)])
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s' % option_schema[1])
+ value = newvalue
+
+ # Check whether we're adding a provider entry.
+ is_provider = option.rfind('_provider')
+ if (is_provider > 0):
+ provider = option[:is_provider]
+ try:
+ self.add_provider(value, provider)
+ except NoSuchProviderError:
+ raise NoOptionError
+ else:
+ self.options[option] = value
+
+ def set_name(self, newname):
+ """
+ Change the name of the domain
+
+ newname:
+ New name for this domain
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ TypeError:
+ newname was not a string
+ """
+
+ if type(newname) != str:
+ raise TypeError
+
+ if not self.oldname:
+ # Only set the oldname once
+ self.oldname = self.name
+ self.name = newname
+
+ def add_provider(self, provider, provider_type):
+ """
+ Add a new provider type to the domain
+
+ type:
+ Provider backend type. (e.g. local, ldap, krb5, etc.)
+ subtype:
+ Subtype of the backend type. (e.g. id, auth, chpass)
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ ProviderSubtypeInUse:
+ Another backend is already providing this subtype
+ NoSuchProviderError:
+ The specified provider is not listed in the schema or plugins
+ NoSuchProviderSubtypeError:
+ The specified provider subtype is not listed in the schema
+ """
+ # Check that provider and provider_type are valid
+ configured_providers = self.list_providers()
+ if provider in configured_providers.keys():
+ if provider_type not in configured_providers[provider]:
+ raise NoSuchProviderSubtypeError(provider_type)
+ else:
+ raise NoSuchProviderError
+
+ # Don't add a provider twice
+ with_this_type = [x for x in self.providers if x[1] == provider_type]
+ if len(with_this_type) > 1:
+ # This should never happen!
+ raise ProviderSubtypeInUse
+ if len(with_this_type) == 1:
+ if with_this_type[0][0] != provider:
+ raise ProviderSubtypeInUse(with_this_type[0][0])
+ else:
+ self.providers.extend([(provider, provider_type)])
+
+ option_name = '%s_provider' % provider_type
+ self.options[option_name] = provider
+
+ # Add defaults for this provider
+ self.options.update(self.schema.get_defaults('provider/%s' %
+ provider))
+ self.options.update(self.schema.get_defaults('provider/%s/%s' %
+ (provider,
+ provider_type)))
+
+ def remove_provider(self, provider_type):
+ """
+ Remove a provider from the domain. If the provider is not present, it
+ is ignored.
+
+ provider_type:
+ Subtype of the backend type. (e.g. id, auth, chpass)
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ No Errors
+ """
+
+ provider = None
+ for (provider, ptype) in self.providers:
+ if ptype == provider_type:
+ break
+ provider = None
+
+ # Check whether the provider_type was found
+ if not provider:
+ return
+
+ # Remove any unused options when removing the provider.
+ options = self.list_provider_options(provider, provider_type)
+
+ # Trim any options that are used by other providers,
+ # if that provider is in use
+ for (prov, ptype) in self.providers:
+ # Ignore the one being removed
+ if (prov, ptype) == (provider, provider_type):
+ continue
+
+ provider_options = self.list_provider_options(prov, ptype)
+ overlap = options_overlap(options.keys(), provider_options.keys())
+ for opt in overlap:
+ del options[opt]
+
+ # We should now have a list of options used only by this
+ # provider. So we remove them.
+ for option in options:
+ if option in self.options:
+ del self.options[option]
+
+ # Remove this provider from the option list
+ option = '%s_provider' % provider_type
+ if option in self.options:
+ del self.options[option]
+
+ self.providers.remove((provider, provider_type))
+
+class SSSDConfig(SSSDChangeConf):
+ """
+ class SSSDConfig
+ Primary class for operating on SSSD configurations
+ """
+ def __init__(self, schemafile=None, schemaplugindir=None):
+ """
+ Initialize the SSSD config parser/editor. This constructor does not
+ open or create a config file. If the schemafile and schemaplugindir
+ are not passed, it will use the system defaults.
+
+ schemafile:
+ The path to the API schema config file. Usually
+ ${prefix}/share/sssd/sssd.api.conf
+ schemaplugindir:
+ The path the directory containing the provider schema config files.
+ Usually ${prefix}/share/sssd/sssd.api.d
+
+ === Returns ===
+ The newly-created SSSDConfig object.
+
+ === Errors ===
+ IOError:
+ Exception raised when the schema file could not be opened for
+ reading.
+ ParsingError:
+ The main schema file or one of those in the plugin directory could
+ not be parsed.
+ """
+ SSSDChangeConf.__init__(self)
+ self.schema = SSSDConfigSchema(schemafile, schemaplugindir)
+ self.configfile = None
+ self.initialized = False
+ self.API_VERSION = 2
+
+ def import_config(self,configfile=None):
+ """
+ Read in a config file, populating all of the service and domain
+ objects with the read values.
+
+ configfile:
+ The path to the SSSD config file. If not specified, use the system
+ default, usually ${prefix}/etc/sssd.conf
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ IOError:
+ Exception raised when the file could not be opened for reading
+ ParsingError:
+ Exception raised when errors occur attempting to parse a file.
+ AlreadyInitializedError:
+ This SSSDConfig object was already initialized by a call to
+ import_config() or new_config()
+ """
+ if self.initialized:
+ raise AlreadyInitializedError
+
+ if not configfile:
+ #TODO: get this from a global setting
+ configfile = '${prefix}/etc/sssd/sssd.conf'
+ # open will raise an IOError if it fails
+ with open(configfile, 'r') as fd:
+ try:
+ self.readfp(fd)
+ except Exception:
+ raise ParsingError
+
+ self.configfile = configfile
+ self.initialized = True
+
+ try:
+ if int(self.get('sssd', 'config_file_version')) != self.API_VERSION:
+ raise ParsingError("Wrong config_file_version")
+ except TypeError:
+ # This happens when config_file_version is missing. We
+ # can assume it is the default version and continue.
+ pass
+
+ def new_config(self):
+ """
+ Initialize the SSSDConfig object with the defaults from the schema.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ AlreadyInitializedError:
+ This SSSDConfig object was already initialized by a call to
+ import_config() or new_config()
+ """
+ if self.initialized:
+ raise AlreadyInitializedError
+
+ self.initialized = True
+
+ #Initialize all services
+ for servicename in self.schema.get_services():
+ service = self.new_service(servicename)
+
+ def write(self, outputfile=None):
+ """
+ Write out the configuration to a file.
+
+ outputfile:
+ The path to write the new config file. If it is not specified, it
+ will use the path specified by the import() call.
+ === Returns ===
+ No return value
+
+ === Errors ===
+ IOError:
+ Exception raised when the file could not be opened for writing
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoOutputFileError:
+ No outputfile was specified and this SSSDConfig object was not
+ initialized by import()
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if outputfile == None:
+ if(self.configfile == None):
+ raise NoOutputFileError
+
+ outputfile = self.configfile
+
+ # open() will raise IOError if it fails
+ old_umask = os.umask(0o177)
+ with open(outputfile, "w") as of:
+ output = self.dump(self.opts)
+ of.write(output)
+ os.umask(old_umask)
+
+ def list_active_services(self):
+ """
+ Return a list of all active services.
+
+ === Returns ===
+ The list of active services.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'services')):
+ active_services = striplist(self.get('sssd', 'services').split(','))
+ service_dict = dict.fromkeys(active_services)
+ if '' in service_dict:
+ del service_dict['']
+
+ # Remove any entries in this list that don't
+ # correspond to an active service, for integrity
+ configured_services = self.list_services()
+ for srv in list(service_dict):
+ if srv not in configured_services:
+ del service_dict[srv]
+
+ active_services = list(service_dict)
+ else:
+ active_services = []
+
+ return active_services
+
+ def list_inactive_services(self):
+ """
+ Return a list of all disabled services.
+
+ === Returns ===
+ The list of inactive services.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'services')):
+ active_services = striplist(self.get('sssd', 'services').split(','))
+ else:
+ active_services = []
+
+ services = [x for x in self.list_services()
+ if x not in active_services]
+ return services
+
+ def list_services(self):
+ """
+ Retrieve a list of known services.
+
+ === Returns ===
+ The list of known services.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ service_list = [x['name'] for x in self.sections()
+ if not x['name'].startswith('domain') ]
+ return service_list
+
+ def get_service(self, name):
+ """
+ Get an SSSDService object to edit a service.
+
+ name:
+ The name of the service to return.
+
+ === Returns ===
+ An SSSDService instance containing the current state of a service in
+ the SSSDConfig
+
+ === Errors ===
+ NoServiceError:
+ There is no such service with the specified name in the SSSDConfig.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not self.has_section(name):
+ raise NoServiceError
+
+ service = SSSDService(name, self.schema)
+ for opt in self.strip_comments_empty(self.options(name)):
+ try:
+ service.set_option(opt['name'], opt['value'])
+ except NoOptionError:
+ # If we come across an option that we don't recognize,
+ # we should just ignore it and continue
+ pass
+
+ return service
+
+ def new_service(self, name):
+ """
+ Create a new service from the defaults and return the SSSDService
+ object for it. This function will also add this service to the list of
+ active services in the [SSSD] section.
+
+ name:
+ The name of the service to create and return.
+
+ === Returns ===
+ The newly-created SSSDService object
+
+ === Errors ===
+ ServiceNotRecognizedError:
+ There is no such service in the schema.
+ ServiceAlreadyExistsError:
+ The service being created already exists in the SSSDConfig object.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if (self.has_section(name)):
+ raise ServiceAlreadyExists(name)
+
+ service = SSSDService(name, self.schema)
+ self.save_service(service)
+ return service
+
+ def activate_service(self, name):
+ """
+ Activate a service
+
+ name:
+ The name of the service to activate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoServiceError:
+ There is no such service with the specified name in the SSSDConfig.
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_services():
+ raise NoServiceError
+
+ item = self.get_option_index('sssd', 'services')[1]
+ if not item:
+ self.set('sssd','services', name)
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to add a new value
+ service_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if '' in service_dict:
+ del service_dict['']
+
+ # Add a new key for the service being activated
+ service_dict[name] = None
+
+ # Write out the joined keys
+ self.set('sssd','services', ", ".join(service_dict.keys()))
+
+ def deactivate_service(self, name):
+ """
+ Deactivate a service
+
+ name:
+ The name of the service to deactivate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoServiceError:
+ There is no such service with the specified name in the SSSDConfig.
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_services():
+ raise NoServiceError
+ item = self.get_option_index('sssd', 'services')[1]
+ if not item:
+ self.set('sssd','services', '')
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to remove the one unwanted value.
+ service_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if '' in service_dict:
+ del service_dict['']
+
+ # Remove the unwanted service from the lest
+ if name in service_dict:
+ del service_dict[name]
+
+ # Write out the joined keys
+ self.set('sssd','services', ", ".join(service_dict.keys()))
+
+ def delete_service(self, name):
+ """
+ Remove a service from the SSSDConfig object. This function will also
+ remove this service from the list of active services in the [SSSD]
+ section. Has no effect if the service does not exist.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ self.delete_option('section', name)
+
+ def save_service(self, service):
+ """
+ Save the changes made to the service object back to the SSSDConfig
+ object.
+
+ service_object:
+ The SSSDService object to save to the configuration.
+
+ === Returns ===
+ No return value
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ TypeError:
+ service_object was not of the type SSSDService
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not isinstance(service, SSSDService):
+ raise TypeError
+
+ name = service.get_name()
+ # Ensure that the existing section is removed
+ # This way we ensure that we are getting a
+ # complete copy of the service.
+ # delete_option() is a noop if the section
+ # does not exist.
+ index = self.delete_option('section', name)
+
+ addkw = []
+ for option,value in service.get_all_options().items():
+ if (type(value) == list):
+ value = ', '.join(value)
+ if option == "debug_level":
+ value = self._get_debug_level_val(value)
+ addkw.append( { 'type' : 'option',
+ 'name' : option,
+ 'value' : str(value) } )
+
+ self.add_section(name, addkw, index)
+
+ def list_active_domains(self):
+ """
+ Return a list of all active domains.
+
+ === Returns ===
+ The list of configured, active domains.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'domains')):
+ sssd_domains = striplist(self.get('sssd', 'domains').split(','))
+ domain_dict = dict.fromkeys(sssd_domains)
+ if '' in domain_dict:
+ del domain_dict['']
+ sssd_domains = list(domain_dict)
+ else:
+ sssd_domains = []
+
+ domains = self.list_domains()
+ for dom in self.list_domains():
+ # Remove explicitly enabled from the list
+ if self.has_option('domain/%s' % dom, 'enabled'):
+ if self.get('domain/%s' % dom, 'enabled') == 'false':
+ domains.remove(dom)
+ if dom in sssd_domains:
+ sssd_domains.remove(dom)
+ else:
+ if dom not in sssd_domains:
+ domains.remove(dom)
+
+ return domains
+
+ def list_inactive_domains(self):
+ """
+ Return a list of all configured, but disabled domains.
+
+ === Returns ===
+ The list of configured, inactive domains.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'domains')):
+ sssd_domains = striplist(self.get('sssd', 'domains').split(','))
+ domain_dict = dict.fromkeys(sssd_domains)
+ if '' in domain_dict:
+ del domain_dict['']
+ sssd_domains = list(domain_dict)
+ else:
+ sssd_domains = []
+
+ domains = self.list_domains()
+ for dom in self.list_domains():
+ # Remove explicitly enabled from the list
+ if self.has_option('domain/%s' % dom, 'enabled'):
+ if self.get('domain/%s' % dom, 'enabled') == 'true':
+ domains.remove(dom)
+ else:
+ if dom in sssd_domains:
+ domains.remove(dom)
+
+ return domains
+
+ def list_domains(self):
+ """
+ Return a list of all configured domains, including inactive domains.
+
+ === Returns ===
+ The list of configured domains, both active and inactive.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ domains = [x['name'][7:] for x in self.sections() if x['name'].startswith('domain/')]
+ return domains
+
+ def get_domain(self, name):
+ """
+ Get an SSSDDomain object to edit a domain.
+
+ name:
+ The name of the domain to return.
+
+ === Returns ===
+ An SSSDDomain instance containing the current state of a domain in the
+ SSSDConfig
+
+ === Errors ===
+ NoDomainError:
+ There is no such domain with the specified name in the SSSDConfig.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not self.has_section('domain/%s' % name):
+ raise NoDomainError(name)
+
+ domain = SSSDDomain(name, self.schema)
+
+ # Read in the providers first or we may have type
+ # errors trying to read in their options
+ providers = [ (x['name'],x['value']) for x in self.strip_comments_empty(self.options('domain/%s' % name))
+ if x['name'].rfind('_provider') > 0]
+
+ # Handle the default value for providers if those weren't set in the configuration file
+ default_providers = {
+ 'id_provider': None,
+ 'sudo_provider': 'id_provider',
+ 'auth_provider': 'id_provider',
+ 'chpass_provider': 'auth_provider',
+ 'autofs_provider': 'id_provider',
+ 'selinux_provider': 'id_provider',
+ 'subdomains_provider': 'id_provider',
+ 'session_provider': 'id_provider',
+ 'hostid_provider': 'id_provider',
+ 'resolver_provider': 'id_provider',
+ }
+
+ configured_providers = domain.list_providers()
+ providers_list = [x[0] for x in providers]
+
+ if 'access_provider' not in providers_list:
+ providers.append(('access_provider', 'permit'))
+
+ for provider, default_provider in default_providers.items():
+ if provider in providers_list:
+ continue
+
+ if default_provider in providers_list:
+ default_provider_value = providers[providers_list.index(default_provider)]
+ if default_provider_value[1] in configured_providers.keys():
+ if provider[:provider.rfind('_provider')] in configured_providers[default_provider_value[1]]:
+ providers.append((provider, default_provider_value[1]))
+
+ for (option, value) in providers:
+ try:
+ domain.set_option(option, value)
+ except NoOptionError:
+ # If we come across an option that we don't recognize,
+ # we should just ignore it and continue
+ pass
+
+ # Read in all the options from the configuration
+ for opt in self.strip_comments_empty(self.options('domain/%s' % name)):
+ if (opt['name'], opt['value']) not in providers:
+ try:
+ domain.set_option(opt['name'], opt['value'])
+ except NoOptionError:
+ # If we come across an option that we don't recognize,
+ # we should just ignore it and continue
+ pass
+
+ # Determine if this domain is currently active
+ domain.active = self.is_domain_active(name)
+
+ return domain
+
+ def new_domain(self, name):
+ """
+ Create a new, empty domain and return the SSSDDomain object for it.
+
+ name:
+ The name of the domain to create and return.
+
+ === Returns ===
+ The newly-created SSSDDomain object
+
+ === Errors ===
+ DomainAlreadyExistsError:
+ The service being created already exists in the SSSDConfig object.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if self.has_section('domain/%s' % name):
+ raise DomainAlreadyExistsError
+
+ domain = SSSDDomain(name, self.schema)
+ self.save_domain(domain)
+ return domain
+
+ def is_domain_active(self, name):
+ """
+ Is a particular domain set active
+
+ name:
+ The name of the configured domain to check
+
+ === Returns ===
+ True if the domain is active, False if it is inactive
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoDomainError:
+ No domain by this name is configured
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_domains():
+ raise NoDomainError
+
+ return name in self.list_active_domains()
+
+ def activate_domain(self, name):
+ """
+ Activate a configured domain
+
+ name:
+ The name of the configured domain to activate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoDomainError:
+ No domain by this name is configured
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_domains():
+ raise NoDomainError
+
+ item = self.get_option_index('sssd', 'domains')[1]
+ if not item:
+ self.set('sssd','domains', name)
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to add a new value
+ domain_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if '' in domain_dict:
+ del domain_dict['']
+
+ # Add a new key for the domain being activated
+ domain_dict[name] = None
+
+ # Write out the joined keys
+ self.set('sssd','domains', ", ".join(domain_dict.keys()))
+
+ def deactivate_domain(self, name):
+ """
+ Deactivate a configured domain
+
+ name:
+ The name of the configured domain to deactivate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoDomainError:
+ No domain by this name is configured
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_domains():
+ raise NoDomainError
+ item = self.get_option_index('sssd', 'domains')[1]
+ if not item:
+ self.set('sssd','domains', '')
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to remove the one unwanted value.
+ domain_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if '' in domain_dict:
+ del domain_dict['']
+
+ # Remove the unwanted domain from the lest
+ if name in domain_dict:
+ del domain_dict[name]
+
+ # Write out the joined keys
+ self.set('sssd','domains', ", ".join(domain_dict.keys()))
+
+ def delete_domain(self, name):
+ """
+ Remove a domain from the SSSDConfig object. This function will also
+ remove this domain from the list of active domains in the [SSSD]
+ section, if it is there.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ # Remove the domain from the active domains list if applicable
+ self.deactivate_domain(name)
+ self.delete_option('section', 'domain/%s' % name)
+
+ def save_domain(self, domain):
+ """
+ Save the changes made to the domain object back to the SSSDConfig
+ object. If this domain is marked active, ensure it is present in the
+ active domain list in the [SSSD] section
+
+ domain_object:
+ The SSSDDomain object to save to the configuration.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ TypeError:
+ domain_object was not of type SSSDDomain
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not isinstance(domain, SSSDDomain):
+ raise TypeError
+
+ name = domain.get_name()
+
+ oldindex = None
+ if domain.oldname and domain.oldname != name:
+ # We are renaming this domain
+ # Remove the old section
+
+ self.deactivate_domain(domain.oldname)
+ oldindex = self.delete_option('section', 'domain/%s' %
+ domain.oldname)
+
+ # Reset the oldname, in case we're not done with
+ # this domain object.
+ domain.oldname = None;
+
+ sectionname = 'domain/%s' % name
+ (no, section_subtree) = self.findOpts(self.opts, 'section', sectionname)
+
+ if name not in self.list_domains():
+ self.add_section(sectionname, []);
+
+ section_options = self.options(sectionname)[:]
+ for option in section_options:
+ if option['type'] == 'option':
+ if option['name'] not in domain.get_all_options():
+ self.delete_option_subtree(section_subtree['value'], 'option', option['name'], True)
+
+ for option,value in domain.get_all_options().items():
+ if (type(value) == list):
+ value = ', '.join(value)
+ if option == "debug_level":
+ value = self._get_debug_level_val(value)
+ self.set(sectionname, option, str(value))
+
+ if domain.active:
+ self.activate_domain(name)
+ else:
+ self.deactivate_domain(name)
diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
new file mode 100644
index 0000000..ac2b82a
--- /dev/null
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -0,0 +1,1752 @@
+'''
+Created on Sep 18, 2009
+
+@author: sgallagh
+'''
+
+import os
+import re
+import sys
+from .sssdoptions import SSSDOptions
+from .ipachangeconf import SSSDChangeConf
+
+# Exceptions
+class SSSDConfigException(Exception): pass
+class ParsingError(Exception): pass
+class AlreadyInitializedError(SSSDConfigException): pass
+class NotInitializedError(SSSDConfigException): pass
+class NoOutputFileError(SSSDConfigException): pass
+class NoServiceError(SSSDConfigException): pass
+class NoSectionError(SSSDConfigException): pass
+class NoOptionError(SSSDConfigException): pass
+class ServiceNotRecognizedError(SSSDConfigException): pass
+class ServiceAlreadyExists(SSSDConfigException): pass
+class NoDomainError(SSSDConfigException): pass
+class DomainNotRecognized(SSSDConfigException): pass
+class DomainAlreadyExistsError(SSSDConfigException): pass
+class NoSuchProviderError(SSSDConfigException): pass
+class NoSuchProviderSubtypeError(SSSDConfigException): pass
+class ProviderSubtypeInUse(SSSDConfigException): pass
+
+def striplist(l):
+ return([x.strip() for x in l])
+
+def options_overlap(options1, options2):
+ overlap = []
+ for option in options1:
+ if option in options2:
+ overlap.append(option)
+ return overlap
+
+class SSSDConfigSchema(SSSDChangeConf):
+ def __init__(self, schemafile, schemaplugindir):
+ SSSDChangeConf.__init__(self)
+ #TODO: get these from a global setting
+ if not schemafile:
+ schemafile = '@datadir@/sssd/sssd.api.conf'
+ if not schemaplugindir:
+ schemaplugindir = '@datadir@/sssd/sssd.api.d'
+
+ try:
+ # Read the primary config file
+ with open(schemafile, 'r') as fd:
+ self.readfp(fd)
+ # Read in the provider files
+ for file in filter(lambda f: re.search(r'^sssd-.*\.conf$', f),
+ os.listdir(schemaplugindir)):
+ with open(schemaplugindir+ "/" + file) as fd:
+ self.readfp(fd)
+ except IOError:
+ raise
+ except SyntaxError: # can be raised with readfp
+ raise ParsingError
+
+ # Set up lookup table for types
+ self.type_lookup = {
+ 'bool' : bool,
+ 'int' : int,
+ 'long' : long if sys.version_info[0] == 2 else int,
+ 'float': float,
+ 'str' : str,
+ 'list' : list,
+ 'None' : None
+ }
+
+ # Lookup table for acceptable boolean values
+ self.bool_lookup = {
+ 'false' : False,
+ 'true' : True,
+ }
+
+ def get_options(self, section):
+ if not self.has_section(section):
+ raise NoSectionError
+ options = self.options(section)
+
+ # Indexes
+ PRIMARY_TYPE = 0
+ SUBTYPE = 1
+ MANDATORY = 2
+ DEFAULT = 3
+
+ # Parse values
+ parsed_options = {}
+ for option in self.strip_comments_empty(options):
+ unparsed_option = option['value']
+ split_option = striplist(unparsed_option.split(','))
+ optionlen = len(split_option)
+
+ primarytype = self.type_lookup[split_option[PRIMARY_TYPE]]
+ subtype = self.type_lookup[split_option[SUBTYPE]]
+ mandatory = self.bool_lookup[split_option[MANDATORY]]
+
+ if option['name'] in SSSDOptions.option_strings:
+ desc = SSSDOptions.option_strings[option['name']]
+ else:
+ desc = None
+
+ if optionlen == 3:
+ # This option has no defaults
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ None)
+ elif optionlen == 4:
+ if type(split_option[DEFAULT]) == primarytype:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ split_option[DEFAULT])
+ elif primarytype == list:
+ if (type(split_option[DEFAULT]) == subtype):
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ [split_option[DEFAULT]])
+ else:
+ try:
+ if subtype == bool and \
+ type(split_option[DEFAULT]) == str:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ [self.bool_lookup[split_option[DEFAULT].lower()]])
+ else:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ [subtype(split_option[DEFAULT])])
+ except ValueError:
+ raise ParsingError
+ else:
+ try:
+ if primarytype == bool and \
+ type(split_option[DEFAULT]) == str:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ self.bool_lookup[split_option[DEFAULT].lower()])
+ else:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ primarytype(split_option[DEFAULT]))
+ except ValueError:
+ raise ParsingError
+
+ elif optionlen > 4:
+ if (primarytype != list):
+ raise ParsingError
+ fixed_options = []
+ for x in split_option[DEFAULT:]:
+ if type(x) != subtype:
+ try:
+ if (subtype == bool and type(x) == str):
+ newvalue = self.bool_lookup[x.lower()]
+ else:
+ newvalue = subtype(x)
+ fixed_options.extend([newvalue])
+ except ValueError:
+ raise ParsingError
+ else:
+ fixed_options.extend([x])
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ fixed_options)
+ else:
+ # Bad config file
+ raise ParsingError
+
+ return parsed_options
+
+ def get_option(self, section, option):
+ if not self.has_section(section):
+ raise NoSectionError(section)
+ if not self.has_option(section, option):
+ raise NoOptionError("Section [%s] has no option [%s]" %
+ (section, option))
+
+ return self.get_options(section)[option]
+
+ def get_defaults(self, section):
+ if not self.has_section(section):
+ raise NoSectionError(section)
+
+ schema_options = self.get_options(section)
+ defaults = dict([(x,schema_options[x][4])
+ for x in schema_options.keys()
+ if schema_options[x][4] != None])
+
+ return defaults
+
+ def get_services(self):
+ service_list = [x['name'] for x in self.sections()
+ if x['name'] != 'service' and
+ not x['name'].startswith('domain') and
+ not x['name'].startswith('provider')]
+ return service_list
+
+ def get_providers(self):
+ providers = {}
+ for section in self.sections():
+ splitsection = section['name'].split('/')
+ if (splitsection[0] == 'provider'):
+ if(len(splitsection) == 3):
+ if splitsection[1] not in providers:
+ providers[splitsection[1]] = []
+ providers[splitsection[1]].extend([splitsection[2]])
+ for key in providers.keys():
+ providers[key] = tuple(providers[key])
+ return providers
+
+class SSSDConfigObject(object):
+ def __init__(self):
+ self.name = None
+ self.options = {}
+
+ def get_name(self):
+ """
+ Return the name of the object
+
+ === Returns ===
+ The domain name
+
+ === Errors ===
+ No errors
+ """
+ return self.name
+
+ def get_option(self, optionname):
+ """
+ Return the value of an service option
+
+ optionname:
+ The option to get.
+
+ === Returns ===
+ The value for the requested option.
+
+ === Errors ===
+ NoOptionError:
+ The specified option was not listed in the service
+ """
+ if optionname in self.options.keys():
+ return self.options[optionname]
+ raise NoOptionError(optionname)
+
+ def get_all_options(self):
+ """
+ Return a dictionary of name/value pairs for this object
+
+ === Returns ===
+ A dictionary of name/value pairs currently in use for this object
+
+ === Errors ===
+ No errors
+ """
+ return self.options
+
+ def remove_option(self, optionname):
+ """
+ Remove an option from the object. If the option does not exist, it is ignored.
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ No errors
+ """
+ if optionname in self.options:
+ del self.options[optionname]
+
+class SSSDService(SSSDConfigObject):
+ '''
+ Object to manipulate SSSD service options
+ '''
+
+ def __init__(self, servicename, apischema):
+ """
+ Create a new SSSDService, setting its defaults to those found in the
+ schema. This constructor should not be used directly. Use
+ SSSDConfig.new_service() instead.
+
+ name:
+ The service name
+ apischema:
+ An SSSDConfigSchema? object created by SSSDConfig.__init__()
+
+ === Returns ===
+ The newly-created SSSDService object.
+
+ === Errors ===
+ TypeError:
+ The API schema passed in was unusable or the name was not a string.
+ ServiceNotRecognizedError:
+ The service was not listed in the schema
+ """
+ SSSDConfigObject.__init__(self)
+
+ if not isinstance(apischema, SSSDConfigSchema) or type(servicename) != str:
+ raise TypeError
+
+ if not apischema.has_section(servicename):
+ raise ServiceNotRecognizedError(servicename)
+
+ self.name = servicename
+ self.schema = apischema
+
+ # Set up the service object with any known defaults
+ self.options = {}
+
+ # Include a list of hidden options
+ self.hidden_options = []
+
+ # Set up default options for all services
+ self.options.update(self.schema.get_defaults('service'))
+
+ # Set up default options for this service
+ self.options.update(self.schema.get_defaults(self.name))
+
+ def list_options_with_mandatory(self):
+ """
+ List options for the service, including the mandatory flag.
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), whether it is mandatory, the
+ translated option description, and the default value (or 'None') as
+ the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, False, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = {}
+
+ # Get the list of available options for all services
+ schema_options = self.schema.get_options('service')
+ options.update(schema_options)
+
+ schema_options = self.schema.get_options(self.name)
+ options.update(schema_options)
+
+ return options
+
+ def list_options(self):
+ """
+ List all options that apply to this service
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'services' :
+ (list, str, u'SSSD Services to start', ['nss', 'pam']) }
+
+ === Errors ===
+ No Errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def list_mandatory_options(self):
+ """
+ List all mandatory options that apply to this service
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'services' :
+ (list, str, u'SSSD Services to start', ['nss', 'pam']) }
+
+ === Errors ===
+ No Errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ if options[key][2]:
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def set_option(self, optionname, value):
+ """
+ Set a service option to the specified value (or values)
+
+ optionname:
+ The option to change
+ value:
+ The value to set. This may be a single value or a list of values. If
+ it is set to None, it resets the option to its default.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NoOptionError:
+ The specified option is not listed in the schema
+ TypeError:
+ The value specified was not of the expected type
+ """
+ if self.schema.has_option(self.name, optionname):
+ option_schema = self.schema.get_option(self.name, optionname)
+ elif self.schema.has_option('service', optionname):
+ option_schema = self.schema.get_option('service', optionname)
+ elif optionname in self.hidden_options:
+ # Set this option and do not add it to the list of changeable values
+ self.options[optionname] = value
+ return
+ else:
+ raise NoOptionError('Section [%s] has no option [%s]' % (self.name, optionname))
+
+ if value == None:
+ self.remove_option(optionname)
+ return
+
+ raise_error = False
+
+ # If we were expecting a list and didn't get one,
+ # Create a list with a single entry. If it's the
+ # wrong subtype, it will fail below
+ if option_schema[0] == list and type(value) != list:
+ if type(value) == str:
+ value = striplist(value.split(','))
+ else:
+ value = [value]
+
+ if type(value) != option_schema[0]:
+ # If it's possible to convert it, do so
+ try:
+ if option_schema[0] == bool and type(value) == str:
+ value = self.schema.bool_lookup[value.lower()]
+ elif option_schema[0] == int and type(value) == str:
+ # Make sure we handle any reasonable base
+ value = int(value, 0)
+ else:
+ value = option_schema[0](value)
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s for %s, received %s' %
+ (option_schema[0], optionname, type(value)))
+
+ if type(value) == list:
+ # Iterate through the list an ensure that all members
+ # are of the appropriate subtype
+ try:
+ newvalue = []
+ for x in value:
+ if option_schema[1] == bool and \
+ type(x) == str:
+ newvalue.extend([self.schema.bool_lookup[x.lower()]])
+ else:
+ newvalue.extend([option_schema[1](x)])
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s' % option_schema[1])
+
+ value = newvalue
+
+ self.options[optionname] = value
+
+class SSSDDomain(SSSDConfigObject):
+ """
+ Object to manipulate SSSD domain options
+ """
+ def __init__(self, domainname, apischema):
+ """
+ Creates a new, empty SSSDDomain. This domain is inactive by default.
+ This constructor should not be used directly. Use
+ SSSDConfig.new_domain() instead.
+
+ name:
+ The domain name.
+ apischema:
+ An SSSDConfigSchema object created by SSSDConfig.__init__()
+
+ === Returns ===
+ The newly-created SSSDDomain object.
+
+ === Errors ===
+ TypeError:
+ apischema was not an SSSDConfigSchema object or domainname was not
+ a string
+ """
+ SSSDConfigObject.__init__(self)
+
+ if not isinstance(apischema, SSSDConfigSchema) or type(domainname) != str:
+ raise TypeError
+
+ self.name = domainname
+ self.schema = apischema
+ self.active = False
+ self.oldname = None
+ self.providers = []
+
+ # Set up the domain object with any known defaults
+ self.options = {}
+
+ # Set up default options for all domains
+ self.options.update(self.schema.get_defaults('provider'))
+ self.options.update(self.schema.get_defaults('domain'))
+
+ def set_active(self, active):
+ """
+ Enable or disable this domain
+
+ active:
+ Boolean value. If True, this domain will be added to the active
+ domains list when it is saved. If False, it will be removed from the
+ active domains list when it is saved.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ No errors
+ """
+ self.active = bool(active)
+
+ def list_options_with_mandatory(self):
+ """
+ List options for the currently-configured providers, including the
+ mandatory flag
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), whether it is mandatory, the
+ translated option description, and the default value (or 'None') as
+ the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, False, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = {}
+ # Get the list of available options for all domains
+ options.update(self.schema.get_options('provider'))
+
+ options.update(self.schema.get_options('domain'))
+
+ # Candidate for future optimization: will update primary type
+ # for each subtype
+ for (provider, providertype) in self.providers:
+ schema_options = self.schema.get_options('provider/%s'
+ % provider)
+ options.update(schema_options)
+ schema_options = self.schema.get_options('provider/%s/%s'
+ % (provider, providertype))
+ options.update(schema_options)
+ return options
+
+ def list_options(self):
+ """
+ List options available for the currently-configured providers.
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def list_mandatory_options(self):
+ """
+ List mandatory options for the currently-configured providers.
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ if options[key][2]:
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def list_provider_options(self, provider, provider_type=None):
+ """
+ If provider_type is specified, list all options applicable to that
+ target, otherwise list all possible options available for a provider.
+
+ type:
+ Provider backend type. (e.g. local, ldap, krb5, etc.)
+ provider_type:
+ Subtype of the backend type. (e.g. id, auth, access, chpass)
+
+ === Returns ===
+
+ A dictionary of configurable options for the specified provider type.
+ This dictionary is keyed on the option name with a tuple of the
+ variable type, subtype ('None' if the type is not a collection type),
+ the translated option description, and the default value (or 'None')
+ as the value.
+
+ === Errors ===
+
+ NoSuchProviderError:
+ The specified provider is not listed in the schema or plugins
+ NoSuchProviderSubtypeError:
+ The specified provider subtype is not listed in the schema
+ """
+ #TODO section checking
+
+ options = self.schema.get_options('provider/%s' % provider)
+ if(provider_type):
+ options.update(self.schema.get_options('provider/%s/%s' %
+ (provider, provider_type)))
+ else:
+ # Add options from all provider subtypes
+ known_providers = self.list_providers()
+ for provider_type in known_providers[provider]:
+ options.update(self.list_provider_options(provider,
+ provider_type))
+ return options
+
+ def list_providers(self):
+ """
+ Return a dictionary of providers.
+
+ === Returns ===
+ Returns a dictionary of providers, keyed on the primary type, with the
+ value being a tuple of the subtypes it supports.
+
+ Example:
+ { 'ldap' : ('id', 'auth', 'chpass') }
+
+ === Errors ===
+ No Errors
+ """
+ return self.schema.get_providers()
+
+ def set_option(self, option, value):
+ """
+ Set a domain option to the specified value (or values)
+
+ option:
+ The option to change.
+ value:
+ The value to set. This may be a single value or a list of values.
+ If it is set to None, it resets the option to its default.
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ NoOptionError:
+ The specified option is not listed in the schema
+ TypeError:
+ The value specified was not of the expected type
+ """
+ options = self.list_options()
+ if (option not in options.keys()):
+ raise NoOptionError('Section [%s] has no option [%s]' %
+ (self.name, option))
+
+ if value == None:
+ self.remove_option(option)
+ return
+
+ option_schema = options[option]
+ raise_error = False
+
+ # If we were expecting a list and didn't get one,
+ # Create a list with a single entry. If it's the
+ # wrong subtype, it will fail below
+ if option_schema[0] == list and type(value) != list:
+ if type(value) == str:
+ value = striplist(value.split(','))
+ else:
+ value = [value]
+
+ if type(value) != option_schema[0]:
+ # If it's possible to convert it, do so
+ try:
+ if option_schema[0] == bool and \
+ type(value) == str:
+ value = self.schema.bool_lookup[value.lower()]
+ elif option_schema[0] == int and type(value) == str:
+ # Make sure we handle any reasonable base
+ value = int(value, 0)
+ else:
+ value = option_schema[0](value)
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s for %s, received %s' %
+ (option_schema[0], option, type(value)))
+
+ if type(value) == list:
+ # Iterate through the list an ensure that all members
+ # are of the appropriate subtype
+ try:
+ newvalue = []
+ for x in value:
+ if option_schema[1] == bool and \
+ type(x) == str:
+ newvalue.extend([self.schema.bool_lookup[x.lower()]])
+ else:
+ newvalue.extend([option_schema[1](x)])
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s' % option_schema[1])
+ value = newvalue
+
+ # Check whether we're adding a provider entry.
+ is_provider = option.rfind('_provider')
+ if (is_provider > 0):
+ provider = option[:is_provider]
+ try:
+ self.add_provider(value, provider)
+ except NoSuchProviderError:
+ raise NoOptionError
+ else:
+ self.options[option] = value
+
+ def set_name(self, newname):
+ """
+ Change the name of the domain
+
+ newname:
+ New name for this domain
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ TypeError:
+ newname was not a string
+ """
+
+ if type(newname) != str:
+ raise TypeError
+
+ if not self.oldname:
+ # Only set the oldname once
+ self.oldname = self.name
+ self.name = newname
+
+ def add_provider(self, provider, provider_type):
+ """
+ Add a new provider type to the domain
+
+ type:
+ Provider backend type. (e.g. local, ldap, krb5, etc.)
+ subtype:
+ Subtype of the backend type. (e.g. id, auth, chpass)
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ ProviderSubtypeInUse:
+ Another backend is already providing this subtype
+ NoSuchProviderError:
+ The specified provider is not listed in the schema or plugins
+ NoSuchProviderSubtypeError:
+ The specified provider subtype is not listed in the schema
+ """
+ # Check that provider and provider_type are valid
+ configured_providers = self.list_providers()
+ if provider in configured_providers.keys():
+ if provider_type not in configured_providers[provider]:
+ raise NoSuchProviderSubtypeError(provider_type)
+ else:
+ raise NoSuchProviderError
+
+ # Don't add a provider twice
+ with_this_type = [x for x in self.providers if x[1] == provider_type]
+ if len(with_this_type) > 1:
+ # This should never happen!
+ raise ProviderSubtypeInUse
+ if len(with_this_type) == 1:
+ if with_this_type[0][0] != provider:
+ raise ProviderSubtypeInUse(with_this_type[0][0])
+ else:
+ self.providers.extend([(provider, provider_type)])
+
+ option_name = '%s_provider' % provider_type
+ self.options[option_name] = provider
+
+ # Add defaults for this provider
+ self.options.update(self.schema.get_defaults('provider/%s' %
+ provider))
+ self.options.update(self.schema.get_defaults('provider/%s/%s' %
+ (provider,
+ provider_type)))
+
+ def remove_provider(self, provider_type):
+ """
+ Remove a provider from the domain. If the provider is not present, it
+ is ignored.
+
+ provider_type:
+ Subtype of the backend type. (e.g. id, auth, chpass)
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ No Errors
+ """
+
+ provider = None
+ for (provider, ptype) in self.providers:
+ if ptype == provider_type:
+ break
+ provider = None
+
+ # Check whether the provider_type was found
+ if not provider:
+ return
+
+ # Remove any unused options when removing the provider.
+ options = self.list_provider_options(provider, provider_type)
+
+ # Trim any options that are used by other providers,
+ # if that provider is in use
+ for (prov, ptype) in self.providers:
+ # Ignore the one being removed
+ if (prov, ptype) == (provider, provider_type):
+ continue
+
+ provider_options = self.list_provider_options(prov, ptype)
+ overlap = options_overlap(options.keys(), provider_options.keys())
+ for opt in overlap:
+ del options[opt]
+
+ # We should now have a list of options used only by this
+ # provider. So we remove them.
+ for option in options:
+ if option in self.options:
+ del self.options[option]
+
+ # Remove this provider from the option list
+ option = '%s_provider' % provider_type
+ if option in self.options:
+ del self.options[option]
+
+ self.providers.remove((provider, provider_type))
+
+class SSSDConfig(SSSDChangeConf):
+ """
+ class SSSDConfig
+ Primary class for operating on SSSD configurations
+ """
+ def __init__(self, schemafile=None, schemaplugindir=None):
+ """
+ Initialize the SSSD config parser/editor. This constructor does not
+ open or create a config file. If the schemafile and schemaplugindir
+ are not passed, it will use the system defaults.
+
+ schemafile:
+ The path to the API schema config file. Usually
+ @datadir@/sssd/sssd.api.conf
+ schemaplugindir:
+ The path the directory containing the provider schema config files.
+ Usually @datadir@/sssd/sssd.api.d
+
+ === Returns ===
+ The newly-created SSSDConfig object.
+
+ === Errors ===
+ IOError:
+ Exception raised when the schema file could not be opened for
+ reading.
+ ParsingError:
+ The main schema file or one of those in the plugin directory could
+ not be parsed.
+ """
+ SSSDChangeConf.__init__(self)
+ self.schema = SSSDConfigSchema(schemafile, schemaplugindir)
+ self.configfile = None
+ self.initialized = False
+ self.API_VERSION = 2
+
+ def import_config(self,configfile=None):
+ """
+ Read in a config file, populating all of the service and domain
+ objects with the read values.
+
+ configfile:
+ The path to the SSSD config file. If not specified, use the system
+ default, usually @sysconfdir@/sssd.conf
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ IOError:
+ Exception raised when the file could not be opened for reading
+ ParsingError:
+ Exception raised when errors occur attempting to parse a file.
+ AlreadyInitializedError:
+ This SSSDConfig object was already initialized by a call to
+ import_config() or new_config()
+ """
+ if self.initialized:
+ raise AlreadyInitializedError
+
+ if not configfile:
+ #TODO: get this from a global setting
+ configfile = '@sysconfdir@/sssd/sssd.conf'
+ # open will raise an IOError if it fails
+ with open(configfile, 'r') as fd:
+ try:
+ self.readfp(fd)
+ except Exception:
+ raise ParsingError
+
+ self.configfile = configfile
+ self.initialized = True
+
+ try:
+ if int(self.get('sssd', 'config_file_version')) != self.API_VERSION:
+ raise ParsingError("Wrong config_file_version")
+ except TypeError:
+ # This happens when config_file_version is missing. We
+ # can assume it is the default version and continue.
+ pass
+
+ def new_config(self):
+ """
+ Initialize the SSSDConfig object with the defaults from the schema.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ AlreadyInitializedError:
+ This SSSDConfig object was already initialized by a call to
+ import_config() or new_config()
+ """
+ if self.initialized:
+ raise AlreadyInitializedError
+
+ self.initialized = True
+
+ #Initialize all services
+ for servicename in self.schema.get_services():
+ service = self.new_service(servicename)
+
+ def write(self, outputfile=None):
+ """
+ Write out the configuration to a file.
+
+ outputfile:
+ The path to write the new config file. If it is not specified, it
+ will use the path specified by the import() call.
+ === Returns ===
+ No return value
+
+ === Errors ===
+ IOError:
+ Exception raised when the file could not be opened for writing
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoOutputFileError:
+ No outputfile was specified and this SSSDConfig object was not
+ initialized by import()
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if outputfile == None:
+ if(self.configfile == None):
+ raise NoOutputFileError
+
+ outputfile = self.configfile
+
+ # open() will raise IOError if it fails
+ old_umask = os.umask(0o177)
+ with open(outputfile, "w") as of:
+ output = self.dump(self.opts)
+ of.write(output)
+ os.umask(old_umask)
+
+ def list_active_services(self):
+ """
+ Return a list of all active services.
+
+ === Returns ===
+ The list of active services.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'services')):
+ active_services = striplist(self.get('sssd', 'services').split(','))
+ service_dict = dict.fromkeys(active_services)
+ if '' in service_dict:
+ del service_dict['']
+
+ # Remove any entries in this list that don't
+ # correspond to an active service, for integrity
+ configured_services = self.list_services()
+ for srv in list(service_dict):
+ if srv not in configured_services:
+ del service_dict[srv]
+
+ active_services = list(service_dict)
+ else:
+ active_services = []
+
+ return active_services
+
+ def list_inactive_services(self):
+ """
+ Return a list of all disabled services.
+
+ === Returns ===
+ The list of inactive services.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'services')):
+ active_services = striplist(self.get('sssd', 'services').split(','))
+ else:
+ active_services = []
+
+ services = [x for x in self.list_services()
+ if x not in active_services]
+ return services
+
+ def list_services(self):
+ """
+ Retrieve a list of known services.
+
+ === Returns ===
+ The list of known services.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ service_list = [x['name'] for x in self.sections()
+ if not x['name'].startswith('domain') ]
+ return service_list
+
+ def get_service(self, name):
+ """
+ Get an SSSDService object to edit a service.
+
+ name:
+ The name of the service to return.
+
+ === Returns ===
+ An SSSDService instance containing the current state of a service in
+ the SSSDConfig
+
+ === Errors ===
+ NoServiceError:
+ There is no such service with the specified name in the SSSDConfig.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not self.has_section(name):
+ raise NoServiceError
+
+ service = SSSDService(name, self.schema)
+ for opt in self.strip_comments_empty(self.options(name)):
+ try:
+ service.set_option(opt['name'], opt['value'])
+ except NoOptionError:
+ # If we come across an option that we don't recognize,
+ # we should just ignore it and continue
+ pass
+
+ return service
+
+ def new_service(self, name):
+ """
+ Create a new service from the defaults and return the SSSDService
+ object for it. This function will also add this service to the list of
+ active services in the [SSSD] section.
+
+ name:
+ The name of the service to create and return.
+
+ === Returns ===
+ The newly-created SSSDService object
+
+ === Errors ===
+ ServiceNotRecognizedError:
+ There is no such service in the schema.
+ ServiceAlreadyExistsError:
+ The service being created already exists in the SSSDConfig object.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if (self.has_section(name)):
+ raise ServiceAlreadyExists(name)
+
+ service = SSSDService(name, self.schema)
+ self.save_service(service)
+ return service
+
+ def activate_service(self, name):
+ """
+ Activate a service
+
+ name:
+ The name of the service to activate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoServiceError:
+ There is no such service with the specified name in the SSSDConfig.
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_services():
+ raise NoServiceError
+
+ item = self.get_option_index('sssd', 'services')[1]
+ if not item:
+ self.set('sssd','services', name)
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to add a new value
+ service_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if '' in service_dict:
+ del service_dict['']
+
+ # Add a new key for the service being activated
+ service_dict[name] = None
+
+ # Write out the joined keys
+ self.set('sssd','services', ", ".join(service_dict.keys()))
+
+ def deactivate_service(self, name):
+ """
+ Deactivate a service
+
+ name:
+ The name of the service to deactivate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoServiceError:
+ There is no such service with the specified name in the SSSDConfig.
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_services():
+ raise NoServiceError
+ item = self.get_option_index('sssd', 'services')[1]
+ if not item:
+ self.set('sssd','services', '')
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to remove the one unwanted value.
+ service_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if '' in service_dict:
+ del service_dict['']
+
+ # Remove the unwanted service from the lest
+ if name in service_dict:
+ del service_dict[name]
+
+ # Write out the joined keys
+ self.set('sssd','services', ", ".join(service_dict.keys()))
+
+ def delete_service(self, name):
+ """
+ Remove a service from the SSSDConfig object. This function will also
+ remove this service from the list of active services in the [SSSD]
+ section. Has no effect if the service does not exist.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ self.delete_option('section', name)
+
+ def save_service(self, service):
+ """
+ Save the changes made to the service object back to the SSSDConfig
+ object.
+
+ service_object:
+ The SSSDService object to save to the configuration.
+
+ === Returns ===
+ No return value
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ TypeError:
+ service_object was not of the type SSSDService
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not isinstance(service, SSSDService):
+ raise TypeError
+
+ name = service.get_name()
+ # Ensure that the existing section is removed
+ # This way we ensure that we are getting a
+ # complete copy of the service.
+ # delete_option() is a noop if the section
+ # does not exist.
+ index = self.delete_option('section', name)
+
+ addkw = []
+ for option,value in service.get_all_options().items():
+ if (type(value) == list):
+ value = ', '.join(value)
+ if option == "debug_level":
+ value = self._get_debug_level_val(value)
+ addkw.append( { 'type' : 'option',
+ 'name' : option,
+ 'value' : str(value) } )
+
+ self.add_section(name, addkw, index)
+
+ def list_active_domains(self):
+ """
+ Return a list of all active domains.
+
+ === Returns ===
+ The list of configured, active domains.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'domains')):
+ sssd_domains = striplist(self.get('sssd', 'domains').split(','))
+ domain_dict = dict.fromkeys(sssd_domains)
+ if '' in domain_dict:
+ del domain_dict['']
+ sssd_domains = list(domain_dict)
+ else:
+ sssd_domains = []
+
+ domains = self.list_domains()
+ for dom in self.list_domains():
+ # Remove explicitly enabled from the list
+ if self.has_option('domain/%s' % dom, 'enabled'):
+ if self.get('domain/%s' % dom, 'enabled') == 'false':
+ domains.remove(dom)
+ if dom in sssd_domains:
+ sssd_domains.remove(dom)
+ else:
+ if dom not in sssd_domains:
+ domains.remove(dom)
+
+ return domains
+
+ def list_inactive_domains(self):
+ """
+ Return a list of all configured, but disabled domains.
+
+ === Returns ===
+ The list of configured, inactive domains.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'domains')):
+ sssd_domains = striplist(self.get('sssd', 'domains').split(','))
+ domain_dict = dict.fromkeys(sssd_domains)
+ if '' in domain_dict:
+ del domain_dict['']
+ sssd_domains = list(domain_dict)
+ else:
+ sssd_domains = []
+
+ domains = self.list_domains()
+ for dom in self.list_domains():
+ # Remove explicitly enabled from the list
+ if self.has_option('domain/%s' % dom, 'enabled'):
+ if self.get('domain/%s' % dom, 'enabled') == 'true':
+ domains.remove(dom)
+ else:
+ if dom in sssd_domains:
+ domains.remove(dom)
+
+ return domains
+
+ def list_domains(self):
+ """
+ Return a list of all configured domains, including inactive domains.
+
+ === Returns ===
+ The list of configured domains, both active and inactive.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ domains = [x['name'][7:] for x in self.sections() if x['name'].startswith('domain/')]
+ return domains
+
+ def get_domain(self, name):
+ """
+ Get an SSSDDomain object to edit a domain.
+
+ name:
+ The name of the domain to return.
+
+ === Returns ===
+ An SSSDDomain instance containing the current state of a domain in the
+ SSSDConfig
+
+ === Errors ===
+ NoDomainError:
+ There is no such domain with the specified name in the SSSDConfig.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not self.has_section('domain/%s' % name):
+ raise NoDomainError(name)
+
+ domain = SSSDDomain(name, self.schema)
+
+ # Read in the providers first or we may have type
+ # errors trying to read in their options
+ providers = [ (x['name'],x['value']) for x in self.strip_comments_empty(self.options('domain/%s' % name))
+ if x['name'].rfind('_provider') > 0]
+
+ # Handle the default value for providers if those weren't set in the configuration file
+ default_providers = {
+ 'id_provider': None,
+ 'sudo_provider': 'id_provider',
+ 'auth_provider': 'id_provider',
+ 'chpass_provider': 'auth_provider',
+ 'autofs_provider': 'id_provider',
+ 'selinux_provider': 'id_provider',
+ 'subdomains_provider': 'id_provider',
+ 'session_provider': 'id_provider',
+ 'hostid_provider': 'id_provider',
+ 'resolver_provider': 'id_provider',
+ }
+
+ configured_providers = domain.list_providers()
+ providers_list = [x[0] for x in providers]
+
+ if 'access_provider' not in providers_list:
+ providers.append(('access_provider', 'permit'))
+
+ for provider, default_provider in default_providers.items():
+ if provider in providers_list:
+ continue
+
+ if default_provider in providers_list:
+ default_provider_value = providers[providers_list.index(default_provider)]
+ if default_provider_value[1] in configured_providers.keys():
+ if provider[:provider.rfind('_provider')] in configured_providers[default_provider_value[1]]:
+ providers.append((provider, default_provider_value[1]))
+
+ for (option, value) in providers:
+ try:
+ domain.set_option(option, value)
+ except NoOptionError:
+ # If we come across an option that we don't recognize,
+ # we should just ignore it and continue
+ pass
+
+ # Read in all the options from the configuration
+ for opt in self.strip_comments_empty(self.options('domain/%s' % name)):
+ if (opt['name'], opt['value']) not in providers:
+ try:
+ domain.set_option(opt['name'], opt['value'])
+ except NoOptionError:
+ # If we come across an option that we don't recognize,
+ # we should just ignore it and continue
+ pass
+
+ # Determine if this domain is currently active
+ domain.active = self.is_domain_active(name)
+
+ return domain
+
+ def new_domain(self, name):
+ """
+ Create a new, empty domain and return the SSSDDomain object for it.
+
+ name:
+ The name of the domain to create and return.
+
+ === Returns ===
+ The newly-created SSSDDomain object
+
+ === Errors ===
+ DomainAlreadyExistsError:
+ The service being created already exists in the SSSDConfig object.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if self.has_section('domain/%s' % name):
+ raise DomainAlreadyExistsError
+
+ domain = SSSDDomain(name, self.schema)
+ self.save_domain(domain)
+ return domain
+
+ def is_domain_active(self, name):
+ """
+ Is a particular domain set active
+
+ name:
+ The name of the configured domain to check
+
+ === Returns ===
+ True if the domain is active, False if it is inactive
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoDomainError:
+ No domain by this name is configured
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_domains():
+ raise NoDomainError
+
+ return name in self.list_active_domains()
+
+ def activate_domain(self, name):
+ """
+ Activate a configured domain
+
+ name:
+ The name of the configured domain to activate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoDomainError:
+ No domain by this name is configured
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_domains():
+ raise NoDomainError
+
+ item = self.get_option_index('sssd', 'domains')[1]
+ if not item:
+ self.set('sssd','domains', name)
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to add a new value
+ domain_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if '' in domain_dict:
+ del domain_dict['']
+
+ # Add a new key for the domain being activated
+ domain_dict[name] = None
+
+ # Write out the joined keys
+ self.set('sssd','domains', ", ".join(domain_dict.keys()))
+
+ def deactivate_domain(self, name):
+ """
+ Deactivate a configured domain
+
+ name:
+ The name of the configured domain to deactivate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoDomainError:
+ No domain by this name is configured
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_domains():
+ raise NoDomainError
+ item = self.get_option_index('sssd', 'domains')[1]
+ if not item:
+ self.set('sssd','domains', '')
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to remove the one unwanted value.
+ domain_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if '' in domain_dict:
+ del domain_dict['']
+
+ # Remove the unwanted domain from the lest
+ if name in domain_dict:
+ del domain_dict[name]
+
+ # Write out the joined keys
+ self.set('sssd','domains', ", ".join(domain_dict.keys()))
+
+ def delete_domain(self, name):
+ """
+ Remove a domain from the SSSDConfig object. This function will also
+ remove this domain from the list of active domains in the [SSSD]
+ section, if it is there.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ # Remove the domain from the active domains list if applicable
+ self.deactivate_domain(name)
+ self.delete_option('section', 'domain/%s' % name)
+
+ def save_domain(self, domain):
+ """
+ Save the changes made to the domain object back to the SSSDConfig
+ object. If this domain is marked active, ensure it is present in the
+ active domain list in the [SSSD] section
+
+ domain_object:
+ The SSSDDomain object to save to the configuration.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ TypeError:
+ domain_object was not of type SSSDDomain
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not isinstance(domain, SSSDDomain):
+ raise TypeError
+
+ name = domain.get_name()
+
+ oldindex = None
+ if domain.oldname and domain.oldname != name:
+ # We are renaming this domain
+ # Remove the old section
+
+ self.deactivate_domain(domain.oldname)
+ oldindex = self.delete_option('section', 'domain/%s' %
+ domain.oldname)
+
+ # Reset the oldname, in case we're not done with
+ # this domain object.
+ domain.oldname = None;
+
+ sectionname = 'domain/%s' % name
+ (no, section_subtree) = self.findOpts(self.opts, 'section', sectionname)
+
+ if name not in self.list_domains():
+ self.add_section(sectionname, []);
+
+ section_options = self.options(sectionname)[:]
+ for option in section_options:
+ if option['type'] == 'option':
+ if option['name'] not in domain.get_all_options():
+ self.delete_option_subtree(section_subtree['value'], 'option', option['name'], True)
+
+ for option,value in domain.get_all_options().items():
+ if (type(value) == list):
+ value = ', '.join(value)
+ if option == "debug_level":
+ value = self._get_debug_level_val(value)
+ self.set(sectionname, option, str(value))
+
+ if domain.active:
+ self.activate_domain(name)
+ else:
+ self.deactivate_domain(name)
diff --git a/src/config/SSSDConfig/ipachangeconf.py b/src/config/SSSDConfig/ipachangeconf.py
new file mode 100644
index 0000000..a2b3b00
--- /dev/null
+++ b/src/config/SSSDConfig/ipachangeconf.py
@@ -0,0 +1,584 @@
+"""
+ipachangeconf - configuration file manipulation classes and functions
+partially based on authconfig code
+Copyright (c) 1999-2007 Red Hat, Inc.
+Author: Simo Sorce <ssorce@redhat.com>
+
+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 3 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, see <http://www.gnu.org/licenses/>.
+"""
+
+import fcntl
+import os
+import shutil
+import re
+
+
+def openLocked(filename, perms, create=True):
+ fd = -1
+
+ flags = os.O_RDWR
+ if create:
+ flags = flags | os.O_CREAT
+
+ try:
+ fd = os.open(filename, flags, perms)
+ fcntl.lockf(fd, fcntl.LOCK_EX)
+ except OSError as err:
+ errno, strerr = err.args
+ if fd != -1:
+ try:
+ os.close(fd)
+ except OSError:
+ pass
+ raise IOError(errno, strerr)
+ return os.fdopen(fd, "r+")
+
+
+class IPAChangeConf(object):
+
+ def __init__(self, name):
+ self.progname = name
+ self.indent = ("", "", "")
+ self.assign = (" = ", "=")
+ self.dassign = self.assign[0]
+ self.comment = ("#",)
+ self.dcomment = self.comment[0]
+ self.eol = ("\n",)
+ self.deol = self.eol[0]
+ self.sectnamdel = ("[", "]")
+ self.subsectdel = ("{", "}")
+ self.backup_suffix = ".ipabkp"
+
+ def setProgName(self, name):
+ self.progname = name
+
+ def setIndent(self, indent):
+ if type(indent) is tuple:
+ self.indent = indent
+ elif type(indent) is str:
+ self.indent = (indent, )
+ else:
+ raise ValueError('Indent must be a list of strings')
+
+ def setOptionAssignment(self, assign):
+ if type(assign) is tuple:
+ self.assign = assign
+ else:
+ self.assign = (assign, )
+ self.dassign = self.assign[0]
+
+ def setCommentPrefix(self, comment):
+ if type(comment) is tuple:
+ self.comment = comment
+ else:
+ self.comment = (comment, )
+ self.dcomment = self.comment[0]
+
+ def setEndLine(self, eol):
+ if type(eol) is tuple:
+ self.eol = eol
+ else:
+ self.eol = (eol, )
+ self.deol = self.eol[0]
+
+ def setSectionNameDelimiters(self, delims):
+ self.sectnamdel = delims
+
+ def setSubSectionDelimiters(self, delims):
+ self.subsectdel = delims
+
+ def matchComment(self, line):
+ for v in self.comment:
+ if line.lstrip().startswith(v):
+ return line.lstrip()[len(v):]
+ return False
+
+ def matchEmpty(self, line):
+ if line.strip() == "":
+ return True
+ return False
+
+ def matchSection(self, line):
+ cl = "".join(line.strip().split())
+ if len(self.sectnamdel) != 2:
+ return False
+ if not cl.startswith(self.sectnamdel[0]):
+ return False
+ if not cl.endswith(self.sectnamdel[1]):
+ return False
+ return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
+
+ def matchSubSection(self, line):
+ if self.matchComment(line):
+ return False
+
+ parts = line.split(self.dassign, 1)
+ if len(parts) < 2:
+ return False
+
+ if parts[1].strip() == self.subsectdel[0]:
+ return parts[0].strip()
+
+ return False
+
+ def matchSubSectionEnd(self, line):
+ if self.matchComment(line):
+ return False
+
+ if line.strip() == self.subsectdel[1]:
+ return True
+
+ return False
+
+ def getSectionLine(self, section):
+ if len(self.sectnamdel) != 2:
+ return section
+ return self.sectnamdel[0] + section + self.sectnamdel[1] + self.deol
+
+ @staticmethod
+ def _get_debug_level_val(value):
+ if value > 16:
+ value = hex(value)
+
+ return value
+
+ def dump(self, options, level=0):
+ output = ""
+ if level >= len(self.indent):
+ level = len(self.indent) - 1
+
+ for o in options:
+ if o['type'] == "section":
+ output += self.sectnamdel[0] + o['name'] + self.sectnamdel[1] + self.deol
+ output += self.dump(o['value'], level + 1)
+ continue
+ if o['type'] == "subsection":
+ output += self.indent[level] + o['name'] + self.dassign + self.subsectdel[0] + self.deol
+ output += self.dump(o['value'], level + 1)
+ output += self.indent[level] + self.subsectdel[1] + self.deol
+ continue
+ if o['type'] == "option":
+ output += self.indent[level] + o['name'] + self.dassign + o['value'] + self.deol
+ continue
+ if o['type'] == "comment":
+ output += self.dcomment + o['value'] + self.deol
+ continue
+ if o['type'] == "empty":
+ output += self.deol
+ continue
+ raise SyntaxError('Unknown type: [' + o['type'] + ']')
+
+ return output
+
+ def parseLine(self, line):
+
+ if self.matchEmpty(line):
+ return {'name': 'empty', 'type': 'empty'}
+
+ value = self.matchComment(line)
+ if value:
+ return {'name': 'comment', 'type': 'comment', 'value': value.rstrip()}
+
+ parts = line.split(self.dassign, 1)
+ if len(parts) < 2:
+ raise SyntaxError('Syntax Error: Unknown line format')
+
+ return {'name': parts[0].strip(), 'type': 'option', 'value': parts[1].rstrip()}
+
+ def findOpts(self, opts, type, name, exclude_sections=False):
+
+ num = 0
+ for o in opts:
+ if o['type'] == type and o['name'] == name:
+ return (num, o)
+ if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"):
+ return (num, None)
+ num += 1
+ return (num, None)
+
+ def commentOpts(self, inopts, level=0):
+
+ opts = []
+
+ if level >= len(self.indent):
+ level = len(self.indent) - 1
+
+ for o in inopts:
+ if o['type'] == 'section':
+ no = self.commentOpts(o['value'], level + 1)
+ val = self.dcomment + self.sectnamdel[0] + o['name'] + self.sectnamdel[1]
+ opts.append({'name': 'comment', 'type': 'comment', 'value': val})
+ for n in no:
+ opts.append(n)
+ continue
+ if o['type'] == 'subsection':
+ no = self.commentOpts(o['value'], level + 1)
+ val = self.indent[level] + o['name'] + self.dassign + self.subsectdel[0]
+ opts.append({'name': 'comment', 'type': 'comment', 'value': val})
+ for n in no:
+ opts.append(n)
+ val = self.indent[level] + self.subsectdel[1]
+ opts.append({'name': 'comment', 'type': 'comment', 'value': val})
+ continue
+ if o['type'] == 'option':
+ val = self.indent[level] + o['name'] + self.dassign + o['value']
+ opts.append({'name': 'comment', 'type': 'comment', 'value': val})
+ continue
+ if o['type'] == 'comment':
+ opts.append(o)
+ continue
+ if o['type'] == 'empty':
+ opts.append({'name': 'comment', 'type': 'comment', 'value': ''})
+ continue
+ raise SyntaxError('Unknown type: [' + o['type'] + ']')
+
+ return opts
+
+ def mergeOld(self, oldopts, newopts):
+
+ opts = []
+
+ for o in oldopts:
+ if o['type'] == "section" or o['type'] == "subsection":
+ (num, no) = self.findOpts(newopts, o['type'], o['name'])
+ if not no:
+ opts.append(o)
+ continue
+ if no['action'] == "set":
+ mo = self.mergeOld(o['value'], no['value'])
+ opts.append({'name': o['name'], 'type': o['type'], 'value': mo})
+ continue
+ if no['action'] == "comment":
+ co = self.commentOpts(o['value'])
+ for c in co:
+ opts.append(c)
+ continue
+ if no['action'] == "remove":
+ continue
+ raise SyntaxError('Unknown action: [' + no['action'] + ']')
+
+ if o['type'] == "comment" or o['type'] == "empty":
+ opts.append(o)
+ continue
+
+ if o['type'] == "option":
+ (num, no) = self.findOpts(newopts, 'option', o['name'], True)
+ if not no:
+ opts.append(o)
+ continue
+ if no['action'] == 'comment' or no['action'] == 'remove':
+ if no['value'] is not None and o['value'] != no['value']:
+ opts.append(o)
+ continue
+ if no['action'] == 'comment':
+ opts.append({'name': 'comment', 'type': 'comment',
+ 'value': self.dcomment + o['name'] + self.dassign + o['value']})
+ continue
+ if no['action'] == 'set':
+ opts.append(no)
+ continue
+ raise SyntaxError('Unknown action: [' + o['action'] + ']')
+
+ raise SyntaxError('Unknown type: [' + o['type'] + ']')
+
+ return opts
+
+ def mergeNew(self, opts, newopts):
+
+ cline = 0
+
+ for no in newopts:
+
+ if no['type'] == "section" or no['type'] == "subsection":
+ (num, o) = self.findOpts(opts, no['type'], no['name'])
+ if not o:
+ if no['action'] == 'set':
+ opts.append(no)
+ continue
+ if no['action'] == "set":
+ self.mergeNew(o['value'], no['value'])
+ continue
+ cline = num + 1
+ continue
+
+ if no['type'] == "option":
+ (num, o) = self.findOpts(opts, no['type'], no['name'], True)
+ if not o:
+ if no['action'] == 'set':
+ opts.append(no)
+ continue
+ cline = num + 1
+ continue
+
+ if no['type'] == "comment" or no['type'] == "empty":
+ opts.insert(cline, no)
+ cline += 1
+ continue
+
+ raise SyntaxError('Unknown type: [' + no['type'] + ']')
+
+ def merge(self, oldopts, newopts):
+ """
+ Use a two pass strategy
+ First we create a new opts tree from oldopts removing/commenting
+ the options as indicated by the contents of newopts
+ Second we fill in the new opts tree with options as indicated
+ in the newopts tree (this is because entire (sub)sections may
+ exist in the newopts that do not exist in oldopts)
+ """
+ opts = self.mergeOld(oldopts, newopts)
+ self.mergeNew(opts, newopts)
+ return opts
+
+ # TODO: Make parse() recursive?
+ def parse(self, f):
+
+ opts = []
+ sectopts = []
+ section = None
+ subsectopts = []
+ subsection = None
+ curopts = opts
+ fatheropts = opts
+
+ # Read in the old file.
+ for line in f:
+
+ # It's a section start.
+ value = self.matchSection(line)
+ if value:
+ if section is not None:
+ opts.append({'name': section, 'type': 'section', 'value': sectopts})
+ sectopts = []
+ curopts = sectopts
+ fatheropts = sectopts
+ section = value
+ continue
+
+ # It's a subsection start.
+ value = self.matchSubSection(line)
+ if value:
+ if subsection is not None:
+ raise SyntaxError('nested subsections are not supported yet')
+ subsectopts = []
+ curopts = subsectopts
+ subsection = value
+ continue
+
+ value = self.matchSubSectionEnd(line)
+ if value:
+ if subsection is None:
+ raise SyntaxError('Unmatched end subsection terminator found')
+ fatheropts.append({'name': subsection, 'type': 'subsection', 'value': subsectopts})
+ subsection = None
+ curopts = fatheropts
+ continue
+
+ # Copy anything else as is.
+ curopts.append(self.parseLine(line))
+
+ # Add last section if any
+ if sectopts:
+ opts.append({'name': section, 'type': 'section', 'value': sectopts})
+
+ return opts
+
+ def changeConf(self, file, newopts):
+ """Write settings to configuration file
+ file is a path
+ options is a set of dictionaries in the form:
+ [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
+ section is a section name like 'global'
+ """
+ output = ""
+ f = None
+ try:
+ # Do not catch an unexisting file error, we want to fail in that case
+ shutil.copy2(file, file + self.backup_suffix)
+
+ f = openLocked(file, 0o644)
+
+ oldopts = self.parse(f)
+
+ options = self.merge(oldopts, newopts)
+
+ output = self.dump(options)
+
+ # Write it out and close it.
+ f.seek(0)
+ f.truncate(0)
+ f.write(output)
+ finally:
+ try:
+ if f:
+ f.close()
+ except IOError:
+ pass
+ return True
+
+ def newConf(self, file, options):
+ """Write settings to new file, backup old
+ file is a path
+ options is a set of dictionaries in the form:
+ [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
+ section is a section name like 'global'
+ """
+ output = ""
+ f = None
+ try:
+ try:
+ shutil.copy2(file, file + self.backup_suffix)
+ except IOError as err:
+ if err.errno == 2:
+ # The orign file did not exist
+ pass
+
+ f = openLocked(file, 0o644)
+
+ # Truncate
+ f.seek(0)
+ f.truncate(0)
+
+ output = self.dump(options)
+
+ f.write(output)
+ finally:
+ try:
+ if f:
+ f.close()
+ except IOError:
+ pass
+ return True
+
+
+class SSSDChangeConf(IPAChangeConf):
+ """An SSSD-specific subclass of IPAChangeConf"""
+ OPTCRE = re.compile(
+ r'\s*(?P<option>[^:=\s][^:=]*)' # very permissive!
+ r'\s*=\s*' # any number of space/tab,
+ # followed by separator
+ # followed by any # space/tab
+ r'(?P<value>.*)$' # everything up to EOL
+ )
+
+ def __init__(self):
+ IPAChangeConf.__init__(self, "SSSD")
+ self.comment = ("#", ";")
+ self.backup_suffix = ".bak"
+ self.opts = []
+
+ def parseLine(self, line):
+ """
+ Overrides IPAChangeConf parseLine so that lines are split
+ using any separator in self.assign, not just the default one
+ """
+
+ if self.matchEmpty(line):
+ return {'name': 'empty', 'type': 'empty'}
+
+ value = self.matchComment(line)
+ if value:
+ return {'name': 'comment', 'type': 'comment', 'value': value.rstrip()}
+
+ mo = self.OPTCRE.match(line)
+ if not mo:
+ raise SyntaxError('Syntax Error: Unknown line format')
+
+ try:
+ name, value = mo.group('option', 'value')
+ except IndexError:
+ raise SyntaxError('Syntax Error: Unknown line format')
+
+ return {'name': name.strip(), 'type': 'option', 'value': value.strip()}
+
+ def readfp(self, fd):
+ self.opts.extend(self.parse(fd))
+
+ def read(self, filename):
+ fd = open(filename, 'r')
+ self.readfp(fd)
+ fd.close()
+
+ def get(self, section, name):
+ index, item = self.get_option_index(section, name)
+ if item:
+ return item['value']
+
+ def set(self, section, name, value):
+ modkw = {'type': 'section',
+ 'name': section,
+ 'value': [{
+ 'type': 'option',
+ 'name': name,
+ 'value': value,
+ 'action': 'set',
+ }],
+ 'action': 'set',
+ }
+ self.opts = self.merge(self.opts, [modkw])
+
+ def add_section(self, name, optkw, index=0):
+ optkw.append({'type': 'empty', 'value': 'empty'})
+ addkw = {'type': 'section',
+ 'name': name,
+ 'value': optkw}
+ self.opts.insert(index, addkw)
+
+ def delete_section(self, name):
+ self.delete_option('section', name)
+
+ def sections(self):
+ return [o for o in self.opts if o['type'] == 'section']
+
+ def has_section(self, section):
+ return len([o for o in self.opts if o['type'] == 'section' if o['name'] == section]) > 0
+
+ def options(self, section):
+ for opt in self.opts:
+ if opt['type'] == 'section' and opt['name'] == section:
+ return opt['value']
+
+ def delete_option(self, type, name, exclude_sections=False):
+ return self.delete_option_subtree(self.opts, type, name)
+
+ def delete_option_subtree(self, subtree, type, name, exclude_sections=False):
+ index, item = self.findOpts(subtree, type, name, exclude_sections)
+ if item:
+ del subtree[index]
+ return index
+
+ def has_option(self, section, name):
+ index, item = self.get_option_index(section, name)
+ if index != -1 and item is not None:
+ return True
+ return False
+
+ def strip_comments_empty(self, optlist):
+ retlist = []
+ for opt in optlist:
+ if opt['type'] in ('comment', 'empty'):
+ continue
+ retlist.append(opt)
+ return retlist
+
+ def get_option_index(self, parent_name, name, type='option'):
+ subtree = None
+ if parent_name:
+ pindex, pdata = self.findOpts(self.opts, 'section', parent_name)
+ if not pdata:
+ return (-1, None)
+ subtree = pdata['value']
+ else:
+ subtree = self.opts
+ return self.findOpts(subtree, type, name)
diff --git a/src/config/SSSDConfig/sssdoptions.py b/src/config/SSSDConfig/sssdoptions.py
new file mode 100644
index 0000000..0d75e6d
--- /dev/null
+++ b/src/config/SSSDConfig/sssdoptions.py
@@ -0,0 +1,578 @@
+import sys
+import gettext
+
+PACKAGE = 'sss_daemon'
+LOCALEDIR = '/usr/share/locale'
+
+translation = gettext.translation(PACKAGE, LOCALEDIR, fallback=True)
+if sys.version_info[0] > 2:
+ _ = translation.gettext
+else:
+ _ = translation.ugettext
+
+
+class SSSDOptions(object):
+ def __init__(self):
+ pass
+
+ option_strings = {
+ # [service]
+ 'debug': _('Set the verbosity of the debug logging'),
+ 'debug_level': _('Set the verbosity of the debug logging'),
+ 'debug_timestamps': _('Include timestamps in debug logs'),
+ 'debug_microseconds': _('Include microseconds in timestamps in debug logs'),
+ 'debug_backtrace_enabled': _('Enable/disable debug backtrace'),
+ 'timeout': _('Watchdog timeout before restarting service'),
+ 'command': _('Command to start service'),
+ 'reconnection_retries': _('Number of times to attempt connection to Data Providers'),
+ 'fd_limit': _('The number of file descriptors that may be opened by this responder'),
+ 'client_idle_timeout': _('Idle time before automatic disconnection of a client'),
+ 'responder_idle_timeout': _('Idle time before automatic shutdown of the responder'),
+ 'cache_first': _('Always query all the caches before querying the Data Providers'),
+ 'offline_timeout': _('When SSSD switches to offline mode the amount of time before it tries to go back online '
+ 'will increase based upon the time spent disconnected. This value is in seconds and '
+ 'calculated by the following: offline_timeout + random_offset.'),
+
+ # [sssd]
+ 'config_file_version': _(
+ 'Indicates what is the syntax of the config file. SSSD 0.6.0 and later use version 2.'),
+ 'services': _('SSSD Services to start'),
+ 'domains': _('SSSD Domains to start'),
+ 're_expression': _('Regex to parse username and domain'),
+ 'full_name_format': _('Printf-compatible format for displaying fully-qualified names'),
+ 'krb5_rcache_dir': _('Directory on the filesystem where SSSD should store Kerberos replay cache files.'),
+ 'default_domain_suffix': _('Domain to add to names without a domain component.'),
+ 'user': _('The user to drop privileges to'),
+ 'certificate_verification': _('Tune certificate verification'),
+ 'override_space': _('All spaces in group or user names will be replaced with this character'),
+ 'disable_netlink': _('Tune sssd to honor or ignore netlink state changes'),
+ 'enable_files_domain': _('Enable or disable the implicit files domain'),
+ 'domain_resolution_order': _('A specific order of the domains to be looked up'),
+ 'monitor_resolv_conf': _('Controls if SSSD should monitor the state of resolv.conf to identify when it needs '
+ 'to update its internal DNS resolver.'),
+ 'try_inotify': _('SSSD monitors the state of resolv.conf to identify when it needs to update its internal DNS '
+ 'resolver. By default, we will attempt to use inotify for this, and will fall back to '
+ 'polling resolv.conf every five seconds if inotify cannot be used.'),
+ 'implicit_pac_responder': _('Run PAC responder automatically for AD and IPA provider'),
+ 'core_dumpable': _('Enable or disable core dumps for all SSSD processes.'),
+ 'passkey_verification': _('Tune passkey verification behavior'),
+
+ # [nss]
+ 'enum_cache_timeout': _('Enumeration cache timeout length (seconds)'),
+ 'entry_cache_no_wait_timeout': _('Entry cache background update timeout length (seconds)'),
+ 'entry_negative_timeout': _('Negative cache timeout length (seconds)'),
+ 'local_negative_timeout': _('Files negative cache timeout length (seconds)'),
+ 'filter_users': _('Users that SSSD should explicitly ignore'),
+ 'filter_groups': _('Groups that SSSD should explicitly ignore'),
+ 'filter_users_in_groups': _('Should filtered users appear in groups'),
+ 'pwfield': _('The value of the password field the NSS provider should return'),
+ 'override_homedir': _('Override homedir value from the identity provider with this value'),
+ 'fallback_homedir': _('Substitute empty homedir value from the identity provider with this value'),
+ 'override_shell': _('Override shell value from the identity provider with this value'),
+ 'allowed_shells': _('The list of shells users are allowed to log in with'),
+ 'vetoed_shells': _('The list of shells that will be vetoed, and replaced with the fallback shell'),
+ 'shell_fallback': _('If a shell stored in central directory is allowed but not available, use this fallback'),
+ 'default_shell': _('Shell to use if the provider does not list one'),
+ 'memcache_timeout': _('How long will be in-memory cache records valid'),
+ 'memcache_size_passwd': _(
+ 'Size (in megabytes) of the data table allocated inside fast in-memory cache for passwd requests'),
+ 'memcache_size_group': _(
+ 'Size (in megabytes) of the data table allocated inside fast in-memory cache for group requests'),
+ 'memcache_size_initgroups': _(
+ 'Size (in megabytes) of the data table allocated inside fast in-memory cache for initgroups requests'),
+ 'homedir_substring': _('The value of this option will be used in the expansion of the override_homedir option '
+ 'if the template contains the format string %H.'),
+ 'get_domains_timeout': _('Specifies time in seconds for which the list of subdomains will be considered '
+ 'valid.'),
+ 'entry_cache_nowait_percentage': _('The entry cache can be set to automatically update entries in the '
+ 'background if they are requested beyond a percentage of the '
+ 'entry_cache_timeout value for the domain.'),
+
+ # [pam]
+ 'offline_credentials_expiration': _('How long to allow cached logins between online logins (days)'),
+ 'offline_failed_login_attempts': _('How many failed logins attempts are allowed when offline'),
+ 'offline_failed_login_delay': _(
+ 'How long (minutes) to deny login after offline_failed_login_attempts has been reached'),
+ 'pam_verbosity': _('What kind of messages are displayed to the user during authentication'),
+ 'pam_response_filter': _('Filter PAM responses sent to the pam_sss'),
+ 'pam_id_timeout': _('How many seconds to keep identity information cached for PAM requests'),
+ 'pam_pwd_expiration_warning': _('How many days before password expiration a warning should be displayed'),
+ 'pam_trusted_users': _('List of trusted uids or user\'s name'),
+ 'pam_public_domains': _('List of domains accessible even for untrusted users.'),
+ 'pam_account_expired_message': _('Message printed when user account is expired.'),
+ 'pam_account_locked_message': _('Message printed when user account is locked.'),
+ 'pam_cert_auth': _('Allow certificate based/Smartcard authentication.'),
+ 'pam_cert_db_path': _('Path to certificate database with PKCS#11 modules.'),
+ 'pam_cert_verification': _('Tune certificate verification for PAM authentication.'),
+ 'p11_child_timeout': _('How many seconds will pam_sss wait for p11_child to finish'),
+ 'pam_app_services': _('Which PAM services are permitted to contact application domains'),
+ 'pam_p11_allowed_services': _('Allowed services for using smartcards'),
+ 'p11_wait_for_card_timeout': _('Additional timeout to wait for a card if requested'),
+ 'p11_uri': _('PKCS#11 URI to restrict the selection of devices for Smartcard authentication'),
+ 'pam_initgroups_scheme': _('When shall the PAM responder force an initgroups request'),
+ 'pam_gssapi_services': _('List of PAM services that are allowed to authenticate with GSSAPI.'),
+ 'pam_gssapi_check_upn': _('Whether to match authenticated UPN with target user'),
+ 'pam_gssapi_indicators_map': _('List of pairs <PAM service>:<authentication indicator> that '
+ 'must be enforced for PAM access with GSSAPI authentication'),
+ 'pam_passkey_auth': _('Allow passkey device authentication.'),
+ 'passkey_child_timeout': _('How many seconds will pam_sss wait for passkey_child to finish'),
+ 'passkey_debug_libfido2': _('Enable debugging in the libfido2 library'),
+
+ # [sudo]
+ 'sudo_timed': _('Whether to evaluate the time-based attributes in sudo rules'),
+ 'sudo_inverse_order': _('If true, SSSD will switch back to lower-wins ordering logic'),
+ 'sudo_threshold': _('Maximum number of rules that can be refreshed at once. If this is exceeded, full refresh '
+ 'is performed.'),
+
+ # [autofs]
+ 'autofs_negative_timeout': _('Negative cache timeout length (seconds)'),
+
+ # [ssh]
+ 'ssh_hash_known_hosts': _('Whether to hash host names and addresses in the known_hosts file'),
+ 'ssh_known_hosts_timeout': _('How many seconds to keep a host in the known_hosts file after its host keys '
+ 'were requested'),
+ 'ca_db': _('Path to storage of trusted CA certificates'),
+ 'ssh_use_certificate_keys': _('Allow to generate ssh-keys from certificates'),
+ 'ssh_use_certificate_matching_rules': _('Use the following matching rules to filter the certificates for '
+ 'ssh-key generation'),
+
+ # [pac]
+ 'allowed_uids': _('List of UIDs or user names allowed to access the PAC responder'),
+ 'pac_lifetime': _('How long the PAC data is considered valid'),
+ 'pac_check': _('Validate the PAC'),
+
+ # [ifp]
+ 'user_attributes': _('List of user attributes the InfoPipe is allowed to publish'),
+
+ # [session_recording]
+ 'scope': _('One of the following strings specifying the scope of session recording: none - No users are '
+ 'recorded. some - Users/groups specified by users and groups options are recorded. all - All users '
+ 'are recorded.'),
+ 'users': _('A comma-separated list of users which should have session recording enabled. Matches user names '
+ 'as returned by NSS. I.e. after the possible space replacement, case changes, etc.'),
+ 'groups': _('A comma-separated list of groups, members of which should have session recording enabled. '
+ 'Matches group names as returned by NSS. I.e. after the possible space replacement, case changes, '
+ 'etc.'),
+ 'exclude_users': _('A comma-separated list of users to be excluded from recording, only when scope=all'),
+ 'exclude_groups': _('A comma-separated list of groups, members of which should be excluded from recording, '
+ ' only when scope=all. '),
+
+ # [provider]
+ 'id_provider': _('Identity provider'),
+ 'auth_provider': _('Authentication provider'),
+ 'access_provider': _('Access control provider'),
+ 'chpass_provider': _('Password change provider'),
+ 'sudo_provider': _('SUDO provider'),
+ 'autofs_provider': _('Autofs provider'),
+ 'hostid_provider': _('Host identity provider'),
+ 'selinux_provider': _('SELinux provider'),
+ 'session_provider': _('Session management provider'),
+ 'resolver_provider': _('Resolver provider'),
+
+ # [domain]
+ 'domain_type': _('Whether the domain is usable by the OS or by applications'),
+ 'enabled': _('Enable or disable the domain'),
+ 'min_id': _('Minimum user ID'),
+ 'max_id': _('Maximum user ID'),
+ 'enumerate': _('Enable enumerating all users/groups'),
+ 'cache_credentials': _('Cache credentials for offline login'),
+ 'use_fully_qualified_names': _('Display users/groups in fully-qualified form'),
+ 'ignore_group_members': _('Don\'t include group members in group lookups'),
+ 'entry_cache_timeout': _('Entry cache timeout length (seconds)'),
+ 'lookup_family_order': _('Restrict or prefer a specific address family when performing DNS lookups'),
+ 'account_cache_expiration': _('How long to keep cached entries after last successful login (days)'),
+ 'dns_resolver_server_timeout': _('How long should SSSD talk to single DNS server before trying next server ('
+ 'miliseconds)'),
+ 'dns_resolver_op_timeout': _('How long should keep trying to resolve single DNS query (seconds)'),
+ 'dns_resolver_timeout': _('How long to wait for replies from DNS when resolving servers (seconds)'),
+ 'dns_discovery_domain': _('The domain part of service discovery DNS query'),
+ 'override_gid': _('Override GID value from the identity provider with this value'),
+ 'case_sensitive': _('Treat usernames as case sensitive'),
+ 'entry_cache_user_timeout': _('Entry cache timeout length (seconds)'),
+ 'entry_cache_group_timeout': _('Entry cache timeout length (seconds)'),
+ 'entry_cache_netgroup_timeout': _('Entry cache timeout length (seconds)'),
+ 'entry_cache_service_timeout': _('Entry cache timeout length (seconds)'),
+ 'entry_cache_autofs_timeout': _('Entry cache timeout length (seconds)'),
+ 'entry_cache_sudo_timeout': _('Entry cache timeout length (seconds)'),
+ 'entry_cache_resolver_timeout': _('Entry cache timeout length (seconds)'),
+ 'refresh_expired_interval': _('How often should expired entries be refreshed in background'),
+ 'refresh_expired_interval_offset': _("Maximum period deviation when refreshing expired entries in background"),
+ 'dyndns_update': _("Whether to automatically update the client's DNS entry"),
+ 'dyndns_ttl': _("The TTL to apply to the client's DNS entry after updating it"),
+ 'dyndns_iface': _("The interface whose IP should be used for dynamic DNS updates"),
+ 'dyndns_refresh_interval': _("How often to periodically update the client's DNS entry"),
+ 'dyndns_refresh_interval_offset': _("Maximum period deviation when updating the client's DNS entry"),
+ 'dyndns_update_ptr': _("Whether the provider should explicitly update the PTR record as well"),
+ 'dyndns_force_tcp': _("Whether the nsupdate utility should default to using TCP"),
+ 'dyndns_auth': _("What kind of authentication should be used to perform the DNS update"),
+ 'dyndns_server': _("Override the DNS server used to perform the DNS update"),
+ 'subdomain_enumerate': _('Control enumeration of trusted domains'),
+ 'subdomain_refresh_interval': _('How often should subdomains list be refreshed'),
+ 'subdomain_refresh_interval_offset': _('Maximum period deviation when refreshing the subdomain list'),
+ 'subdomain_inherit': _('List of options that should be inherited into a subdomain'),
+ 'subdomain_homedir': _('Default subdomain homedir value'),
+ 'cached_auth_timeout': _('How long can cached credentials be used for cached authentication'),
+ 'auto_private_groups': _('Whether to automatically create private groups for users'),
+ 'pwd_expiration_warning': _('Display a warning N days before the password expires.'),
+ 'realmd_tags': _('Various tags stored by the realmd configuration service for this domain.'),
+ 'subdomains_provider': _('The provider which should handle fetching of subdomains. This value should be '
+ 'always the same as id_provider.'),
+ 'entry_cache_ssh_host_timeout': _('How many seconds to keep a host ssh key after refresh. IE how long to '
+ 'cache the host key for.'),
+ 'cache_credentials_minimal_first_factor_length': _('If 2-Factor-Authentication (2FA) is used and credentials '
+ 'should be saved this value determines the minimal length '
+ 'the first authentication factor (long term password) must '
+ 'have to be saved as SHA512 hash into the cache.'),
+ 'local_auth_policy': _('Local authentication methods policy '),
+
+ # [provider/ipa]
+ 'ipa_domain': _('IPA domain'),
+ 'ipa_server': _('IPA server address'),
+ 'ipa_backup_server': _('Address of backup IPA server'),
+ 'ipa_hostname': _('IPA client hostname'),
+ 'ipa_dyndns_update': _("Whether to automatically update the client's DNS entry in FreeIPA"),
+ 'ipa_dyndns_ttl': _("The TTL to apply to the client's DNS entry after updating it"),
+ 'ipa_dyndns_iface': _("The interface whose IP should be used for dynamic DNS updates"),
+ 'ipa_hbac_search_base': _("Search base for HBAC related objects"),
+ 'ipa_hbac_refresh': _("The amount of time between lookups of the HBAC rules against the IPA server"),
+ 'ipa_selinux_refresh': _("The amount of time in seconds between lookups of the SELinux maps against the IPA "
+ "server"),
+ 'ipa_hbac_support_srchost': _("If set to false, host argument given by PAM will be ignored"),
+ 'ipa_automount_location': _("The automounter location this IPA client is using"),
+ 'ipa_master_domain_search_base': _("Search base for object containing info about IPA domain"),
+ 'ipa_ranges_search_base': _("Search base for objects containing info about ID ranges"),
+ 'ipa_enable_dns_sites': _("Enable DNS sites - location based service discovery"),
+ 'ipa_views_search_base': _("Search base for view containers"),
+ 'ipa_view_class': _("Objectclass for view containers"),
+ 'ipa_view_name': _("Attribute with the name of the view"),
+ 'ipa_override_object_class': _("Objectclass for override objects"),
+ 'ipa_anchor_uuid': _("Attribute with the reference to the original object"),
+ 'ipa_user_override_object_class': _("Objectclass for user override objects"),
+ 'ipa_group_override_object_class': _("Objectclass for group override objects"),
+ 'ipa_deskprofile_search_base': _("Search base for Desktop Profile related objects"),
+ 'ipa_deskprofile_refresh': _("The amount of time in seconds between lookups of the Desktop Profile rules "
+ "against the IPA server"),
+ 'ipa_deskprofile_request_interval': _("The amount of time in minutes between lookups of Desktop Profiles "
+ "rules against the IPA server when the last request did not find any "
+ "rule"),
+ 'ipa_subid_ranges_search_base': _("Search base for SUBID ranges"),
+ 'ipa_access_order': _("Which rules should be used to evaluate access control"),
+ 'ipa_host_fqdn': _('The LDAP attribute that contains FQDN of the host.'),
+ 'ipa_host_object_class': _('The object class of a host entry in LDAP.'),
+ 'ipa_host_search_base': _('Use the given string as search base for host objects.'),
+ 'ipa_host_ssh_public_key': _('The LDAP attribute that contains the host\'s SSH public keys.'),
+ 'ipa_netgroup_domain': _('The LDAP attribute that contains NIS domain name of the netgroup.'),
+ 'ipa_netgroup_member': _('The LDAP attribute that contains the names of the netgroup\'s members.'),
+ 'ipa_netgroup_member_ext_host': _('The LDAP attribute that lists FQDNs of hosts and host groups that are '
+ 'members of the netgroup.'),
+ 'ipa_netgroup_member_host': _('The LDAP attribute that lists hosts and host groups that are direct members of '
+ 'the netgroup.'),
+ 'ipa_netgroup_member_of': _('The LDAP attribute that lists netgroup\'s memberships.'),
+ 'ipa_netgroup_member_user': _('The LDAP attribute that lists system users and groups that are direct members '
+ 'of the netgroup.'),
+ 'ipa_netgroup_name': _('The LDAP attribute that corresponds to the netgroup name.'),
+ 'ipa_netgroup_object_class': _('The object class of a netgroup entry in LDAP.'),
+ 'ipa_netgroup_uuid': _('The LDAP attribute that contains the UUID/GUID of an LDAP netgroup object.'),
+ 'ipa_selinux_usermap_enabled': _('The LDAP attribute that contains whether or not is user map enabled for '
+ 'usage.'),
+ 'ipa_selinux_usermap_host_category': _('The LDAP attribute that contains host category such as \'all\'.'),
+ 'ipa_selinux_usermap_member_host': _('The LDAP attribute that contains all hosts / hostgroups this rule match '
+ 'against.'),
+ 'ipa_selinux_usermap_member_user': _('The LDAP attribute that contains all users / groups this rule match '
+ 'against.'),
+ 'ipa_selinux_usermap_name': _('The LDAP attribute that contains the name of SELinux usermap.'),
+ 'ipa_selinux_usermap_object_class': _('The object class of a host entry in LDAP.'),
+ 'ipa_selinux_usermap_see_also': _('The LDAP attribute that contains DN of HBAC rule which can be used for '
+ 'matching instead of memberUser and memberHost.'),
+ 'ipa_selinux_usermap_selinux_user': _('The LDAP attribute that contains SELinux user string itself.'),
+ 'ipa_selinux_usermap_user_category': _('The LDAP attribute that contains user category such as \'all\'.'),
+ 'ipa_selinux_usermap_uuid': _('The LDAP attribute that contains unique ID of the user map.'),
+ 'ipa_server_mode': _('The option denotes that the SSSD is running on IPA server and should perform lookups of '
+ 'users and groups from trusted domains differently.'),
+ 'ipa_subdomains_search_base': _('Use the given string as search base for trusted domains.'),
+
+ # [provider/ad]
+ 'ad_domain': _('Active Directory domain'),
+ 'ad_enabled_domains': _('Enabled Active Directory domains'),
+ 'ad_server': _('Active Directory server address'),
+ 'ad_backup_server': _('Active Directory backup server address'),
+ 'ad_hostname': _('Active Directory client hostname'),
+ 'ad_enable_dns_sites': _('Enable DNS sites - location based service discovery'),
+ 'ad_access_filter': _('LDAP filter to determine access privileges'),
+ 'ad_enable_gc': _('Whether to use the Global Catalog for lookups'),
+ 'ad_gpo_access_control': _('Operation mode for GPO-based access control'),
+ 'ad_gpo_cache_timeout': _("The amount of time between lookups of the GPO policy files against the AD server"),
+ 'ad_gpo_map_interactive': _('PAM service names that map to the GPO (Deny)InteractiveLogonRight '
+ 'policy settings'),
+ 'ad_gpo_map_remote_interactive': _('PAM service names that map to the GPO (Deny)RemoteInteractiveLogonRight '
+ 'policy settings'),
+ 'ad_gpo_map_network': _('PAM service names that map to the GPO (Deny)NetworkLogonRight policy settings'),
+ 'ad_gpo_map_batch': _('PAM service names that map to the GPO (Deny)BatchLogonRight policy settings'),
+ 'ad_gpo_map_service': _('PAM service names that map to the GPO (Deny)ServiceLogonRight policy settings'),
+ 'ad_gpo_map_permit': _('PAM service names for which GPO-based access is always granted'),
+ 'ad_gpo_map_deny': _('PAM service names for which GPO-based access is always denied'),
+ 'ad_gpo_default_right': _('Default logon right (or permit/deny) to use for unmapped PAM service names'),
+ 'ad_site': _('a particular site to be used by the client'),
+ 'ad_maximum_machine_account_password_age': _('Maximum age in days before the machine account password should '
+ 'be renewed'),
+ 'ad_machine_account_password_renewal_opts': _('Option for tuning the machine account renewal task'),
+ 'ad_update_samba_machine_account_password': _('Whether to update the machine account password in the Samba '
+ 'database'),
+ 'ad_use_ldaps': _('Use LDAPS port for LDAP and Global Catalog requests'),
+ 'ad_allow_remote_domain_local_groups': _('Do not filter domain local groups from other domains'),
+
+ # [provider/krb5]
+ 'krb5_kdcip': _('Kerberos server address'),
+ 'krb5_server': _('Kerberos server address'),
+ 'krb5_backup_server': _('Kerberos backup server address'),
+ 'krb5_realm': _('Kerberos realm'),
+ 'krb5_auth_timeout': _('Authentication timeout'),
+ 'krb5_use_kdcinfo': _('Whether to create kdcinfo files'),
+ 'krb5_confd_path': _('Where to drop krb5 config snippets'),
+
+ # [provider/krb5/auth]
+ 'krb5_ccachedir': _('Directory to store credential caches'),
+ 'krb5_ccname_template': _("Location of the user's credential cache"),
+ 'krb5_keytab': _("Location of the keytab to validate credentials"),
+ 'krb5_validate': _("Enable credential validation"),
+ 'krb5_store_password_if_offline': _("Store password if offline for later online authentication"),
+ 'krb5_renewable_lifetime': _("Renewable lifetime of the TGT"),
+ 'krb5_lifetime': _("Lifetime of the TGT"),
+ 'krb5_renew_interval': _("Time between two checks for renewal"),
+ 'krb5_use_fast': _("Enables FAST"),
+ 'krb5_fast_principal': _("Selects the principal to use for FAST"),
+ 'krb5_fast_use_anonymous_pkinit': _("Use anonymous PKINIT to request FAST credentials"),
+ 'krb5_canonicalize': _("Enables principal canonicalization"),
+ 'krb5_use_enterprise_principal': _("Enables enterprise principals"),
+ 'krb5_use_subdomain_realm': _("Enables using of subdomains realms for authentication"),
+ 'krb5_map_user': _('A mapping from user names to Kerberos principal names'),
+
+ # [provider/krb5/chpass]
+ 'krb5_kpasswd': _('Server where the change password service is running if not on the KDC'),
+ 'krb5_backup_kpasswd': _('Server where the change password service is running if not on the KDC'),
+
+ # [provider/ldap]
+ 'ldap_uri': _('ldap_uri, The URI of the LDAP server'),
+ 'ldap_backup_uri': _('ldap_backup_uri, The URI of the LDAP server'),
+ 'ldap_search_base': _('The default base DN'),
+ 'ldap_schema': _('The Schema Type in use on the LDAP server, rfc2307'),
+ 'ldap_pwmodify_mode': _('Mode used to change user password'),
+ 'ldap_default_bind_dn': _('The default bind DN'),
+ 'ldap_default_authtok_type': _('The type of the authentication token of the default bind DN'),
+ 'ldap_default_authtok': _('The authentication token of the default bind DN'),
+ 'ldap_network_timeout': _('Length of time to attempt connection'),
+ 'ldap_opt_timeout': _('Length of time to attempt synchronous LDAP operations'),
+ 'ldap_offline_timeout': _('Length of time between attempts to reconnect while offline'),
+ 'ldap_force_upper_case_realm': _('Use only the upper case for realm names'),
+ 'ldap_tls_cacert': _('File that contains CA certificates'),
+ 'ldap_tls_cacertdir': _('Path to CA certificate directory'),
+ 'ldap_tls_cert': _('File that contains the client certificate'),
+ 'ldap_tls_key': _('File that contains the client key'),
+ 'ldap_tls_cipher_suite': _('List of possible ciphers suites'),
+ 'ldap_tls_reqcert': _('Require TLS certificate verification'),
+ 'ldap_sasl_mech': _('Specify the sasl mechanism to use'),
+ 'ldap_sasl_authid': _('Specify the sasl authorization id to use'),
+ 'ldap_sasl_realm': _('Specify the sasl authorization realm to use'),
+ 'ldap_sasl_minssf': _('Specify the minimal SSF for LDAP sasl authorization'),
+ 'ldap_sasl_maxssf': _('Specify the maximal SSF for LDAP sasl authorization'),
+ 'ldap_krb5_keytab': _('Kerberos service keytab'),
+ 'ldap_krb5_init_creds': _('Use Kerberos auth for LDAP connection'),
+ 'ldap_referrals': _('Follow LDAP referrals'),
+ 'ldap_krb5_ticket_lifetime': _('Lifetime of TGT for LDAP connection'),
+ 'ldap_deref': _('How to dereference aliases'),
+ 'ldap_dns_service_name': _('Service name for DNS service lookups'),
+ 'ldap_page_size': _('The number of records to retrieve in a single LDAP query'),
+ 'ldap_deref_threshold': _('The number of members that must be missing to trigger a full deref'),
+ 'ldap_ignore_unreadable_references': _('Ignore unreadable LDAP references'),
+ 'ldap_sasl_canonicalize': _('Whether the LDAP library should perform a reverse lookup to canonicalize the '
+ 'host name during a SASL bind'),
+ 'ldap_rfc2307_fallback_to_local_users': _('Allows to retain local users as members of an LDAP group for '
+ 'servers that use the RFC2307 schema.'),
+
+ 'ldap_entry_usn': _('entryUSN attribute'),
+ 'ldap_rootdse_last_usn': _('lastUSN attribute'),
+
+ 'ldap_connection_expiration_timeout': _('How long to retain a connection to the LDAP server before '
+ 'disconnecting'),
+
+ 'ldap_disable_paging': _('Disable the LDAP paging control'),
+ 'ldap_disable_range_retrieval': _('Disable Active Directory range retrieval'),
+
+ # [provider/ldap/id]
+ 'ldap_search_timeout': _('Length of time to wait for a search request'),
+ 'ldap_enumeration_search_timeout': _('Length of time to wait for a enumeration request'),
+ 'ldap_enumeration_refresh_timeout': _('Length of time between enumeration updates'),
+ 'ldap_enumeration_refresh_offset': _('Maximum period deviation between enumeration updates'),
+ 'ldap_purge_cache_timeout': _('Length of time between cache cleanups'),
+ 'ldap_purge_cache_offset': _('Maximum time deviation between cache cleanups'),
+ 'ldap_id_use_start_tls': _('Require TLS for ID lookups'),
+ 'ldap_id_mapping': _('Use ID-mapping of objectSID instead of pre-set IDs'),
+ 'ldap_user_search_base': _('Base DN for user lookups'),
+ 'ldap_user_search_scope': _('Scope of user lookups'),
+ 'ldap_user_search_filter': _('Filter for user lookups'),
+ 'ldap_user_object_class': _('Objectclass for users'),
+ 'ldap_user_name': _('Username attribute'),
+ 'ldap_user_uid_number': _('UID attribute'),
+ 'ldap_user_gid_number': _('Primary GID attribute'),
+ 'ldap_user_gecos': _('GECOS attribute'),
+ 'ldap_user_home_directory': _('Home directory attribute'),
+ 'ldap_user_shell': _('Shell attribute'),
+ 'ldap_user_uuid': _('UUID attribute'),
+ 'ldap_user_objectsid': _("objectSID attribute"),
+ 'ldap_user_primary_group': _('Active Directory primary group attribute for ID-mapping'),
+ 'ldap_user_principal': _('User principal attribute (for Kerberos)'),
+ 'ldap_user_fullname': _('Full Name'),
+ 'ldap_user_member_of': _('memberOf attribute'),
+ 'ldap_user_modify_timestamp': _('Modification time attribute'),
+ 'ldap_user_shadow_last_change': _('shadowLastChange attribute'),
+ 'ldap_user_shadow_min': _('shadowMin attribute'),
+ 'ldap_user_shadow_max': _('shadowMax attribute'),
+ 'ldap_user_shadow_warning': _('shadowWarning attribute'),
+ 'ldap_user_shadow_inactive': _('shadowInactive attribute'),
+ 'ldap_user_shadow_expire': _('shadowExpire attribute'),
+ 'ldap_user_shadow_flag': _('shadowFlag attribute'),
+ 'ldap_user_authorized_service': _('Attribute listing authorized PAM services'),
+ 'ldap_user_authorized_host': _('Attribute listing authorized server hosts'),
+ 'ldap_user_authorized_rhost': _('Attribute listing authorized server rhosts'),
+ 'ldap_user_krb_last_pwd_change': _('krbLastPwdChange attribute'),
+ 'ldap_user_krb_password_expiration': _('krbPasswordExpiration attribute'),
+ 'ldap_pwd_attribute': _('Attribute indicating that server side password policies are active'),
+ 'ldap_user_ad_account_expires': _('accountExpires attribute of AD'),
+ 'ldap_user_ad_user_account_control': _('userAccountControl attribute of AD'),
+ 'ldap_ns_account_lock': _('nsAccountLock attribute'),
+ 'ldap_user_nds_login_disabled': _('loginDisabled attribute of NDS'),
+ 'ldap_user_nds_login_expiration_time': _('loginExpirationTime attribute of NDS'),
+ 'ldap_user_nds_login_allowed_time_map': _('loginAllowedTimeMap attribute of NDS'),
+ 'ldap_user_ssh_public_key': _('SSH public key attribute'),
+ 'ldap_user_auth_type': _('attribute listing allowed authentication types for a user'),
+ 'ldap_user_certificate': _('attribute containing the X509 certificate of the user'),
+ 'ldap_user_email': _('attribute containing the email address of the user'),
+ 'ldap_user_passkey': _('attribute containing the passkey mapping data of the user'),
+ 'ldap_user_extra_attrs': _('A list of extra attributes to download along with the user entry'),
+
+ 'ldap_group_search_base': _('Base DN for group lookups'),
+ 'ldap_group_object_class': _('Objectclass for groups'),
+ 'ldap_group_name': _('Group name'),
+ 'ldap_group_pwd': _('Group password'),
+ 'ldap_group_gid_number': _('GID attribute'),
+ 'ldap_group_member': _('Group member attribute'),
+ 'ldap_group_uuid': _('Group UUID attribute'),
+ 'ldap_group_objectsid': _("objectSID attribute"),
+ 'ldap_group_modify_timestamp': _('Modification time attribute for groups'),
+ 'ldap_group_type': _('Type of the group and other flags'),
+ 'ldap_group_external_member': _('The LDAP group external member attribute'),
+ 'ldap_group_nesting_level': _('Maximum nesting level SSSD will follow'),
+ 'ldap_group_search_filter': _('Filter for group lookups'),
+ 'ldap_group_search_scope': _('Scope of group lookups'),
+
+ 'ldap_netgroup_search_base': _('Base DN for netgroup lookups'),
+ 'ldap_netgroup_object_class': _('Objectclass for netgroups'),
+ 'ldap_netgroup_name': _('Netgroup name'),
+ 'ldap_netgroup_member': _('Netgroups members attribute'),
+ 'ldap_netgroup_triple': _('Netgroup triple attribute'),
+ 'ldap_netgroup_modify_timestamp': _('Modification time attribute for netgroups'),
+
+ 'ldap_service_search_base': _('Base DN for service lookups'),
+ 'ldap_service_object_class': _('Objectclass for services'),
+ 'ldap_service_name': _('Service name attribute'),
+ 'ldap_service_port': _('Service port attribute'),
+ 'ldap_service_proto': _('Service protocol attribute'),
+
+ 'ldap_idmap_range_min': _('Lower bound for ID-mapping'),
+ 'ldap_idmap_range_max': _('Upper bound for ID-mapping'),
+ 'ldap_idmap_range_size': _('Number of IDs for each slice when ID-mapping'),
+ 'ldap_idmap_autorid_compat': _('Use autorid-compatible algorithm for ID-mapping'),
+ 'ldap_idmap_default_domain': _('Name of the default domain for ID-mapping'),
+ 'ldap_idmap_default_domain_sid': _('SID of the default domain for ID-mapping'),
+ 'ldap_idmap_helper_table_size': _('Number of secondary slices'),
+
+ 'ldap_use_tokengroups': _('Whether to use Token-Groups'),
+ 'ldap_min_id': _('Set lower boundary for allowed IDs from the LDAP server'),
+ 'ldap_max_id': _('Set upper boundary for allowed IDs from the LDAP server'),
+ 'ldap_pwdlockout_dn': _('DN for ppolicy queries'),
+ 'wildcard_limit': _('How many maximum entries to fetch during a wildcard request'),
+ 'ldap_library_debug_level': _('Set libldap debug level'),
+
+ # [provider/ldap/auth]
+ 'ldap_pwd_policy': _('Policy to evaluate the password expiration'),
+
+ # [provider/ldap/access]
+ 'ldap_access_filter': _('LDAP filter to determine access privileges'),
+ 'ldap_account_expire_policy': _('Which attributes shall be used to evaluate if an account is expired'),
+ 'ldap_access_order': _('Which rules should be used to evaluate access control'),
+
+ # [provider/ldap/chpass]
+ 'ldap_chpass_uri': _('URI of an LDAP server where password changes are allowed'),
+ 'ldap_chpass_backup_uri': _('URI of a backup LDAP server where password changes are allowed'),
+ 'ldap_chpass_dns_service_name': _('DNS service name for LDAP password change server'),
+ 'ldap_chpass_update_last_change': _('Whether to update the ldap_user_shadow_last_change attribute after a '
+ 'password change'),
+
+ # [provider/ldap/sudo]
+ 'ldap_sudo_search_base': _('Base DN for sudo rules lookups'),
+ 'ldap_sudo_full_refresh_interval': _('Automatic full refresh period'),
+ 'ldap_sudo_smart_refresh_interval': _('Automatic smart refresh period'),
+ 'ldap_sudo_random_offset': _('Smart and full refresh random offset'),
+ 'ldap_sudo_use_host_filter': _('Whether to filter rules by hostname, IP addresses and network'),
+ 'ldap_sudo_hostnames': _('Hostnames and/or fully qualified domain names of this machine to filter sudo rules'),
+ 'ldap_sudo_ip': _('IPv4 or IPv6 addresses or network of this machine to filter sudo rules'),
+ 'ldap_sudo_include_netgroups': _('Whether to include rules that contains netgroup in host attribute'),
+ 'ldap_sudo_include_regexp': _('Whether to include rules that contains regular expression in host attribute'),
+ 'ldap_sudorule_object_class': _('Object class for sudo rules'),
+ 'ldap_sudorule_object_class_attr': _('Name of attribute that is used as object class for sudo rules'),
+ 'ldap_sudorule_name': _('Sudo rule name'),
+ 'ldap_sudorule_command': _('Sudo rule command attribute'),
+ 'ldap_sudorule_host': _('Sudo rule host attribute'),
+ 'ldap_sudorule_user': _('Sudo rule user attribute'),
+ 'ldap_sudorule_option': _('Sudo rule option attribute'),
+ 'ldap_sudorule_runas': _('Sudo rule runas attribute'),
+ 'ldap_sudorule_runasuser': _('Sudo rule runasuser attribute'),
+ 'ldap_sudorule_runasgroup': _('Sudo rule runasgroup attribute'),
+ 'ldap_sudorule_notbefore': _('Sudo rule notbefore attribute'),
+ 'ldap_sudorule_notafter': _('Sudo rule notafter attribute'),
+ 'ldap_sudorule_order': _('Sudo rule order attribute'),
+
+ # [provider/ldap/autofs]
+ 'ldap_autofs_map_object_class': _('Object class for automounter maps'),
+ 'ldap_autofs_map_name': _('Automounter map name attribute'),
+ 'ldap_autofs_entry_object_class': _('Object class for automounter map entries'),
+ 'ldap_autofs_entry_key': _('Automounter map entry key attribute'),
+ 'ldap_autofs_entry_value': _('Automounter map entry value attribute'),
+ 'ldap_autofs_search_base': _('Base DN for automounter map lookups'),
+ 'ldap_autofs_map_master_name': _('The name of the automount master map in LDAP.'),
+
+ # [provider/ldap/resolver]
+ 'ldap_iphost_search_base': _('Base DN for IP hosts lookups'),
+ 'ldap_iphost_object_class': _('Object class for IP hosts'),
+ 'ldap_iphost_name': _('IP host name attribute'),
+ 'ldap_iphost_number': _('IP host number (address) attribute'),
+ 'ldap_iphost_entry_usn': _('IP host entryUSN attribute'),
+ 'ldap_ipnetwork_search_base': _('Base DN for IP networks lookups'),
+ 'ldap_ipnetwork_object_class': _('Object class for IP networks'),
+ 'ldap_ipnetwork_name': _('IP network name attribute'),
+ 'ldap_ipnetwork_number': _('IP network number (address) attribute'),
+ 'ldap_ipnetwork_entry_usn': _('IP network entryUSN attribute'),
+
+ # [provider/simple/access]
+ 'simple_allow_users': _('Comma separated list of allowed users'),
+ 'simple_deny_users': _('Comma separated list of prohibited users'),
+ 'simple_allow_groups': _('Comma separated list of groups that are allowed to log in. This applies only to '
+ 'groups within this SSSD domain. Local groups are not evaluated.'),
+ 'simple_deny_groups': _('Comma separated list of groups that are explicitly denied access. This applies only '
+ 'to groups within this SSSD domain. Local groups are not evaluated.'),
+
+ # [provider/proxy]
+ 'proxy_max_children': _('The number of preforked proxy children.'),
+
+ # [provider/proxy/id]
+ 'proxy_lib_name': _('The name of the NSS library to use'),
+ 'proxy_resolver_lib_name': _('The name of the NSS library to use for hosts and networks lookups'),
+ 'proxy_fast_alias': _('Whether to look up canonical group name from cache if possible'),
+
+ # [provider/proxy/auth]
+ 'proxy_pam_target': _('PAM stack to use'),
+
+ # [provider/files]
+ 'passwd_files': _('Path of passwd file sources.'),
+ 'group_files': _('Path of group file sources.')
+ }
diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py
new file mode 100755
index 0000000..b160be2
--- /dev/null
+++ b/src/config/SSSDConfigTest.py
@@ -0,0 +1,2160 @@
+#!/usr/bin/env python
+# SSSD
+#
+# SSSD Config API tests
+#
+# Copyright (C) Red Hat
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+"""
+Created on Sep 18, 2009
+
+@author: sgallagh
+"""
+import unittest
+import os
+import shutil
+import tempfile
+from stat import ST_MODE, S_IMODE
+
+import sys
+
+srcdir = os.getenv('srcdir')
+if srcdir:
+ sys.path.insert(0, "./src/config")
+ srcdir = srcdir + "/src/config"
+else:
+ srcdir = "."
+import SSSDConfig # noqa
+
+
+def create_temp_dir():
+ test_dir = os.environ.get('SSS_TEST_DIR') or "."
+ return tempfile.mkdtemp(dir=test_dir)
+
+
+def striplist(the_list):
+ return ([x.strip() for x in the_list])
+
+
+class SSSDConfigTestValid(unittest.TestCase):
+ def setUp(self):
+ self.tmp_dir = create_temp_dir()
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp_dir)
+
+ def testServices(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Validate services
+ services = sssdconfig.list_services()
+ self.assertTrue('sssd' in services)
+ self.assertTrue('nss' in services)
+ self.assertTrue('pam' in services)
+
+ # Verify service attributes
+ sssd_service = sssdconfig.get_service('sssd')
+ service_opts = sssd_service.list_options()
+
+ self.assertTrue('services' in service_opts.keys())
+ service_list = sssd_service.get_option('services')
+ self.assertTrue('nss' in service_list)
+ self.assertTrue('pam' in service_list)
+
+ self.assertTrue('domains' in service_opts)
+
+ self.assertTrue('reconnection_retries' in service_opts)
+
+ del sssdconfig
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.new_config()
+ sssdconfig.delete_service('sssd')
+ new_sssd_service = sssdconfig.new_service('sssd')
+ new_options = new_sssd_service.list_options()
+
+ self.assertTrue('debug_level' in new_options)
+ self.assertEqual(new_options['debug_level'][0], int)
+
+ self.assertTrue('command' in new_options)
+ self.assertEqual(new_options['command'][0], str)
+
+ self.assertTrue('reconnection_retries' in new_options)
+ self.assertEqual(new_options['reconnection_retries'][0], int)
+
+ self.assertTrue('services' in new_options)
+ self.assertEqual(new_options['debug_level'][0], int)
+
+ self.assertTrue('domains' in new_options)
+ self.assertEqual(new_options['domains'][0], list)
+ self.assertEqual(new_options['domains'][1], str)
+
+ self.assertTrue('re_expression' in new_options)
+ self.assertEqual(new_options['re_expression'][0], str)
+
+ self.assertTrue('full_name_format' in new_options)
+ self.assertEqual(new_options['full_name_format'][0], str)
+
+ self.assertTrue('default_domain_suffix' in new_options)
+ self.assertEqual(new_options['default_domain_suffix'][0], str)
+
+ self.assertTrue('domain_resolution_order' in new_options)
+ self.assertEqual(new_options['domain_resolution_order'][0], list)
+ self.assertEqual(new_options['domain_resolution_order'][1], str)
+
+ del sssdconfig
+
+ def testDomains(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Validate domain list
+ domains = sssdconfig.list_domains()
+ self.assertTrue('LDAP' in domains)
+ self.assertTrue('PROXY' in domains)
+ self.assertTrue('IPA' in domains)
+
+ # Verify domain attributes
+ ipa_domain = sssdconfig.get_domain('IPA')
+ domain_opts = ipa_domain.list_options()
+ self.assertTrue('debug_level' in domain_opts.keys())
+ self.assertTrue('id_provider' in domain_opts.keys())
+ self.assertTrue('auth_provider' in domain_opts.keys())
+ self.assertEqual(ipa_domain.get_option('debug_level'), 0xff0)
+
+ proxy_domain = sssdconfig.get_domain('PROXY')
+ self.assertEqual(proxy_domain.get_option('debug_level'), 1)
+
+ # Verify attributes in responders
+ pam_responder = sssdconfig.get_service('pam')
+ self.assertEqual(pam_responder.get_option('debug_level'), 2)
+
+ sudo_responder = sssdconfig.get_service('sudo')
+ self.assertEqual(sudo_responder.get_option('debug_level'), 0xfc10)
+
+ del sssdconfig
+
+ def testListProviders(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ sssdconfig.new_config()
+ junk_domain = sssdconfig.new_domain('junk')
+ providers = junk_domain.list_providers()
+ self.assertTrue('ldap' in providers.keys())
+
+ def testCreateNewLDAPConfig(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ sssdconfig.new_config()
+
+ ldap_domain = sssdconfig.new_domain('LDAP')
+ ldap_domain.add_provider('ldap', 'id')
+ ldap_domain.set_option('debug_level', 1)
+ ldap_domain.set_active(True)
+ sssdconfig.save_domain(ldap_domain)
+
+ of = self.tmp_dir + '/testCreateNewLDAPConfig.conf'
+
+ # Ensure the output file doesn't exist
+ try:
+ os.unlink(of)
+ except OSError:
+ pass
+
+ # Write out the file
+ sssdconfig.write(of)
+
+ # Verify that the output file has the correct permissions
+ mode = os.stat(of)[ST_MODE]
+
+ # Output files should not be readable or writable by
+ # non-owners, and should not be executable by anyone
+ self.assertFalse(S_IMODE(mode) & 0o177)
+
+ # try to import saved configuration file
+ config = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ config.import_config(configfile=of)
+
+ # Remove the output file
+ os.unlink(of)
+
+ def testModifyExistingConfig(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ ldap_domain = sssdconfig.get_domain('LDAP')
+ ldap_domain.set_option('debug_level', 3)
+
+ ldap_domain.remove_provider('auth')
+ ldap_domain.add_provider('krb5', 'auth')
+ ldap_domain.set_active(True)
+ sssdconfig.save_domain(ldap_domain)
+
+ proxy_domain = sssdconfig.get_domain('PROXY')
+ proxy_domain.set_option('debug_level', 0x1f10)
+ sssdconfig.save_domain(proxy_domain)
+
+ sudo_responder = sssdconfig.get_service('sudo')
+ sudo_responder.set_option('debug_level', 0x2210)
+ sssdconfig.save_service(sudo_responder)
+
+ pam_responder = sssdconfig.get_service('pam')
+ pam_responder.set_option('debug_level', 9)
+ sssdconfig.save_service(pam_responder)
+
+ of = self.tmp_dir + '/testModifyExistingConfig.conf'
+
+ # Ensure the output file doesn't exist
+ try:
+ os.unlink(of)
+ except OSError:
+ pass
+
+ # Write out the file
+ sssdconfig.write(of)
+
+ # Verify that the output file has the correct permissions
+ mode = os.stat(of)[ST_MODE]
+
+ # Output files should not be readable or writable by
+ # non-owners, and should not be executable by anyone
+ self.assertFalse(S_IMODE(mode) & 0o177)
+
+ # try to import saved configuration file
+ config = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ config.import_config(configfile=of)
+
+ # test set_option 'debug_level' value
+
+ # check internal state before parsing strings which is done in
+ # get_domain or get_service
+ debug_option = [x for x in config.options('domain/LDAP')
+ if x['name'] == 'debug_level']
+ self.assertEqual(len(debug_option), 1)
+ self.assertEqual(debug_option[0]['value'], '3')
+
+ debug_option = [x for x in config.options('domain/PROXY')
+ if x['name'] == 'debug_level']
+ self.assertEqual(len(debug_option), 1)
+ self.assertEqual(debug_option[0]['value'], '0x1f10')
+
+ debug_option = [x for x in config.options('sudo')
+ if x['name'] == 'debug_level']
+ self.assertEqual(len(debug_option), 1)
+ self.assertEqual(debug_option[0]['value'], '0x2210')
+
+ debug_option = [x for x in config.options('pam')
+ if x['name'] == 'debug_level']
+ self.assertEqual(len(debug_option), 1)
+ self.assertEqual(debug_option[0]['value'], '9')
+
+ # Remove the output file
+ os.unlink(of)
+
+ def testSpaces(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+ ldap_domain = sssdconfig.get_domain('LDAP')
+ self.assertEqual(ldap_domain.get_option('auth_provider'), 'ldap')
+ self.assertEqual(ldap_domain.get_option('id_provider'), 'ldap')
+
+
+class SSSDConfigTestInvalid(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def testBadBool(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-invalid-badbool.conf")
+ self.assertRaises(TypeError,
+ sssdconfig.get_domain, 'IPA')
+
+
+class SSSDConfigTestSSSDService(unittest.TestCase):
+ def setUp(self):
+ self.schema = SSSDConfig.SSSDConfigSchema(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ def tearDown(self):
+ pass
+
+ def testInit(self):
+ # Positive test
+ SSSDConfig.SSSDService('sssd', self.schema)
+
+ # Type Error test
+ # Name is not a string
+ self.assertRaises(TypeError, SSSDConfig.SSSDService, 3, self.schema)
+
+ # TypeError test
+ # schema is not an SSSDSchema
+ self.assertRaises(TypeError, SSSDConfig.SSSDService, '3', self)
+
+ # ServiceNotRecognizedError test
+ self.assertRaises(SSSDConfig.ServiceNotRecognizedError,
+ SSSDConfig.SSSDService, 'ssd', self.schema)
+
+ def testListOptions(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ options = service.list_options()
+ control_list = [
+ 'config_file_version',
+ 'services',
+ 'domains',
+ 'timeout',
+ 're_expression',
+ 'full_name_format',
+ 'krb5_rcache_dir',
+ 'user',
+ 'default_domain_suffix',
+ 'debug',
+ 'debug_level',
+ 'debug_timestamps',
+ 'debug_microseconds',
+ 'debug_backtrace_enabled',
+ 'command',
+ 'reconnection_retries',
+ 'fd_limit',
+ 'client_idle_timeout',
+ 'responder_idle_timeout',
+ 'cache_first',
+ 'description',
+ 'certificate_verification',
+ 'override_space',
+ 'disable_netlink',
+ 'enable_files_domain',
+ 'domain_resolution_order',
+ 'try_inotify',
+ 'monitor_resolv_conf',
+ 'implicit_pac_responder',
+ 'core_dumpable',
+ 'passkey_verification',
+ ]
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ self.assertTrue(type(options['reconnection_retries']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['reconnection_retries'][0] == int,
+ "reconnection_retries should require an int. "
+ "list_options is requiring a %s" %
+ options['reconnection_retries'][0])
+
+ self.assertTrue(options['reconnection_retries'][1] is None,
+ "reconnection_retries should not require a subtype. "
+ "list_options is requiring a %s" %
+ options['reconnection_retries'][1])
+
+ self.assertTrue(options['reconnection_retries'][3] is None,
+ "reconnection_retries should have no default")
+
+ self.assertTrue(type(options['services']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['services'][0] == list,
+ "services should require an list. "
+ "list_options is requiring a %s" %
+ options['services'][0])
+
+ self.assertTrue(options['services'][1] == str,
+ "services should require a subtype of str. "
+ "list_options is requiring a %s" %
+ options['services'][1])
+
+ def testListMandatoryOptions(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ options = service.list_mandatory_options()
+ control_list = [
+ 'services',
+ 'domains']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ self.assertTrue(type(options['services']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['services'][0] == list,
+ "services should require an list. "
+ "list_options is requiring a %s" %
+ options['services'][0])
+
+ self.assertTrue(options['services'][1] == str,
+ "services should require a subtype of str. "
+ "list_options is requiring a %s" %
+ options['services'][1])
+
+ def testSetOption(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ # Positive test - Exactly right
+ service.set_option('debug_level', 2)
+ self.assertEqual(service.get_option('debug_level'), 2)
+
+ # Positive test - Allow converting "safe" values
+ service.set_option('debug_level', '2')
+ self.assertEqual(service.get_option('debug_level'), 2)
+
+ # Positive test - Remove option if value is None
+ service.set_option('debug_level', None)
+ self.assertTrue('debug_level' not in service.options.keys())
+
+ # Negative test - Nonexistent Option
+ self.assertRaises(SSSDConfig.NoOptionError, service.set_option, 'nosuchoption', 1)
+
+ # Negative test - Incorrect type
+ self.assertRaises(TypeError, service.set_option, 'debug_level', 'two')
+
+ def testGetOption(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ # Positive test - List of values
+ self.assertEqual(service.get_option('services'), ['nss', 'pam'])
+
+ # Negative Test - Bad Option
+ self.assertRaises(SSSDConfig.NoOptionError, service.get_option, 'nosuchoption')
+
+ def testGetAllOptions(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ # Positive test
+ options = service.get_all_options()
+ control_list = ['services']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ def testRemoveOption(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ # Positive test - Remove an option that exists
+ self.assertEqual(service.get_option('services'), ['nss', 'pam'])
+ service.remove_option('services')
+ self.assertRaises(SSSDConfig.NoOptionError, service.get_option, 'debug_level')
+
+ # Positive test - Remove an option that doesn't exist
+ self.assertRaises(SSSDConfig.NoOptionError, service.get_option, 'nosuchentry')
+ service.remove_option('nosuchentry')
+
+
+class SSSDConfigTestSSSDDomain(unittest.TestCase):
+ def setUp(self):
+ self.schema = SSSDConfig.SSSDConfigSchema(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ def tearDown(self):
+ pass
+
+ def testInit(self):
+ # Positive Test
+ SSSDConfig.SSSDDomain('mydomain', self.schema)
+
+ # Negative Test - Name not a string
+ self.assertRaises(TypeError, SSSDConfig.SSSDDomain, 2, self.schema)
+
+ # Negative Test - Schema is not an SSSDSchema
+ self.assertRaises(TypeError, SSSDConfig.SSSDDomain, 'mydomain', self)
+
+ def testGetName(self):
+ # Positive Test
+ domain = SSSDConfig.SSSDDomain('mydomain', self.schema)
+
+ self.assertEqual(domain.get_name(), 'mydomain')
+
+ def testSetActive(self):
+ # Positive Test
+ domain = SSSDConfig.SSSDDomain('mydomain', self.schema)
+
+ # Should default to inactive
+ self.assertFalse(domain.active)
+ domain.set_active(True)
+ self.assertTrue(domain.active)
+ domain.set_active(False)
+ self.assertFalse(domain.active)
+
+ def testListOptions(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # First test default options
+ options = domain.list_options()
+ control_list = [
+ 'description',
+ 'enabled',
+ 'debug',
+ 'debug_level',
+ 'debug_timestamps',
+ 'domain_type',
+ 'min_id',
+ 'max_id',
+ 'timeout',
+ 'offline_timeout',
+ 'offline_timeout_max',
+ 'offline_timeout_random_offset',
+ 'command',
+ 'enumerate',
+ 'cache_credentials',
+ 'cache_credentials_minimal_first_factor_length',
+ 'use_fully_qualified_names',
+ 'ignore_group_members',
+ 'filter_users',
+ 'filter_groups',
+ 'entry_cache_timeout',
+ 'entry_cache_user_timeout',
+ 'entry_cache_group_timeout',
+ 'entry_cache_netgroup_timeout',
+ 'entry_cache_service_timeout',
+ 'entry_cache_autofs_timeout',
+ 'entry_cache_sudo_timeout',
+ 'entry_cache_ssh_host_timeout',
+ 'entry_cache_resolver_timeout',
+ 'refresh_expired_interval',
+ 'lookup_family_order',
+ 'account_cache_expiration',
+ 'dns_resolver_server_timeout',
+ 'dns_resolver_op_timeout',
+ 'dns_resolver_timeout',
+ 'dns_discovery_domain',
+ 'dyndns_update',
+ 'dyndns_ttl',
+ 'dyndns_iface',
+ 'dyndns_refresh_interval',
+ 'dyndns_refresh_interval_offset',
+ 'dyndns_update_ptr',
+ 'dyndns_force_tcp',
+ 'dyndns_auth',
+ 'dyndns_server',
+ 'subdomain_enumerate',
+ 'override_gid',
+ 'case_sensitive',
+ 'override_homedir',
+ 'fallback_homedir',
+ 'homedir_substring',
+ 'override_shell',
+ 'default_shell',
+ 'pwd_expiration_warning',
+ 'id_provider',
+ 'auth_provider',
+ 'access_provider',
+ 'chpass_provider',
+ 'sudo_provider',
+ 'autofs_provider',
+ 'hostid_provider',
+ 'subdomains_provider',
+ 'selinux_provider',
+ 'session_provider',
+ 'resolver_provider',
+ 'realmd_tags',
+ 'subdomain_refresh_interval',
+ 'subdomain_refresh_interval_offset',
+ 'subdomain_inherit',
+ 'subdomain_homedir',
+ 'full_name_format',
+ 're_expression',
+ 'cached_auth_timeout',
+ 'auto_private_groups',
+ 'pam_gssapi_services',
+ 'pam_gssapi_check_upn',
+ 'pam_gssapi_indicators_map',
+ 'refresh_expired_interval',
+ 'refresh_expired_interval_offset',
+ 'local_auth_policy']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ self.assertTrue(type(options['max_id']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['max_id'][0] == int,
+ "max_id should require an int. "
+ "list_options is requiring a %s" %
+ options['max_id'][0])
+
+ self.assertTrue(options['max_id'][1] is None,
+ "max_id should not require a subtype. "
+ "list_options is requiring a %s" %
+ options['max_id'][1])
+
+ # Add a provider that has global options and verify that
+ # The new options appear.
+ domain.add_provider('krb5', 'auth')
+
+ backup_list = control_list[:]
+ control_list.extend(
+ ['krb5_server',
+ 'krb5_backup_server',
+ 'krb5_realm',
+ 'krb5_kpasswd',
+ 'krb5_backup_kpasswd',
+ 'krb5_ccachedir',
+ 'krb5_ccname_template',
+ 'krb5_keytab',
+ 'krb5_validate',
+ 'krb5_store_password_if_offline',
+ 'krb5_auth_timeout',
+ 'krb5_renewable_lifetime',
+ 'krb5_lifetime',
+ 'krb5_renew_interval',
+ 'krb5_use_fast',
+ 'krb5_fast_principal',
+ 'krb5_fast_use_anonymous_pkinit',
+ 'krb5_canonicalize',
+ 'krb5_use_enterprise_principal',
+ 'krb5_use_subdomain_realm',
+ 'krb5_use_kdcinfo',
+ 'krb5_map_user'])
+
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ control_list.extend(['krb5_kdcip'])
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Remove the auth domain and verify that the options
+ # revert to the backup_list
+ domain.remove_provider('auth')
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in backup_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in backup_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ def testListMandatoryOptions(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # First test default options
+ options = domain.list_mandatory_options()
+ control_list = ['id_provider']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Add a provider that has global options and verify that
+ # The new options appear.
+ domain.add_provider('krb5', 'auth')
+
+ backup_list = control_list[:]
+ control_list.extend(['krb5_realm'])
+
+ options = domain.list_mandatory_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Remove the auth domain and verify that the options
+ # revert to the backup_list
+ domain.remove_provider('auth')
+ options = domain.list_mandatory_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in backup_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in backup_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ def testListProviders(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ control_provider_dict = {
+ 'ipa': ['id', 'auth', 'access', 'chpass', 'sudo', 'autofs',
+ 'session', 'hostid', 'subdomains'],
+ 'ad': ['id', 'auth', 'access', 'chpass', 'sudo', 'autofs',
+ 'subdomains', 'resolver'],
+ 'ldap': ['id', 'auth', 'access', 'chpass', 'sudo', 'autofs',
+ 'resolver'],
+ 'krb5': ['auth', 'access', 'chpass'],
+ 'proxy': ['id', 'auth', 'chpass'],
+ 'simple': ['access'],
+ 'permit': ['access'],
+ 'deny': ['access']}
+
+ providers = domain.list_providers()
+
+ # Ensure that all of the expected defaults are there
+ for provider in control_provider_dict.keys():
+ for ptype in control_provider_dict[provider]:
+ self.assertTrue(provider in providers)
+ self.assertTrue(ptype in providers[provider])
+
+ for provider in providers.keys():
+ for ptype in providers[provider]:
+ self.assertTrue(provider in control_provider_dict)
+ self.assertTrue(ptype in control_provider_dict[provider])
+
+ def testListProviderOptions(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Test looking up a specific provider type
+ options = domain.list_provider_options('krb5', 'auth')
+ control_list = [
+ 'krb5_server',
+ 'krb5_backup_server',
+ 'krb5_kdcip',
+ 'krb5_realm',
+ 'krb5_kpasswd',
+ 'krb5_backup_kpasswd',
+ 'krb5_ccachedir',
+ 'krb5_ccname_template',
+ 'krb5_keytab',
+ 'krb5_validate',
+ 'krb5_store_password_if_offline',
+ 'krb5_auth_timeout',
+ 'krb5_renewable_lifetime',
+ 'krb5_lifetime',
+ 'krb5_renew_interval',
+ 'krb5_use_fast',
+ 'krb5_fast_principal',
+ 'krb5_fast_use_anonymous_pkinit',
+ 'krb5_canonicalize',
+ 'krb5_use_enterprise_principal',
+ 'krb5_use_subdomain_realm',
+ 'krb5_use_kdcinfo',
+ 'krb5_map_user']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Test looking up all provider values
+ options = domain.list_provider_options('krb5')
+ control_list.extend(['krb5_kpasswd'])
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ def testAddProvider(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Positive Test
+ domain.add_provider('proxy', 'id')
+
+ # Negative Test - No such backend type
+ self.assertRaises(SSSDConfig.NoSuchProviderError,
+ domain.add_provider, 'nosuchbackend', 'auth')
+
+ # Negative Test - No such backend subtype
+ self.assertRaises(SSSDConfig.NoSuchProviderSubtypeError,
+ domain.add_provider, 'ldap', 'nosuchsubtype')
+
+ # Negative Test - Try to add a second provider of the same type
+ self.assertRaises(SSSDConfig.ProviderSubtypeInUse,
+ domain.add_provider, 'ldap', 'id')
+
+ def testRemoveProvider(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # First test default options
+ options = domain.list_options()
+ control_list = [
+ 'description',
+ 'enabled',
+ 'debug',
+ 'debug_level',
+ 'debug_timestamps',
+ 'domain_type',
+ 'min_id',
+ 'max_id',
+ 'timeout',
+ 'offline_timeout',
+ 'offline_timeout_max',
+ 'offline_timeout_random_offset',
+ 'command',
+ 'enumerate',
+ 'cache_credentials',
+ 'cache_credentials_minimal_first_factor_length',
+ 'use_fully_qualified_names',
+ 'ignore_group_members',
+ 'filter_users',
+ 'filter_groups',
+ 'entry_cache_timeout',
+ 'entry_cache_user_timeout',
+ 'entry_cache_group_timeout',
+ 'entry_cache_netgroup_timeout',
+ 'entry_cache_service_timeout',
+ 'entry_cache_autofs_timeout',
+ 'entry_cache_sudo_timeout',
+ 'entry_cache_ssh_host_timeout',
+ 'entry_cache_resolver_timeout',
+ 'refresh_expired_interval',
+ 'account_cache_expiration',
+ 'lookup_family_order',
+ 'dns_resolver_server_timeout',
+ 'dns_resolver_op_timeout',
+ 'dns_resolver_timeout',
+ 'dns_discovery_domain',
+ 'dyndns_update',
+ 'dyndns_ttl',
+ 'dyndns_iface',
+ 'dyndns_refresh_interval',
+ 'dyndns_update_ptr',
+ 'dyndns_force_tcp',
+ 'dyndns_auth',
+ 'dyndns_server',
+ 'subdomain_enumerate',
+ 'override_gid',
+ 'case_sensitive',
+ 'override_homedir',
+ 'fallback_homedir',
+ 'homedir_substring',
+ 'override_shell',
+ 'default_shell',
+ 'pwd_expiration_warning',
+ 'id_provider',
+ 'auth_provider',
+ 'access_provider',
+ 'chpass_provider',
+ 'sudo_provider',
+ 'autofs_provider',
+ 'hostid_provider',
+ 'subdomains_provider',
+ 'selinux_provider',
+ 'session_provider',
+ 'resolver_provider',
+ 'realmd_tags',
+ 'subdomain_refresh_interval',
+ 'subdomain_refresh_interval_offset',
+ 'subdomain_inherit',
+ 'subdomain_homedir',
+ 'full_name_format',
+ 're_expression',
+ 'cached_auth_timeout',
+ 'auto_private_groups',
+ 'pam_gssapi_services',
+ 'pam_gssapi_check_upn',
+ 'pam_gssapi_indicators_map',
+ 'refresh_expired_interval',
+ 'refresh_expired_interval_offset',
+ 'dyndns_refresh_interval',
+ 'dyndns_refresh_interval_offset',
+ 'local_auth_policy']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ self.assertTrue(type(options['max_id']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['max_id'][0] == int,
+ "config_file_version should require an int. "
+ "list_options is requiring a %s" %
+ options['max_id'][0])
+
+ self.assertTrue(options['max_id'][1] is None,
+ "config_file_version should not require a subtype. "
+ "list_options is requiring a %s" %
+ options['max_id'][1])
+
+ # Add a provider that has global options and verify that
+ # The new options appear.
+ domain.add_provider('krb5', 'auth')
+
+ backup_list = control_list[:]
+ control_list.extend(
+ ['krb5_server',
+ 'krb5_backup_server',
+ 'krb5_kdcip',
+ 'krb5_realm',
+ 'krb5_kpasswd',
+ 'krb5_backup_kpasswd',
+ 'krb5_ccachedir',
+ 'krb5_ccname_template',
+ 'krb5_keytab',
+ 'krb5_validate',
+ 'krb5_store_password_if_offline',
+ 'krb5_auth_timeout',
+ 'krb5_renewable_lifetime',
+ 'krb5_lifetime',
+ 'krb5_renew_interval',
+ 'krb5_use_fast',
+ 'krb5_fast_principal',
+ 'krb5_fast_use_anonymous_pkinit',
+ 'krb5_canonicalize',
+ 'krb5_use_enterprise_principal',
+ 'krb5_use_subdomain_realm',
+ 'krb5_use_kdcinfo',
+ 'krb5_map_user'])
+
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Add an LDAP one
+ # LDAP ID providers can also use the krb5_realm
+ domain.add_provider('ldap', 'id')
+
+ # Set the krb5_realm option and the ldap_uri option
+ domain.set_option('krb5_realm', 'EXAMPLE.COM')
+ domain.set_option('ldap_uri', 'ldap://ldap.example.com')
+
+ self.assertEqual(domain.get_option('krb5_realm'),
+ 'EXAMPLE.COM')
+ self.assertEqual(domain.get_option('ldap_uri'),
+ 'ldap://ldap.example.com')
+
+ # Remove the LDAP provider and verify that krb5_realm remains
+ domain.remove_provider('id')
+ self.assertEqual(domain.get_option('krb5_realm'),
+ 'EXAMPLE.COM')
+ self.assertFalse('ldap_uri' in domain.options)
+
+ # Remove the auth domain and verify that the options
+ # revert to the backup_list
+ domain.remove_provider('auth')
+ self.assertFalse('auth_provider' in domain.options)
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in backup_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in backup_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Ensure that the krb5_realm option is now gone
+ self.assertFalse('krb5_realm' in domain.options)
+
+ # Test removing nonexistent provider - Real
+ domain.remove_provider('id')
+ self.assertFalse('id_provider' in domain.options)
+
+ # Test removing nonexistent provider - Bad backend type
+ # Should pass without complaint
+ domain.remove_provider('id')
+ self.assertFalse('id_provider' in domain.options)
+
+ # Test removing nonexistent provider - Bad provider type
+ # Should pass without complaint
+ domain.remove_provider('nosuchprovider')
+ self.assertFalse('nosuchprovider_provider' in domain.options)
+
+ def testGetOption(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Negative Test - Try to get valid option that is not set
+ self.assertRaises(SSSDConfig.NoOptionError, domain.get_option, 'max_id')
+
+ # Positive Test - Set the above option and get it
+ domain.set_option('max_id', 10000)
+ self.assertEqual(domain.get_option('max_id'), 10000)
+
+ # Negative Test - Try yo get invalid option
+ self.assertRaises(SSSDConfig.NoOptionError, domain.get_option, 'nosuchoption')
+
+ def testSetOption(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Positive Test
+ domain.set_option('max_id', 10000)
+ self.assertEqual(domain.get_option('max_id'), 10000)
+
+ # Positive Test - Remove option if value is None
+ domain.set_option('max_id', None)
+ self.assertTrue('max_id' not in domain.get_all_options().keys())
+
+ # Negative Test - invalid option
+ self.assertRaises(SSSDConfig.NoOptionError, domain.set_option, 'nosuchoption', 1)
+
+ # Negative Test - incorrect type
+ self.assertRaises(TypeError, domain.set_option, 'max_id', 'a string')
+
+ # Positive Test - Coax options to appropriate type
+ domain.set_option('max_id', '10000')
+ self.assertEqual(domain.get_option('max_id'), 10000)
+
+ domain.set_option('max_id', 30.2)
+ self.assertEqual(domain.get_option('max_id'), 30)
+
+ def testRemoveOption(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Positive test - Remove unset but valid option
+ self.assertFalse('max_id' in domain.get_all_options().keys())
+ domain.remove_option('max_id')
+ self.assertFalse('max_id' in domain.get_all_options().keys())
+
+ # Positive test - Remove unset and unknown option
+ self.assertFalse('nosuchoption' in domain.get_all_options().keys())
+ domain.remove_option('nosuchoption')
+ self.assertFalse('nosuchoption' in domain.get_all_options().keys())
+
+ def testSetName(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Positive test - Change the name once
+ domain.set_name('sssd2')
+ self.assertEqual(domain.get_name(), 'sssd2')
+ self.assertEqual(domain.oldname, 'sssd')
+
+ # Positive test - Change the name a second time
+ domain.set_name('sssd3')
+ self.assertEqual(domain.get_name(), 'sssd3')
+ self.assertEqual(domain.oldname, 'sssd')
+
+ # Negative test - try setting the name to a non-string
+ self.assertRaises(TypeError,
+ domain.set_name, 4)
+
+
+class SSSDConfigTestSSSDConfig(unittest.TestCase):
+ def setUp(self):
+ self.tmp_dir = create_temp_dir()
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp_dir)
+
+ def testInit(self):
+ # Positive test
+ SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - No Such File
+ self.assertRaises(IOError,
+ SSSDConfig.SSSDConfig, "nosuchfile.api.conf", srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Schema is not parsable
+ self.assertRaises(SSSDConfig.ParsingError,
+ SSSDConfig.SSSDConfig, srcdir + "/testconfigs/noparse.api.conf", srcdir + "/etc/sssd.api.d")
+
+ def testImportConfig(self):
+ # Positive Test
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Verify that all sections were imported
+ control_list = [
+ 'sssd',
+ 'nss',
+ 'pam',
+ 'sudo',
+ 'domain/PROXY',
+ 'domain/IPA',
+ 'domain/LDAP',
+ 'domain/INVALIDPROVIDER',
+ 'domain/INVALIDOPTION',
+ ]
+
+ for section in control_list:
+ self.assertTrue(sssdconfig.has_section(section),
+ "Section [%s] missing" %
+ section)
+ for section in sssdconfig.sections():
+ self.assertTrue(section['name'] in control_list)
+
+ # Verify that all options were imported for a section
+ control_list = [
+ 'services',
+ 'reconnection_retries',
+ 'domains',
+ 'debug_timestamps',
+ 'config_file_version']
+
+ for option in control_list:
+ self.assertTrue(sssdconfig.has_option('sssd', option),
+ "Option [%s] missing from [sssd]" %
+ option)
+ for option in sssdconfig.options('sssd'):
+ if option['type'] in ('empty', 'comment'):
+ continue
+ self.assertTrue(option['name'] in control_list,
+ "Option [%s] unexpectedly found" %
+ option)
+
+ # TODO: Check the types and values of the settings
+
+ # Negative Test - Missing config file
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ self.assertRaises(IOError, sssdconfig.import_config, "nosuchfile.conf")
+
+ # Negative Test - Invalid config file
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ self.assertRaises(SSSDConfig.ParsingError, sssdconfig.import_config, srcdir + "/testconfigs/sssd-invalid.conf")
+
+ # Negative Test - Invalid config file version
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ self.assertRaises(SSSDConfig.ParsingError,
+ sssdconfig.import_config,
+ srcdir + "/testconfigs/sssd-badversion.conf")
+
+ # Negative Test - Already initialized
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+ self.assertRaises(SSSDConfig.AlreadyInitializedError,
+ sssdconfig.import_config, srcdir + "/testconfigs/sssd-valid.conf")
+
+ def testImportConfigNoVersion(self):
+ # Positive Test
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(
+ srcdir + "/testconfigs/sssd-noversion.conf"
+ )
+
+ # Validate services
+ services = sssdconfig.list_services()
+ self.assertTrue('sssd' in services)
+ self.assertTrue('nss' in services)
+ self.assertTrue('pam' in services)
+ self.assertTrue('dp' in services)
+
+ # Verify service attributes
+ sssd_service = sssdconfig.get_service('sssd')
+ service_opts = sssd_service.list_options()
+
+ self.assertTrue('services' in service_opts.keys())
+ service_list = sssd_service.get_option('services')
+ self.assertTrue('nss' in service_list)
+ self.assertTrue('pam' in service_list)
+ self.assertTrue('reconnection_retries' in service_opts)
+
+ # Validate domain list
+ domains = sssdconfig.list_domains()
+ self.assertTrue('LDAP' in domains)
+ self.assertTrue('PROXY' in domains)
+ self.assertTrue('IPA' in domains)
+
+ # Verify domain attributes
+ ipa_domain = sssdconfig.get_domain('IPA')
+ domain_opts = ipa_domain.list_options()
+ self.assertTrue('debug_level' in domain_opts.keys())
+ self.assertTrue('id_provider' in domain_opts.keys())
+ self.assertTrue('auth_provider' in domain_opts.keys())
+
+ # Verify domain attributes
+ proxy_domain = sssdconfig.get_domain('PROXY')
+ domain_opts = proxy_domain.list_options()
+ self.assertTrue('debug_level' in domain_opts.keys())
+ self.assertTrue('id_provider' in domain_opts.keys())
+ self.assertTrue('auth_provider' in domain_opts.keys())
+
+ # Verify domain attributes
+ ldap_domain = sssdconfig.get_domain('LDAP')
+ domain_opts = ldap_domain.list_options()
+ self.assertTrue('debug_level' in domain_opts.keys())
+ self.assertTrue('id_provider' in domain_opts.keys())
+ self.assertTrue('auth_provider' in domain_opts.keys())
+
+ domain_control_list = [
+ 'cache_credentials',
+ 'id_provider',
+ 'auth_provider',
+ 'access_provider',
+ 'autofs_provider',
+ 'chpass_provider',
+ 'sudo_provider',
+ 'subdomains_provider',
+ 'resolver_provider',
+ 'default_shell',
+ 'fallback_homedir',
+ 'cache_credentials',
+ 'use_fully_qualified_names',
+ ]
+
+ ad_domain = sssdconfig.get_domain("ad.example.com")
+
+ for option in ad_domain.get_all_options():
+ self.assertTrue(option in domain_control_list)
+
+ negative_domain_control_list = [
+ 'ad_server',
+ 'ldap_id_mapping',
+ 'ldap_sasl_authid',
+ 'selinux_provider',
+ 'hostid_provider',
+ 'session_provider',
+ ]
+
+ for option in ad_domain.get_all_options():
+ self.assertFalse(option in negative_domain_control_list)
+
+ def testNewConfig(self):
+ # Positive Test
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.new_config()
+
+ # Check that the defaults were set
+ control_list = [
+ 'sssd',
+ 'nss',
+ 'pam',
+ 'sudo',
+ 'autofs',
+ 'ssh',
+ 'pac',
+ 'ifp',
+ 'session_recording']
+ for section in control_list:
+ self.assertTrue(sssdconfig.has_section(section),
+ "Section [%s] missing" %
+ section)
+ for section in sssdconfig.sections():
+ self.assertTrue(section['name'] in control_list)
+
+ control_list = ['services']
+ for option in control_list:
+ self.assertTrue(sssdconfig.has_option('sssd', option),
+ "Option [%s] missing from [sssd]" %
+ option)
+ for option in sssdconfig.options('sssd'):
+ if option['type'] in ('empty', 'comment'):
+ continue
+ self.assertTrue(option['name'] in control_list,
+ "Option [%s] unexpectedly found" %
+ option)
+
+ # Negative Test - Already Initialized
+ self.assertRaises(SSSDConfig.AlreadyInitializedError, sssdconfig.new_config)
+
+ def testWrite(self):
+ # TODO Write tests to compare output files
+ pass
+
+ def testListActiveServices(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not Initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_active_services)
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ control_list = [
+ 'nss',
+ 'pam']
+ active_services = sssdconfig.list_active_services()
+ self.assertTrue(isinstance(active_services, list))
+
+ for service in control_list:
+ self.assertTrue(service in active_services,
+ "Service [%s] missing" %
+ service)
+ for service in active_services:
+ self.assertTrue(service in control_list,
+ "Service [%s] unexpectedly found" %
+ service)
+
+ def testListInactiveServices(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not Initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_inactive_services)
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ control_list = [
+ 'sssd',
+ 'sudo']
+ inactive_services = sssdconfig.list_inactive_services()
+
+ for service in control_list:
+ self.assertTrue(service in inactive_services,
+ "Service [%s] missing" %
+ service)
+ for service in inactive_services:
+ self.assertTrue(service in control_list,
+ "Service [%s] unexpectedly found" %
+ service)
+
+ def testListServices(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - sssdconfig not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_services)
+
+ sssdconfig.new_config()
+
+ control_list = [
+ 'sssd',
+ 'pam',
+ 'nss',
+ 'sudo',
+ 'autofs',
+ 'ssh',
+ 'pac',
+ 'ifp',
+ 'session_recording']
+ service_list = sssdconfig.list_services()
+ for service in control_list:
+ self.assertTrue(service in service_list,
+ "Service [%s] missing" %
+ service)
+ for service in service_list:
+ self.assertTrue(service in control_list,
+ "Service [%s] unexpectedly found" %
+ service)
+
+ def testGetService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.get_service, 'sssd')
+
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ service = sssdconfig.get_service('sssd')
+ self.assertTrue(isinstance(service, SSSDConfig.SSSDService))
+
+ # Verify the contents of this service
+ self.assertEqual(type(service.get_option('debug_timestamps')), bool)
+ self.assertFalse(service.get_option('debug_timestamps'))
+
+ # Negative Test - No such service
+ self.assertRaises(SSSDConfig.NoServiceError, sssdconfig.get_service, 'nosuchservice')
+
+ # Positive test - Service with invalid option loads
+ # but ignores the invalid option
+ service = sssdconfig.get_service('pam')
+ self.assertFalse('nosuchoption' in service.options)
+
+ def testNewService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.new_service, 'sssd')
+
+ sssdconfig.new_config()
+
+ # Positive Test
+ # First need to remove the existing service
+ sssdconfig.delete_service('sssd')
+ service = sssdconfig.new_service('sssd')
+ self.assertTrue(service.get_name() in sssdconfig.list_services())
+
+ # TODO: check that the values of this new service
+ # are set to the defaults from the schema
+
+ def testDeleteService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.delete_service, 'sssd')
+
+ sssdconfig.new_config()
+
+ # Positive Test
+ sssdconfig.delete_service('sssd')
+
+ def testSaveService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ new_service = SSSDConfig.SSSDService('sssd', sssdconfig.schema)
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.save_service, new_service)
+
+ # Positive Test
+ sssdconfig.new_config()
+ sssdconfig.save_service(new_service)
+
+ # TODO: check that all entries were saved correctly (change a few)
+
+ # Negative Test - Type Error
+ self.assertRaises(TypeError, sssdconfig.save_service, self)
+
+ def testActivateService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ service_name = 'sudo'
+
+ # Negative test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError,
+ sssdconfig.activate_service, service_name)
+
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Positive test - Activate an inactive service
+ self.assertTrue(service_name in sssdconfig.list_services())
+ self.assertFalse(service_name in sssdconfig.list_active_services())
+ self.assertTrue(service_name in sssdconfig.list_inactive_services())
+
+ sssdconfig.activate_service(service_name)
+ self.assertTrue(service_name in sssdconfig.list_services())
+ self.assertTrue(service_name in sssdconfig.list_active_services())
+ self.assertFalse(service_name in sssdconfig.list_inactive_services())
+
+ # Positive test - Activate an active service
+ # This should succeed
+ sssdconfig.activate_service(service_name)
+ self.assertTrue(service_name in sssdconfig.list_services())
+ self.assertTrue(service_name in sssdconfig.list_active_services())
+ self.assertFalse(service_name in sssdconfig.list_inactive_services())
+
+ # Negative test - Invalid service name
+ self.assertRaises(SSSDConfig.NoServiceError,
+ sssdconfig.activate_service, 'nosuchservice')
+
+ # Negative test - Invalid service name type
+ self.assertRaises(SSSDConfig.NoServiceError,
+ sssdconfig.activate_service, self)
+
+ def testDeactivateService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ service_name = 'pam'
+
+ # Negative test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError,
+ sssdconfig.activate_service, service_name)
+
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Positive test -Deactivate an active service
+ self.assertTrue(service_name in sssdconfig.list_services())
+ self.assertTrue(service_name in sssdconfig.list_active_services())
+ self.assertFalse(service_name in sssdconfig.list_inactive_services())
+
+ sssdconfig.deactivate_service(service_name)
+ self.assertTrue(service_name in sssdconfig.list_services())
+ self.assertFalse(service_name in sssdconfig.list_active_services())
+ self.assertTrue(service_name in sssdconfig.list_inactive_services())
+
+ # Positive test - Deactivate an inactive service
+ # This should succeed
+ sssdconfig.deactivate_service(service_name)
+ self.assertTrue(service_name in sssdconfig.list_services())
+ self.assertFalse(service_name in sssdconfig.list_active_services())
+ self.assertTrue(service_name in sssdconfig.list_inactive_services())
+
+ # Negative test - Invalid service name
+ self.assertRaises(SSSDConfig.NoServiceError,
+ sssdconfig.activate_service, 'nosuchservice')
+
+ # Negative test - Invalid service name type
+ self.assertRaises(SSSDConfig.NoServiceError,
+ sssdconfig.activate_service, self)
+
+ def testListActiveDomains(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not Initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_active_domains)
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ control_list = [
+ 'IPA']
+ active_domains = sssdconfig.list_active_domains()
+ self.assertTrue(isinstance(active_domains, list))
+
+ for domain in control_list:
+ self.assertTrue(domain in active_domains,
+ "Domain [%s] missing" %
+ domain)
+ for domain in active_domains:
+ self.assertTrue(domain in control_list,
+ "Domain [%s] unexpectedly found" %
+ domain)
+
+ def testListInactiveDomains(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not Initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_inactive_domains)
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ control_list = [
+ 'PROXY',
+ 'LDAP',
+ 'INVALIDPROVIDER',
+ 'INVALIDOPTION',
+ ]
+ inactive_domains = sssdconfig.list_inactive_domains()
+
+ for domain in control_list:
+ self.assertTrue(domain in inactive_domains,
+ "Domain [%s] missing" %
+ domain)
+ for domain in inactive_domains:
+ self.assertTrue(domain in control_list,
+ "Domain [%s] unexpectedly found" %
+ domain)
+
+ def testListDomains(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not Initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_domains)
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ control_list = [
+ 'IPA',
+ 'PROXY',
+ 'LDAP',
+ 'INVALIDPROVIDER',
+ 'INVALIDOPTION',
+ ]
+ domains = sssdconfig.list_domains()
+
+ for domain in control_list:
+ self.assertTrue(domain in domains,
+ "Domain [%s] missing" %
+ domain)
+ for domain in domains:
+ self.assertTrue(domain in control_list,
+ "Domain [%s] unexpectedly found" %
+ domain)
+
+ def testListWithInvalidDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not Initialized
+ self.assertRaises(SSSDConfig.NotInitializedError,
+ sssdconfig.list_domains)
+
+ # Positive Test
+ sssdconfig.import_config(
+ srcdir + '/testconfigs/sssd-nonexisting-services-domains.conf'
+ )
+
+ domains = sssdconfig.list_active_domains()
+ self.assertTrue("active" in domains and len(domains) == 1,
+ "domain 'active' not found among active domains")
+
+ domains = sssdconfig.list_inactive_domains()
+ self.assertTrue("inactive" in domains and len(domains) == 1,
+ "domain 'inactive' not found among inactive domains")
+
+ services = sssdconfig.list_active_services()
+ self.assertTrue("nss" in services and len(services) == 1,
+ "service 'nss' not found among active services")
+
+ services = sssdconfig.list_inactive_services()
+ self.assertTrue(len(services) == 2,
+ "unexpected count of inactive services")
+ for service in ("sssd", "pam"):
+ self.assertTrue(service in services,
+ "service '%s' not found among inactive services"
+ % service)
+
+ def testGetDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.get_domain, 'sssd')
+
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ domain = sssdconfig.get_domain('IPA')
+ self.assertTrue(isinstance(domain, SSSDConfig.SSSDDomain))
+ self.assertTrue(domain.active)
+
+ domain = sssdconfig.get_domain('LDAP')
+ self.assertTrue(isinstance(domain, SSSDConfig.SSSDDomain))
+ self.assertFalse(domain.active)
+
+ # TODO verify the contents of this domain
+ self.assertTrue(domain.get_option('ldap_id_use_start_tls'))
+ self.assertTrue(domain.get_option('ldap_sudo_include_regexp'))
+ self.assertTrue(domain.get_option('ldap_autofs_map_master_name'))
+
+ # Negative Test - No such domain
+ self.assertRaises(SSSDConfig.NoDomainError, sssdconfig.get_domain, 'nosuchdomain')
+
+ # Positive Test - Domain with unknown provider
+ # Expected result: Domain is imported, but does not contain the
+ # unknown provider entry
+ domain = sssdconfig.get_domain('INVALIDPROVIDER')
+ self.assertFalse('chpass_provider' in domain.options)
+
+ # Positive Test - Domain with unknown option
+ # Expected result: Domain is imported, but does not contain the
+ # unknown option entry
+ domain = sssdconfig.get_domain('INVALIDOPTION')
+ self.assertFalse('nosuchoption' in domain.options)
+
+ def testNewDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.new_domain, 'example.com')
+
+ sssdconfig.new_config()
+
+ # Positive Test
+ domain = sssdconfig.new_domain('example.com')
+ self.assertTrue(isinstance(domain, SSSDConfig.SSSDDomain))
+ self.assertTrue(domain.get_name() in sssdconfig.list_domains())
+ self.assertTrue(domain.get_name() in sssdconfig.list_inactive_domains())
+
+ # TODO: check that the values of this new domain
+ # are set to the defaults from the schema
+
+ def testDeleteDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.delete_domain, 'IPA')
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ self.assertTrue('IPA' in sssdconfig.list_domains())
+ self.assertTrue('IPA' in sssdconfig.list_active_domains())
+ self.assertTrue(sssdconfig.has_section('domain/IPA'))
+ sssdconfig.delete_domain('IPA')
+ self.assertFalse('IPA' in sssdconfig.list_domains())
+ self.assertFalse('IPA' in sssdconfig.list_active_domains())
+ self.assertFalse(sssdconfig.has_section('domain/IPA'))
+
+ def testSaveDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.save_domain, 'IPA')
+
+ # Positive Test
+ sssdconfig.new_config()
+ domain = sssdconfig.new_domain('example.com')
+ domain.add_provider('ldap', 'id')
+ domain.set_option('ldap_uri', 'ldap://ldap.example.com')
+ domain.set_active(True)
+ sssdconfig.save_domain(domain)
+
+ self.assertTrue('example.com' in sssdconfig.list_domains())
+ self.assertTrue('example.com' in sssdconfig.list_active_domains())
+ self.assertEqual(sssdconfig.get('domain/example.com', 'ldap_uri'),
+ 'ldap://ldap.example.com')
+
+ # Negative Test - Type Error
+ self.assertRaises(TypeError, sssdconfig.save_domain, self)
+
+ # Positive test - Change the domain name and save it
+ domain.set_name('example.com2')
+ self.assertEqual(domain.name, 'example.com2')
+ self.assertEqual(domain.oldname, 'example.com')
+ sssdconfig.save_domain(domain)
+
+ self.assertTrue('example.com2' in sssdconfig.list_domains())
+ self.assertTrue('example.com2' in sssdconfig.list_active_domains())
+ self.assertTrue(sssdconfig.has_section('domain/example.com2'))
+ self.assertEqual(sssdconfig.get('domain/example.com2',
+ 'ldap_uri'),
+ 'ldap://ldap.example.com')
+ self.assertFalse('example.com' in sssdconfig.list_domains())
+ self.assertFalse('example.com' in sssdconfig.list_active_domains())
+ self.assertFalse('example.com' in sssdconfig.list_inactive_domains())
+ self.assertFalse(sssdconfig.has_section('domain/example.com'))
+ self.assertEqual(domain.oldname, None)
+
+ # Positive test - Set the domain inactive and save it
+ activelist = sssdconfig.list_active_domains()
+ inactivelist = sssdconfig.list_inactive_domains()
+
+ domain.set_active(False)
+ sssdconfig.save_domain(domain)
+
+ self.assertFalse('example.com2' in sssdconfig.list_active_domains())
+ self.assertTrue('example.com2' in sssdconfig.list_inactive_domains())
+
+ self.assertEqual(len(sssdconfig.list_active_domains()),
+ len(activelist) - 1)
+ self.assertEqual(len(sssdconfig.list_inactive_domains()),
+ len(inactivelist) + 1)
+
+ # Positive test - Set the domain active and save it
+ activelist = sssdconfig.list_active_domains()
+ inactivelist = sssdconfig.list_inactive_domains()
+ domain.set_active(True)
+ sssdconfig.save_domain(domain)
+
+ self.assertTrue('example.com2' in sssdconfig.list_active_domains())
+ self.assertFalse('example.com2' in sssdconfig.list_inactive_domains())
+
+ self.assertEqual(len(sssdconfig.list_active_domains()),
+ len(activelist) + 1)
+ self.assertEqual(len(sssdconfig.list_inactive_domains()),
+ len(inactivelist) - 1)
+
+ # Positive test - Set the domain inactive and save it
+ activelist = sssdconfig.list_active_domains()
+ inactivelist = sssdconfig.list_inactive_domains()
+
+ sssdconfig.deactivate_domain(domain.get_name())
+
+ self.assertFalse('example.com2' in sssdconfig.list_active_domains())
+ self.assertTrue('example.com2' in sssdconfig.list_inactive_domains())
+
+ self.assertEqual(len(sssdconfig.list_active_domains()),
+ len(activelist) - 1)
+ self.assertEqual(len(sssdconfig.list_inactive_domains()),
+ len(inactivelist) + 1)
+
+ # Positive test - Set the domain active and save it
+ activelist = sssdconfig.list_active_domains()
+ inactivelist = sssdconfig.list_inactive_domains()
+
+ sssdconfig.activate_domain(domain.get_name())
+
+ self.assertTrue('example.com2' in sssdconfig.list_active_domains())
+ self.assertFalse('example.com2' in sssdconfig.list_inactive_domains())
+
+ self.assertEqual(len(sssdconfig.list_active_domains()),
+ len(activelist) + 1)
+ self.assertEqual(len(sssdconfig.list_inactive_domains()),
+ len(inactivelist) - 1)
+
+ # Positive test - Ensure that saved domains retain values
+ domain.set_option('ldap_krb5_init_creds', True)
+ domain.set_option('ldap_id_use_start_tls', False)
+ domain.set_option('ldap_user_search_base',
+ 'cn=accounts, dc=example, dc=com')
+ self.assertTrue(domain.get_option('ldap_krb5_init_creds'))
+ self.assertFalse(domain.get_option('ldap_id_use_start_tls'))
+ self.assertEqual(domain.get_option('ldap_user_search_base'),
+ 'cn=accounts, dc=example, dc=com')
+
+ sssdconfig.save_domain(domain)
+
+ of = self.tmp_dir + '/testSaveDomain.out'
+
+ # Ensure the output file doesn't exist
+ try:
+ os.unlink(of)
+ except OSError:
+ pass
+
+ # Write out the file
+ sssdconfig.write(of)
+
+ # Verify that the output file has the correct permissions
+ mode = os.stat(of)[ST_MODE]
+
+ # Output files should not be readable or writable by
+ # non-owners, and should not be executable by anyone
+ self.assertFalse(S_IMODE(mode) & 0o177)
+
+ # Remove the output file
+ os.unlink(of)
+
+ domain2 = sssdconfig.get_domain('example.com2')
+ self.assertTrue(domain2.get_option('ldap_krb5_init_creds'))
+ self.assertFalse(domain2.get_option('ldap_id_use_start_tls'))
+
+ def testActivateDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ domain_name = 'PROXY'
+
+ # Negative test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError,
+ sssdconfig.activate_domain, domain_name)
+
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Positive test - Activate an inactive domain
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertFalse(domain_name in sssdconfig.list_active_domains())
+ self.assertTrue(domain_name in sssdconfig.list_inactive_domains())
+
+ sssdconfig.activate_domain('PROXY')
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertTrue(domain_name in sssdconfig.list_active_domains())
+ self.assertFalse(domain_name in sssdconfig.list_inactive_domains())
+
+ # Positive test - Activate an active domain
+ # This should succeed
+ sssdconfig.activate_domain('PROXY')
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertTrue(domain_name in sssdconfig.list_active_domains())
+ self.assertFalse(domain_name in sssdconfig.list_inactive_domains())
+
+ # Negative test - Invalid domain name
+ self.assertRaises(SSSDConfig.NoDomainError,
+ sssdconfig.activate_domain, 'nosuchdomain')
+
+ # Negative test - Invalid domain name type
+ self.assertRaises(SSSDConfig.NoDomainError,
+ sssdconfig.activate_domain, self)
+
+ def testDeactivateDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ domain_name = 'IPA'
+
+ # Negative test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError,
+ sssdconfig.activate_domain, domain_name)
+
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Positive test -Deactivate an active domain
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertTrue(domain_name in sssdconfig.list_active_domains())
+ self.assertFalse(domain_name in sssdconfig.list_inactive_domains())
+
+ sssdconfig.deactivate_domain(domain_name)
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertFalse(domain_name in sssdconfig.list_active_domains())
+ self.assertTrue(domain_name in sssdconfig.list_inactive_domains())
+
+ # Positive test - Deactivate an inactive domain
+ # This should succeed
+ sssdconfig.deactivate_domain(domain_name)
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertFalse(domain_name in sssdconfig.list_active_domains())
+ self.assertTrue(domain_name in sssdconfig.list_inactive_domains())
+
+ # Negative test - Invalid domain name
+ self.assertRaises(SSSDConfig.NoDomainError,
+ sssdconfig.activate_domain, 'nosuchdomain')
+
+ # Negative test - Invalid domain name type
+ self.assertRaises(SSSDConfig.NoDomainError,
+ sssdconfig.activate_domain, self)
+
+ def testParse(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ with open(srcdir + "/testconfigs/sssd-test-parse.conf", "r") as f:
+ data = sssdconfig.parse(f)
+
+ self.assertEqual(len(data), 4)
+ self.assertEqual(data[-1], {'type': "section",
+ 'name': "nss",
+ 'value': [{'type': 'option',
+ 'name': 'debug_level',
+ 'value': '1'},
+ {'type': 'empty',
+ 'name': 'empty'}]})
+
+ with open(srcdir + "/testconfigs/sssd-valid.conf", "r") as f:
+ data = sssdconfig.parse(f)
+
+ self.assertEqual(len(data), 9)
+ self.assertEqual(data[-1], {'name': "sudo",
+ 'type': "section",
+ 'value': [{'type': 'option',
+ 'name': 'debug_level',
+ 'value': '0xfC10'}]})
+
+ def testEnabledOption(self):
+ """Test the new enabled option."""
+ # Positive Test
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-enabled-option.conf")
+
+ # Verify that all sections were imported
+ control_list = [
+ 'nss',
+ 'sssd',
+ 'pam',
+ 'domain/enabled_1',
+ 'domain/enabled_2',
+ 'domain/enabled_3',
+ 'domain/disabled_1',
+ 'domain/disabled_2',
+ 'domain/disabled_3',
+ ]
+
+ for section in control_list:
+ self.assertTrue(sssdconfig.has_section(section),
+ "Section [%s] missing" %
+ section)
+ for section in sssdconfig.sections():
+ self.assertTrue(section['name'] in control_list)
+
+ # Verify that all options were imported for [sssd] section
+ control_list = [
+ 'services',
+ 'reconnection_retries',
+ 'domains',
+ 'config_file_version',
+ 'debug_timestamps']
+
+ for option in control_list:
+ self.assertTrue(sssdconfig.has_option('sssd', option),
+ "Option [%s] missing from [sssd]" %
+ option)
+ for option in sssdconfig.options('sssd'):
+ if option['type'] in ('empty', 'comment'):
+ continue
+ self.assertTrue(option['name'] in control_list,
+ "Option [%s] unexpectedly found" %
+ option)
+
+ # Verify enabled domains
+ control_list = [
+ 'enabled_1',
+ 'enabled_2',
+ 'enabled_3']
+
+ if (sssdconfig.has_option('sssd', 'domains')):
+ sssd_domains = striplist(sssdconfig.get('sssd', 'domains').split(','))
+ domain_dict = dict.fromkeys(sssd_domains)
+ if '' in domain_dict:
+ del domain_dict['']
+ sssd_domains = list(domain_dict)
+ else:
+ sssd_domains = []
+
+ for domain in sssdconfig.list_active_domains():
+ self.assertTrue(domain in control_list,
+ "Domain [domain/%s] should be disabled" % domain)
+ for domain in control_list:
+ self.assertTrue(domain in sssdconfig.list_active_domains(),
+ "Domain [domain/%s] should be enabled" % domain)
+
+ # Verify disabled domains
+ control_list = [
+ 'disabled_1',
+ 'disabled_2',
+ 'disabled_3']
+
+ for domain in sssdconfig.list_inactive_domains():
+ self.assertTrue(domain in control_list,
+ "Domain [domain/%s] should be enabled" % domain)
+ for domain in control_list:
+ self.assertTrue(domain in sssdconfig.list_inactive_domains(),
+ "Domain [domain/%s] should be disabled" % domain)
+
+
+if __name__ == "__main__":
+ error = 0
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestSSSDService)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x1
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestSSSDDomain)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x2
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestSSSDConfig)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x4
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestValid)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x8
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestInvalid)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x10
+
+ sys.exit(error)
diff --git a/src/config/SSSDConfigTest.py2.sh b/src/config/SSSDConfigTest.py2.sh
new file mode 100755
index 0000000..7bbd82a
--- /dev/null
+++ b/src/config/SSSDConfigTest.py2.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+SCRIPT=$(readlink -f "$0")
+SCRIPT_PATH=$(dirname "$SCRIPT")
+exec python2 $SCRIPT_PATH/SSSDConfigTest.py
diff --git a/src/config/SSSDConfigTest.py3.sh b/src/config/SSSDConfigTest.py3.sh
new file mode 100755
index 0000000..89b9f07
--- /dev/null
+++ b/src/config/SSSDConfigTest.py3.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+SCRIPT=$(readlink -f "$0")
+SCRIPT_PATH=$(dirname "$SCRIPT")
+exec python3 $SCRIPT_PATH/SSSDConfigTest.py
diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
new file mode 100644
index 0000000..61cbdaf
--- /dev/null
+++ b/src/config/cfg_rules.ini
@@ -0,0 +1,840 @@
+[rule/allowed_sections]
+validator = ini_allowed_sections
+section = sssd
+section = nss
+section = pam
+section = sudo
+section = autofs
+section = ssh
+section = pac
+section = ifp
+section = kcm
+section = session_recording
+section_re = ^prompting/password$
+section_re = ^prompting/password/[^/\@]\+$
+section_re = ^prompting/2fa$
+section_re = ^prompting/2fa/[^/\@]\+$
+section_re = ^prompting/passkey$
+section_re = ^prompting/passkey/[^/\@]\+$
+section_re = ^domain/[^/\@]\+$
+section_re = ^domain/[^/\@]\+/[^/\@]\+$
+section_re = ^application/[^/\@]\+$
+section_re = ^certmap/[^/\@]\+/[^/\@]\+$
+
+
+[rule/allowed_sssd_options]
+validator = ini_allowed_options
+section_re = ^sssd$
+
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+
+# Monitor service
+option = services
+option = domains
+option = timeout
+option = re_expression
+option = full_name_format
+option = krb5_rcache_dir
+option = user
+option = default_domain_suffix
+option = certificate_verification
+option = override_space
+option = config_file_version
+option = disable_netlink
+option = enable_files_domain
+option = domain_resolution_order
+option = try_inotify
+option = monitor_resolv_conf
+option = implicit_pac_responder
+option = core_dumpable
+option = passkey_verification
+
+[rule/allowed_nss_options]
+validator = ini_allowed_options
+section_re = ^nss$
+
+option = timeout
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+option = responder_idle_timeout
+option = cache_first
+
+# Name service
+option = user_attributes
+option = enum_cache_timeout
+option = entry_cache_nowait_percentage
+option = entry_negative_timeout
+option = local_negative_timeout
+option = filter_users
+option = filter_groups
+option = filter_users_in_groups
+option = pwfield
+option = override_homedir
+option = fallback_homedir
+option = homedir_substring
+option = override_shell
+option = allowed_shells
+option = vetoed_shells
+option = shell_fallback
+option = default_shell
+option = get_domains_timeout
+option = memcache_timeout
+option = memcache_size_passwd
+option = memcache_size_group
+option = memcache_size_initgroups
+
+[rule/allowed_pam_options]
+validator = ini_allowed_options
+section_re = ^pam$
+
+option = timeout
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+option = responder_idle_timeout
+option = cache_first
+
+# Authentication service
+option = offline_credentials_expiration
+option = offline_failed_login_attempts
+option = offline_failed_login_delay
+option = pam_verbosity
+option = pam_response_filter
+option = pam_id_timeout
+option = pam_pwd_expiration_warning
+option = get_domains_timeout
+option = pam_trusted_users
+option = pam_public_domains
+option = pam_account_expired_message
+option = pam_account_locked_message
+option = pam_cert_auth
+option = pam_cert_db_path
+option = pam_cert_verification
+option = p11_child_timeout
+option = pam_app_services
+option = pam_p11_allowed_services
+option = p11_wait_for_card_timeout
+option = p11_uri
+option = pam_initgroups_scheme
+option = pam_gssapi_services
+option = pam_gssapi_check_upn
+option = pam_gssapi_indicators_map
+option = pam_passkey_auth
+option = passkey_child_timeout
+option = passkey_debug_libfido2
+
+[rule/allowed_sudo_options]
+validator = ini_allowed_options
+section_re = ^sudo$
+
+option = timeout
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+option = responder_idle_timeout
+option = cache_first
+
+# sudo service
+option = sudo_timed
+option = sudo_inverse_order
+option = sudo_threshold
+
+[rule/allowed_autofs_options]
+validator = ini_allowed_options
+section_re = ^autofs$
+
+option = timeout
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+option = responder_idle_timeout
+option = cache_first
+
+# autofs service
+option = autofs_negative_timeout
+
+[rule/allowed_ssh_options]
+validator = ini_allowed_options
+section_re = ^ssh$
+
+option = timeout
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+option = responder_idle_timeout
+option = cache_first
+
+# ssh service
+option = ssh_hash_known_hosts
+option = ssh_known_hosts_timeout
+option = ca_db
+option = ssh_use_certificate_keys
+option = ssh_use_certificate_matching_rules
+
+[rule/allowed_pac_options]
+validator = ini_allowed_options
+section_re = ^pac$
+
+option = timeout
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+option = responder_idle_timeout
+option = cache_first
+
+# PAC responder
+option = allowed_uids
+option = pac_lifetime
+option = pac_check
+
+[rule/allowed_ifp_options]
+validator = ini_allowed_options
+section_re = ^ifp$
+
+option = timeout
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+option = responder_idle_timeout
+option = cache_first
+
+# InfoPipe responder
+option = allowed_uids
+option = user_attributes
+
+# KCM responder
+[rule/allowed_kcm_options]
+validator = ini_allowed_options
+section_re = ^kcm$
+
+option = timeout
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+option = socket_path
+option = ccache_storage
+option = responder_idle_timeout
+option = max_ccaches
+option = max_uid_ccaches
+option = max_ccache_size
+option = tgt_renewal
+option = tgt_renewal_inherit
+option = krb5_lifetime
+option = krb5_renewable_lifetime
+option = krb5_renew_interval
+option = krb5_validate
+option = krb5_canonicalize
+option = krb5_auth_timeout
+
+# Session recording
+[rule/allowed_session_recording_options]
+validator = ini_allowed_options
+section_re = ^session_recording$
+
+option = scope
+option = users
+option = groups
+option = exclude_users
+option = exclude_groups
+
+# Prompting during authentication
+[rule/allowed_prompting_password_options]
+validator = ini_allowed_options
+section_re = ^prompting/password$
+
+option = password_prompt
+
+[rule/allowed_prompting_2fa_options]
+validator = ini_allowed_options
+section_re = ^prompting/2fa$
+
+option = single_prompt
+option = first_prompt
+option = second_prompt
+
+[rule/allowed_prompting_passkey_options]
+validator = ini_allowed_options
+section_re = ^prompting/passkey$
+
+option = interactive
+option = interactive_prompt
+option = touch
+option = touch_prompt
+
+[rule/allowed_prompting_password_subsec_options]
+validator = ini_allowed_options
+section_re = ^prompting/password/[^/\@]\+$
+
+option = password_prompt
+
+[rule/allowed_prompting_2fa_subsec_options]
+validator = ini_allowed_options
+section_re = ^prompting/2fa/[^/\@]\+$
+
+option = single_prompt
+option = first_prompt
+option = second_prompt
+
+[rule/allowed_prompting_passkey_subsec_options]
+validator = ini_allowed_options
+section_re = ^prompting/passkey/[^/\@]\+$
+
+option = interactive
+option = interactive_prompt
+option = touch
+option = touch_prompt
+
+[rule/allowed_domain_options]
+validator = ini_allowed_options
+section_re = ^\(domain\|application\)/[^/]\+$
+
+option = debug
+option = debug_level
+option = debug_timestamps
+option = debug_microseconds
+option = debug_backtrace_enabled
+option = command
+option = reconnection_retries
+option = fd_limit
+option = client_idle_timeout
+option = description
+
+#Available provider types
+option = id_provider
+option = auth_provider
+option = access_provider
+option = chpass_provider
+option = sudo_provider
+option = autofs_provider
+option = hostid_provider
+option = subdomains_provider
+option = selinux_provider
+option = session_provider
+option = resolver_provider
+
+# Options available to all domains
+option = enabled
+option = domain_type
+option = min_id
+option = max_id
+option = timeout
+option = enumerate
+option = subdomain_enumerate
+option = offline_timeout
+option = offline_timeout_max
+option = offline_timeout_random_offset
+option = cache_credentials
+option = cache_credentials_minimal_first_factor_length
+option = use_fully_qualified_names
+option = ignore_group_members
+option = entry_cache_timeout
+option = lookup_family_order
+option = account_cache_expiration
+option = pwd_expiration_warning
+option = filter_users
+option = filter_groups
+option = dns_resolver_server_timeout
+option = dns_resolver_op_timeout
+option = dns_resolver_timeout
+option = dns_resolver_use_search_list
+option = dns_discovery_domain
+option = override_gid
+option = case_sensitive
+option = override_homedir
+option = fallback_homedir
+option = homedir_substring
+option = override_shell
+option = default_shell
+option = description
+option = realmd_tags
+option = subdomain_refresh_interval
+option = subdomain_refresh_interval_offset
+option = subdomain_inherit
+option = subdomain_homedir
+option = cached_auth_timeout
+option = wildcard_limit
+option = full_name_format
+option = re_expression
+option = auto_private_groups
+option = pam_gssapi_services
+option = pam_gssapi_check_upn
+option = pam_gssapi_indicators_map
+option = local_auth_policy
+
+#Entry cache timeouts
+option = entry_cache_user_timeout
+option = entry_cache_group_timeout
+option = entry_cache_netgroup_timeout
+option = entry_cache_service_timeout
+option = entry_cache_autofs_timeout
+option = entry_cache_sudo_timeout
+option = entry_cache_ssh_host_timeout
+option = entry_cache_computer_timeout
+option = entry_cache_resolver_timeout
+option = refresh_expired_interval
+option = refresh_expired_interval_offset
+
+# Dynamic DNS updates
+option = dyndns_update
+option = dyndns_ttl
+option = dyndns_iface
+option = dyndns_refresh_interval
+option = dyndns_refresh_interval_offset
+option = dyndns_update_ptr
+option = dyndns_force_tcp
+option = dyndns_auth
+option = dyndns_auth_ptr
+option = dyndns_server
+
+# files provider specific options
+option = passwd_files
+option = group_files
+option = fallback_to_nss
+
+# proxy provider specific options
+option = proxy_lib_name
+option = proxy_resolver_lib_name
+option = proxy_fast_alias
+option = proxy_pam_target
+option = proxy_max_children
+
+# simple access provider specific options
+option = simple_allow_users
+option = simple_deny_users
+option = simple_allow_groups
+option = simple_deny_groups
+
+# AD provider specific options
+option = ad_access_filter
+option = ad_backup_server
+option = ad_domain
+option = ad_enable_dns_sites
+option = ad_enabled_domains
+option = ad_enable_gc
+option = ad_gpo_access_control
+option = ad_gpo_implicit_deny
+option = ad_gpo_ignore_unreadable
+option = ad_gpo_cache_timeout
+option = ad_gpo_default_right
+option = ad_gpo_map_batch
+option = ad_gpo_map_deny
+option = ad_gpo_map_interactive
+option = ad_gpo_map_network
+option = ad_gpo_map_permit
+option = ad_gpo_map_remote_interactive
+option = ad_gpo_map_service
+option = ad_hostname
+option = ad_machine_account_password_renewal_opts
+option = ad_maximum_machine_account_password_age
+option = ad_server
+option = ad_site
+option = ad_update_samba_machine_account_password
+option = ad_use_ldaps
+option = ad_allow_remote_domain_local_groups
+
+# IPA provider specific options
+option = ipa_access_order
+option = ipa_anchor_uuid
+option = ipa_automount_location
+option = ipa_backup_server
+option = ipa_deskprofile_refresh
+option = ipa_deskprofile_request_interval
+option = ipa_deskprofile_search_base
+option = ipa_subid_ranges_search_base
+option = ipa_domain
+option = ipa_dyndns_iface
+option = ipa_dyndns_ttl
+option = ipa_dyndns_update
+option = ipa_enable_dns_sites
+option = ipa_group_override_object_class
+option = ipa_hbac_refresh
+option = ipa_hbac_search_base
+option = ipa_hbac_support_srchost
+option = ipa_host_fqdn
+option = ipa_hostgroup_memberof
+option = ipa_hostgroup_member
+option = ipa_hostgroup_name
+option = ipa_hostgroup_objectclass
+option = ipa_hostgroup_uuid
+option = ipa_host_member_of
+option = ipa_host_name
+option = ipa_hostname
+option = ipa_host_object_class
+option = ipa_host_search_base
+option = ipa_host_serverhostname
+option = ipa_host_ssh_public_key
+option = ipa_host_uuid
+option = ipa_master_domain_search_base
+option = ipa_netgroup_domain
+option = ipa_netgroup_member_ext_host
+option = ipa_netgroup_member_host
+option = ipa_netgroup_member_of
+option = ipa_netgroup_member
+option = ipa_netgroup_member_user
+option = ipa_netgroup_name
+option = ipa_netgroup_object_class
+option = ipa_netgroup_uuid
+option = ipa_override_object_class
+option = ipa_ranges_search_base
+option = ipa_selinux_refresh
+option = ipa_selinux_usermap_enabled
+option = ipa_selinux_usermap_host_category
+option = ipa_selinux_usermap_member_host
+option = ipa_selinux_usermap_member_user
+option = ipa_selinux_usermap_name
+option = ipa_selinux_usermap_object_class
+option = ipa_selinux_usermap_see_also
+option = ipa_selinux_usermap_selinux_user
+option = ipa_selinux_usermap_user_category
+option = ipa_selinux_usermap_uuid
+option = ipa_server_mode
+option = ipa_server
+option = ipa_subdomains_search_base
+option = ipa_sudocmdgroup_entry_usn
+option = ipa_sudocmdgroup_member
+option = ipa_sudocmdgroup_name
+option = ipa_sudocmdgroup_object_class
+option = ipa_sudocmdgroup_uuid
+option = ipa_sudocmd_memberof
+option = ipa_sudocmd_object_class
+option = ipa_sudocmd_sudoCmd
+option = ipa_sudocmd_uuid
+option = ipa_sudorule_allowcmd
+option = ipa_sudorule_cmdcategory
+option = ipa_sudorule_denycmd
+option = ipa_sudorule_enabled_flag
+option = ipa_sudorule_entry_usn
+option = ipa_sudorule_externaluser
+option = ipa_sudorule_hostcategory
+option = ipa_sudorule_host
+option = ipa_sudorule_name
+option = ipa_sudorule_notafter
+option = ipa_sudorule_notbefore
+option = ipa_sudorule_object_class
+option = ipa_sudorule_option
+option = ipa_sudorule_runasextgroup
+option = ipa_sudorule_runasextusergroup
+option = ipa_sudorule_runasextuser
+option = ipa_sudorule_runasgroupcategory
+option = ipa_sudorule_runasgroup
+option = ipa_sudorule_runasusercategory
+option = ipa_sudorule_sudoorder
+option = ipa_sudorule_usercategory
+option = ipa_sudorule_user
+option = ipa_sudorule_uuid
+option = ipa_user_override_object_class
+option = ipa_view_class
+option = ipa_view_name
+option = ipa_views_search_base
+
+# krb5 provider specific options
+option = krb5_auth_timeout
+option = krb5_backup_kpasswd
+option = krb5_backup_server
+option = krb5_canonicalize
+option = krb5_ccachedir
+option = krb5_ccname_template
+option = krb5_confd_path
+option = krb5_fast_principal
+option = krb5_fast_use_anonymous_pkinit
+option = krb5_kdcinfo_lookahead
+option = krb5_kdcip
+option = krb5_keytab
+option = krb5_kpasswd
+option = krb5_lifetime
+option = krb5_map_user
+option = krb5_realm
+option = krb5_renewable_lifetime
+option = krb5_renew_interval
+option = krb5_server
+option = krb5_store_password_if_offline
+option = krb5_use_enterprise_principal
+option = krb5_use_subdomain_realm
+option = krb5_use_fast
+option = krb5_use_kdcinfo
+option = krb5_validate
+
+# ldap provider specific options
+option = ldap_access_filter
+option = ldap_access_order
+option = ldap_account_expire_policy
+option = ldap_autofs_entry_key
+option = ldap_autofs_entry_object_class
+option = ldap_autofs_entry_value
+option = ldap_autofs_map_master_name
+option = ldap_autofs_map_name
+option = ldap_autofs_map_object_class
+option = ldap_autofs_search_base
+option = ldap_backup_uri
+option = ldap_chpass_backup_uri
+option = ldap_chpass_dns_service_name
+option = ldap_chpass_update_last_change
+option = ldap_chpass_uri
+option = ldap_connection_expire_timeout
+option = ldap_connection_expire_offset
+option = ldap_connection_idle_timeout
+option = ldap_default_authtok
+option = ldap_default_authtok_type
+option = ldap_default_bind_dn
+option = ldap_deref
+option = ldap_deref_threshold
+option = ldap_ignore_unreadable_references
+option = ldap_disable_paging
+option = ldap_disable_range_retrieval
+option = ldap_dns_service_name
+option = ldap_entry_usn
+option = ldap_enumeration_refresh_timeout
+option = ldap_enumeration_refresh_offset
+option = ldap_enumeration_search_timeout
+option = ldap_force_upper_case_realm
+option = ldap_group_entry_usn
+option = ldap_group_external_member
+option = ldap_group_gid_number
+option = ldap_group_member
+option = ldap_group_modify_timestamp
+option = ldap_group_name
+option = ldap_group_nesting_level
+option = ldap_group_object_class
+option = ldap_group_objectsid
+option = ldap_group_search_base
+option = ldap_group_search_filter
+option = ldap_group_search_scope
+option = ldap_group_type
+option = ldap_group_uuid
+option = ldap_idmap_autorid_compat
+option = ldap_idmap_default_domain_sid
+option = ldap_idmap_default_domain
+option = ldap_idmap_helper_table_size
+option = ldap_id_mapping
+option = ldap_idmap_range_max
+option = ldap_idmap_range_min
+option = ldap_idmap_range_size
+option = ldap_id_use_start_tls
+option = ldap_krb5_init_creds
+option = ldap_krb5_keytab
+option = ldap_krb5_ticket_lifetime
+option = ldap_library_debug_level
+option = ldap_max_id
+option = ldap_min_id
+option = ldap_netgroup_member
+option = ldap_netgroup_modify_timestamp
+option = ldap_netgroup_name
+option = ldap_netgroup_object_class
+option = ldap_netgroup_search_base
+option = ldap_netgroup_triple
+option = ldap_network_timeout
+option = ldap_ns_account_lock
+option = ldap_offline_timeout
+option = ldap_opt_timeout
+option = ldap_page_size
+option = ldap_purge_cache_timeout
+option = ldap_purge_cache_offset
+option = ldap_pwd_attribute
+option = ldap_pwdlockout_dn
+option = ldap_pwd_policy
+option = ldap_referrals
+option = ldap_rfc2307_fallback_to_local_users
+option = ldap_rootdse_last_usn
+option = ldap_sasl_authid
+option = ldap_sasl_canonicalize
+option = ldap_sasl_mech
+option = ldap_sasl_minssf
+option = ldap_sasl_maxssf
+option = ldap_sasl_realm
+option = ldap_schema
+option = ldap_pwmodify_mode
+option = ldap_search_base
+option = ldap_search_timeout
+option = ldap_service_entry_usn
+option = ldap_service_name
+option = ldap_service_object_class
+option = ldap_service_port
+option = ldap_service_proto
+option = ldap_service_search_base
+option = ldap_sudo_full_refresh_interval
+option = ldap_sudo_hostnames
+option = ldap_sudo_include_netgroups
+option = ldap_sudo_include_regexp
+option = ldap_sudo_ip
+option = ldap_sudorule_command
+option = ldap_sudorule_host
+option = ldap_sudorule_name
+option = ldap_sudorule_notafter
+option = ldap_sudorule_notbefore
+option = ldap_sudorule_object_class
+option = ldap_sudorule_option
+option = ldap_sudorule_order
+option = ldap_sudorule_runasgroup
+option = ldap_sudorule_runas
+option = ldap_sudorule_runasuser
+option = ldap_sudorule_user
+option = ldap_sudo_search_base
+option = ldap_sudo_smart_refresh_interval
+option = ldap_sudo_random_offset
+option = ldap_sudo_use_host_filter
+option = ldap_tls_cacertdir
+option = ldap_tls_cacert
+option = ldap_tls_cert
+option = ldap_tls_cipher_suite
+option = ldap_tls_key
+option = ldap_tls_reqcert
+option = ldap_uri
+option = ldap_user_ad_account_expires
+option = ldap_user_ad_user_account_control
+option = ldap_user_authorized_host
+option = ldap_user_authorized_rhost
+option = ldap_user_authorized_service
+option = ldap_user_auth_type
+option = ldap_user_certificate
+option = ldap_user_email
+option = ldap_user_entry_usn
+option = ldap_user_extra_attrs
+option = ldap_user_fullname
+option = ldap_user_gecos
+option = ldap_user_gid_number
+option = ldap_user_home_directory
+option = ldap_user_krb_last_pwd_change
+option = ldap_user_krb_password_expiration
+option = ldap_user_member_of
+option = ldap_user_modify_timestamp
+option = ldap_user_name
+option = ldap_user_nds_login_allowed_time_map
+option = ldap_user_nds_login_disabled
+option = ldap_user_nds_login_expiration_time
+option = ldap_user_object_class
+option = ldap_user_objectsid
+option = ldap_user_primary_group
+option = ldap_user_principal
+option = ldap_user_search_base
+option = ldap_user_search_filter
+option = ldap_user_search_scope
+option = ldap_user_shadow_expire
+option = ldap_user_shadow_flag
+option = ldap_user_shadow_inactive
+option = ldap_user_shadow_last_change
+option = ldap_user_shadow_max
+option = ldap_user_shadow_min
+option = ldap_user_shadow_warning
+option = ldap_user_shell
+option = ldap_user_ssh_public_key
+option = ldap_user_uid_number
+option = ldap_user_uuid
+option = ldap_use_tokengroups
+option = ldap_host_object_class
+option = ldap_host_name
+option = ldap_host_fqdn
+option = ldap_host_serverhostname
+option = ldap_host_member_of
+option = ldap_host_search_base
+option = ldap_host_ssh_public_key
+option = ldap_host_uuid
+option = ldap_iphost_search_base
+option = ldap_iphost_object_class
+option = ldap_iphost_name
+option = ldap_iphost_number
+option = ldap_iphost_entry_usn
+option = ldap_ipnetwork_search_base
+option = ldap_ipnetwork_object_class
+option = ldap_ipnetwork_name
+option = ldap_ipnetwork_number
+option = ldap_ipnetwork_entry_usn
+
+# For application domains
+option = inherit_from
+
+[rule/allowed_subdomain_options]
+validator = ini_allowed_options
+section_re = ^domain/[^/\@]\+/[^/\@]\+$
+
+option = ldap_search_base
+option = ldap_user_search_base
+option = ldap_group_search_base
+option = ldap_netgroup_search_base
+option = ldap_service_search_base
+option = ldap_sasl_mech
+option = ad_server
+option = ad_backup_server
+option = ad_site
+option = use_fully_qualified_names
+option = auto_private_groups
+option = pam_gssapi_services
+option = pam_gssapi_check_upn
+option = pam_gssapi_indicators_map
+
+[rule/sssd_checks]
+validator = sssd_checks
+
+[rule/allowed_certmap_options]
+validator = ini_allowed_options
+section_re = ^certmap/[^/\@]\+/[^/\@]\+$
+
+option = matchrule
+option = maprule
+option = priority
+option = domains
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
new file mode 100644
index 0000000..5ae6aab
--- /dev/null
+++ b/src/config/etc/sssd.api.conf
@@ -0,0 +1,228 @@
+# Format:
+# option = type, subtype, mandatory[, default]
+
+[service]
+# Options available to all services
+timeout = int, None, false
+debug = int, None, false
+debug_level = int, None, false
+debug_timestamps = bool, None, false
+debug_microseconds = bool, None, false
+debug_backtrace_enabled = bool, None, false
+command = str, None, false
+reconnection_retries = int, None, false
+fd_limit = int, None, false
+client_idle_timeout = int, None, false
+responder_idle_timeout = int, None, false
+cache_first = int, None, false
+description = str, None, false
+
+[sssd]
+# Monitor service
+config_file_version = int, None, false
+services = list, str, true, nss, pam
+domains = list, str, true
+re_expression = str, None, false
+full_name_format = str, None, false
+krb5_rcache_dir = str, None, false
+user = str, None, false
+default_domain_suffix = str, None, false
+certificate_verification = str, None, false
+override_space = str, None, false
+disable_netlink = bool, None, false
+enable_files_domain = str, None, false
+domain_resolution_order = list, str, false
+try_inotify = bool, None, false
+monitor_resolv_conf = bool, None, false
+implicit_pac_responder = bool, None, false
+core_dumpable = bool, None, false
+passkey_verification = str, None, false
+
+[nss]
+# Name service
+enum_cache_timeout = int, None, false
+entry_cache_nowait_percentage = int, None, false
+entry_negative_timeout = int, None, false
+local_negative_timeout = int, None, false
+filter_users = list, str, false
+filter_groups = list, str, false
+filter_users_in_groups = bool, None, false
+pwfield = str, None, false
+override_homedir = str, None, false
+fallback_homedir = str, None, false
+homedir_substring = str, None, false, /home
+override_shell = str, None, false
+allowed_shells = list, str, false
+vetoed_shells = list, str, false
+shell_fallback = str, None, false
+default_shell = str, None, false
+get_domains_timeout = int, None, false
+memcache_timeout = int, None, false
+user_attributes = str, None, false
+
+[pam]
+# Authentication service
+offline_credentials_expiration = int, None, false
+offline_failed_login_attempts = int, None, false
+offline_failed_login_delay = int, None, false
+pam_verbosity = int, None, false
+pam_response_filter = str, None, false
+pam_id_timeout = int, None, false
+pam_pwd_expiration_warning = int, None, false
+get_domains_timeout = int, None, false
+pam_trusted_users = str, None, false
+pam_public_domains = str, None, false
+pam_account_expired_message = str, None, false
+pam_account_locked_message = str, None, false
+pam_cert_auth = bool, None, false
+pam_cert_db_path = str, None, false
+pam_cert_verification = str, None, false
+p11_child_timeout = int, None, false
+pam_app_services = str, None, false
+pam_p11_allowed_services = str, None, false
+p11_wait_for_card_timeout = int, None, false
+p11_uri = str, None, false
+pam_initgroups_scheme = str, None, false
+pam_gssapi_services = str, None, false
+pam_gssapi_check_upn = bool, None, false
+pam_gssapi_indicators_map = str, None, false
+pam_passkey_auth = bool, None, false
+passkey_child_timeout = int, None, false
+passkey_debug_libfido2 = bool, None, false
+
+[sudo]
+# sudo service
+sudo_timed = bool, None, false
+sudo_inverse_order = bool, None, false
+sudo_threshold = int, None, false
+
+[autofs]
+# autofs service
+autofs_negative_timeout = int, None, false
+
+[ssh]
+# ssh service
+ssh_hash_known_hosts = bool, None, false
+ssh_known_hosts_timeout = int, None, false
+ca_db = str, None, false
+ssh_use_certificate_keys = bool, None, false
+ssh_use_certificate_matching_rules = str, None, false
+
+[pac]
+# PAC responder
+allowed_uids = str, None, false
+pac_lifetime = int, None, false
+pac_check = str, None, false
+
+[ifp]
+# InfoPipe responder
+allowed_uids = str, None, false
+user_attributes = str, None, false
+
+[session_recording]
+# Session recording service
+scope = str, None, false
+users = list, str, false
+groups = list, str, false
+exclude_users = list, str, false
+exclude_groups = list, str, false
+
+[provider]
+#Available provider types
+id_provider = str, None, true
+auth_provider = str, None, false
+access_provider = str, None, false
+chpass_provider = str, None, false
+sudo_provider = str, None, false
+autofs_provider = str, None, false
+hostid_provider = str, None, false
+subdomains_provider = str, None, false
+selinux_provider = str, None, false
+session_provider = str, None, false
+resolver_provider = str, None, false
+
+[domain]
+# Options available to all domains
+enabled = bool, None, false
+description = str, None, false
+domain_type = str, None, false
+debug = int, None, false
+debug_level = int, None, false
+debug_timestamps = bool, None, false
+command = str, None, false
+min_id = int, None, false
+max_id = int, None, false
+timeout = int, None, false
+enumerate = bool, None, false
+subdomain_enumerate = str, None, false
+offline_timeout = int, None, false
+offline_timeout_max = int, None, false
+offline_timeout_random_offset = int, None, false
+cache_credentials = bool, None, false
+cache_credentials_minimal_first_factor_length = int, None, false
+use_fully_qualified_names = bool, None, false
+ignore_group_members = bool, None, false
+entry_cache_timeout = int, None, false
+lookup_family_order = str, None, false
+account_cache_expiration = int, None, false
+pwd_expiration_warning = int, None, false
+filter_users = list, str, false
+filter_groups = list, str, false
+dns_resolver_server_timeout = int, None, false
+dns_resolver_op_timeout = int, None, false
+dns_resolver_timeout = int, None, false
+dns_discovery_domain = str, None, false
+override_gid = int, None, false
+case_sensitive = str, None, false
+override_homedir = str, None, false
+fallback_homedir = str, None, false
+homedir_substring = str, None, false
+override_shell = str, None, false
+default_shell = str, None, false
+description = str, None, false
+realmd_tags = str, None, false
+subdomain_refresh_interval = int, None, false
+subdomain_refresh_interval_offset = int, None, false
+subdomain_inherit = str, None, false
+subdomain_homedir = str, None, false
+cached_auth_timeout = int, None, false
+full_name_format = str, None, false
+re_expression = str, None, false
+auto_private_groups = str, None, false
+pam_gssapi_services = str, None, false
+pam_gssapi_check_upn = bool, None, false
+pam_gssapi_indicators_map = str, None, false
+local_auth_policy = str, None, false
+
+#Entry cache timeouts
+entry_cache_user_timeout = int, None, false
+entry_cache_group_timeout = int, None, false
+entry_cache_netgroup_timeout = int, None, false
+entry_cache_service_timeout = int, None, false
+entry_cache_autofs_timeout = int, None, false
+entry_cache_sudo_timeout = int, None, false
+entry_cache_ssh_host_timeout = int, None, false
+entry_cache_resolver_timeout = int, None, false
+refresh_expired_interval = int, None, false
+refresh_expired_interval_offset = int, None, false
+
+# Dynamic DNS updates
+dyndns_update = bool, None, false
+dyndns_ttl = int, None, false
+dyndns_iface = str, None, false
+dyndns_refresh_interval = int, None, false
+dyndns_refresh_interval_offset = int, None, false
+dyndns_update_ptr = bool, None, false
+dyndns_force_tcp = bool, None, false
+dyndns_auth = str, None, false
+dyndns_server = str, None, false
+
+# Special providers
+[provider/permit]
+
+[provider/permit/access]
+
+[provider/deny]
+
+[provider/deny/access]
+
diff --git a/src/config/etc/sssd.api.d/crash_test_dummy b/src/config/etc/sssd.api.d/crash_test_dummy
new file mode 100644
index 0000000..02e447e
--- /dev/null
+++ b/src/config/etc/sssd.api.d/crash_test_dummy
@@ -0,0 +1 @@
+Please do not delete this file, it is part of the config API self-test.
diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf b/src/config/etc/sssd.api.d/sssd-ad.conf
new file mode 100644
index 0000000..3bf89ed
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-ad.conf
@@ -0,0 +1,212 @@
+[provider/ad]
+ad_domain = str, None, false
+ad_enabled_domains = str, None, false
+ad_server = str, None, false
+ad_backup_server = str, None, false
+ad_hostname = str, None, false
+ad_enable_dns_sites = bool, None, false
+ad_access_filter = str, None, false
+ad_enable_gc = bool, None, false
+ad_gpo_access_control = str, None, false
+ad_gpo_cache_timeout = int, None, false
+ad_gpo_map_interactive = str, None, false
+ad_gpo_map_remote_interactive = str, None, false
+ad_gpo_map_network = str, None, false
+ad_gpo_map_batch = str, None, false
+ad_gpo_map_service = str, None, false
+ad_gpo_map_permit = str, None, false
+ad_gpo_map_deny = str, None, false
+ad_gpo_default_right = str, None, false
+ad_site = str, None, false
+ad_maximum_machine_account_password_age = int, None, false
+ad_machine_account_password_renewal_opts = str, None, false
+ad_update_samba_machine_account_password = bool, None, false
+ad_use_ldaps = bool, None, false
+ad_allow_remote_domain_local_groups = bool, None, false
+ldap_uri = str, None, false
+ldap_backup_uri = str, None, false
+ldap_search_base = str, None, false
+ldap_schema = str, None, false
+ldap_pwmodify_mode = str, None, false
+ldap_default_bind_dn = str, None, false
+ldap_default_authtok_type = str, None, false
+ldap_default_authtok = str, None, false
+ldap_network_timeout = int, None, false
+ldap_opt_timeout = int, None, false
+ldap_offline_timeout = int, None, false
+ldap_tls_cacert = str, None, false
+ldap_tls_cacertdir = str, None, false
+ldap_tls_cert = str, None, false
+ldap_tls_key = str, None, false
+ldap_tls_cipher_suite = str, None, false
+ldap_tls_reqcert = str, None, false
+ldap_sasl_mech = str, None, false
+ldap_sasl_authid = str, None, false
+ldap_sasl_minssf = int, None, false
+ldap_sasl_maxssf = int, None, false
+krb5_kdcip = str, None, false
+krb5_server = str, None, false
+krb5_backup_server = str, None, false
+krb5_realm = str, None, false
+krb5_auth_timeout = int, None, false
+krb5_canonicalize = bool, None, false
+krb5_use_kdcinfo = bool, None, false
+ldap_krb5_keytab = str, None, false
+ldap_krb5_init_creds = bool, None, false
+ldap_entry_usn = str, None, false
+ldap_rootdse_last_usn = str, None, false
+ldap_referrals = bool, None, false
+ldap_krb5_ticket_lifetime = int, None, false
+ldap_dns_service_name = str, None, false
+ldap_deref = str, None, false
+ldap_page_size = int, None, false
+ldap_deref_threshold = int, None, false
+ldap_connection_expire_timeout = int, None, false
+ldap_connection_expire_offset = int, None, false
+ldap_connection_idle_timeout = int, None, false
+ldap_disable_paging = bool, None, false
+krb5_confd_path = str, None, false
+wildcard_limit = int, None, false
+
+[provider/ad/id]
+ldap_search_timeout = int, None, false
+ldap_enumeration_refresh_timeout = int, None, false
+ldap_purge_cache_timeout = int, None, false
+ldap_id_use_start_tls = bool, None, false
+ldap_id_mapping = bool, None, false
+ldap_user_search_base = str, None, false
+ldap_user_search_scope = str, None, false
+ldap_user_search_filter = str, None, false
+ldap_user_extra_attrs = str, None, false
+ldap_user_object_class = str, None, false
+ldap_user_name = str, None, false
+ldap_user_uid_number = str, None, false
+ldap_user_gid_number = str, None, false
+ldap_user_gecos = str, None, false
+ldap_user_home_directory = str, None, false
+ldap_user_shell = str, None, false
+ldap_user_uuid = str, None, false
+ldap_user_objectsid = str, None, false
+ldap_user_primary_group = str, None, false
+ldap_user_principal = str, None, false
+ldap_user_fullname = str, None, false
+ldap_user_member_of = str, None, false
+ldap_user_modify_timestamp = str, None, false
+ldap_user_entry_usn = str, None, false
+ldap_user_shadow_last_change = str, None, false
+ldap_user_shadow_min = str, None, false
+ldap_user_shadow_max = str, None, false
+ldap_user_shadow_warning = str, None, false
+ldap_user_shadow_inactive = str, None, false
+ldap_user_shadow_expire = str, None, false
+ldap_user_shadow_flag = str, None, false
+ldap_user_krb_last_pwd_change = str, None, false
+ldap_user_krb_password_expiration = str, None, false
+ldap_pwd_attribute = str, None, false
+ldap_user_ssh_public_key = str, None, false
+ldap_user_auth_type = str, None, false
+ldap_user_certificate = str, None, false
+ldap_user_email = str, None, false
+ldap_user_passkey = str, None, false
+ldap_group_search_base = str, None, false
+ldap_group_search_scope = str, None, false
+ldap_group_search_filter = str, None, false
+ldap_group_object_class = str, None, false
+ldap_group_name = str, None, false
+ldap_group_gid_number = str, None, false
+ldap_group_member = str, None, false
+ldap_group_uuid = str, None, false
+ldap_group_objectsid = str, None, false
+ldap_group_modify_timestamp = str, None, false
+ldap_group_entry_usn = str, None, false
+ldap_group_type = str, None, false
+ldap_group_external_member = str, None, false
+ldap_force_upper_case_realm = bool, None, false
+ldap_group_nesting_level = int, None, false
+ldap_netgroup_search_base = str, None, false
+ldap_service_object_class = str, None, false
+ldap_service_name = str, None, false
+ldap_service_port = str, None, false
+ldap_service_proto = str, None, false
+ldap_service_search_base = str, None, false
+ldap_service_entry_usn = str, None, false
+ldap_idmap_range_min = int, None, false
+ldap_idmap_range_max = int, None, false
+ldap_idmap_range_size = int, None, false
+ldap_idmap_autorid_compat = bool, None, false
+ldap_idmap_default_domain = str, None, false
+ldap_idmap_default_domain_sid = str, None, false
+ldap_idmap_helper_table_size = int, None, false
+ldap_use_tokengroups = bool, None, false
+ldap_rfc2307_fallback_to_local_users = bool, None, false
+ldap_pwdlockout_dn = str, None, false
+
+[provider/ad/auth]
+krb5_ccachedir = str, None, false
+krb5_ccname_template = str, None, false
+krb5_keytab = str, None, false
+krb5_validate = bool, None, false
+ldap_pwd_policy = str, None, false
+krb5_store_password_if_offline = bool, None, false
+krb5_renewable_lifetime = str, None, false
+krb5_lifetime = str, None, false
+krb5_renew_interval = str, None, false
+krb5_use_fast = str, None, false
+krb5_fast_principal = str, None, false
+krb5_fast_use_anonymous_pkinit = bool, None, false
+krb5_use_enterprise_principal = bool, None, false
+krb5_use_subdomain_realm = bool, None, false
+krb5_map_user = str, None, false
+
+[provider/ad/access]
+
+[provider/ad/chpass]
+krb5_kpasswd = str, None, false
+krb5_backup_kpasswd = str, None, false
+
+[provider/ad/subdomains]
+
+[provider/ad/sudo]
+ldap_sudo_search_base = str, None, false
+ldap_sudo_full_refresh_interval = int, None, false
+ldap_sudo_smart_refresh_interval = int, None, false
+ldap_sudo_random_offset = int, None, false
+ldap_sudo_use_host_filter = bool, None, false
+ldap_sudo_hostnames = str, None, false
+ldap_sudo_ip = str, None, false
+ldap_sudo_include_netgroups = bool, None, false
+ldap_sudo_include_regexp = bool, None, false
+ldap_sudorule_object_class = str, None, false
+ldap_sudorule_object_class_attr = str, None, false
+ldap_sudorule_name = str, None, false
+ldap_sudorule_command = str, None, false
+ldap_sudorule_host = str, None, false
+ldap_sudorule_user = str, None, false
+ldap_sudorule_option = str, None, false
+ldap_sudorule_runas = str, None, false
+ldap_sudorule_runasuser = str, None, false
+ldap_sudorule_runasgroup = str, None, false
+ldap_sudorule_notbefore = str, None, false
+ldap_sudorule_notafter = str, None, false
+ldap_sudorule_order = str, None, false
+
+[provider/ad/autofs]
+ldap_autofs_map_master_name = str, None, false
+ldap_autofs_map_object_class = str, None, false
+ldap_autofs_map_name = str, None, false
+ldap_autofs_entry_object_class = str, None, false
+ldap_autofs_entry_key = str, None, false
+ldap_autofs_entry_value = str, None, false
+ldap_autofs_search_base = str, None, false
+
+[provider/ad/resolver]
+ldap_iphost_search_base = str, None, false
+ldap_iphost_object_class = str, None, false
+ldap_iphost_name = str, None, false
+ldap_iphost_number = str, None, false
+ldap_iphost_entry_usn = str, None, false
+ldap_ipnetwork_search_base = str, None, false
+ldap_ipnetwork_object_class = str, None, false
+ldap_ipnetwork_name = str, None, false
+ldap_ipnetwork_number = str, None, false
+ldap_ipnetwork_entry_usn = str, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-files.conf b/src/config/etc/sssd.api.d/sssd-files.conf
new file mode 100644
index 0000000..2444d49
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-files.conf
@@ -0,0 +1,3 @@
+[provider/files]
+passwd_files = str, None, false
+group_files = str, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-ipa.conf b/src/config/etc/sssd.api.d/sssd-ipa.conf
new file mode 100644
index 0000000..b28281c
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-ipa.conf
@@ -0,0 +1,286 @@
+# Format:
+# option = type, subtype, mandatory[, default]
+[provider/ipa]
+ipa_domain = str, None, false
+ipa_server = str, None, false
+ipa_backup_server = str, None, false
+ipa_hostname = str, None, false
+ipa_deskprofile_search_base = str, None, false
+ipa_subid_ranges_search_base = str, None, false
+ipa_access_order = str, None, false
+ipa_dyndns_update = bool, None, false
+ipa_dyndns_ttl = int, None, false
+ipa_dyndns_iface = str, None, false
+ipa_hbac_search_base = str, None, false
+ipa_host_search_base = str, None, false
+ipa_master_domain_search_base = str, None, false
+ipa_ranges_search_base = str, None, false
+ipa_enable_dns_sites = bool, None, false
+ldap_uri = str, None, false
+ldap_backup_uri = str, None, false
+ldap_search_base = str, None, false
+ldap_schema = str, None, false
+ldap_pwmodify_mode = str, None, false
+ldap_default_bind_dn = str, None, false
+ldap_default_authtok_type = str, None, false
+ldap_default_authtok = str, None, false
+ldap_network_timeout = int, None, false
+ldap_opt_timeout = int, None, false
+ldap_offline_timeout = int, None, false
+ldap_tls_cacert = str, None, false
+ldap_tls_cacertdir = str, None, false
+ldap_tls_cert = str, None, false
+ldap_tls_key = str, None, false
+ldap_tls_cipher_suite = str, None, false
+ldap_tls_reqcert = str, None, false
+ldap_sasl_mech = str, None, false
+ldap_sasl_authid = str, None, false
+ldap_sasl_minssf = int, None, false
+ldap_sasl_maxssf = int, None, false
+krb5_kdcip = str, None, false
+krb5_server = str, None, false
+krb5_backup_server = str, None, false
+krb5_realm = str, None, false
+krb5_auth_timeout = int, None, false
+krb5_use_kdcinfo = bool, None, false
+krb5_kpasswd = str, None, false
+krb5_backup_kpasswd = str, None, false
+krb5_canonicalize = bool, None, false
+ldap_krb5_keytab = str, None, false
+ldap_krb5_init_creds = bool, None, false
+ldap_entry_usn = str, None, false
+ldap_rootdse_last_usn = str, None, false
+ldap_referrals = bool, None, false
+ldap_krb5_ticket_lifetime = int, None, false
+ldap_dns_service_name = str, None, false
+ldap_deref = str, None, false
+ldap_page_size = int, None, false
+ldap_deref_threshold = int, None, false
+ldap_connection_expire_timeout = int, None, false
+ldap_connection_expire_offset = int, None, false
+ldap_connection_idle_timeout = int, None, false
+ldap_disable_paging = bool, None, false
+krb5_confd_path = str, None, false
+wildcard_limit = int, None, false
+
+[provider/ipa/id]
+ldap_search_timeout = int, None, false
+ldap_enumeration_refresh_timeout = int, None, false
+ldap_purge_cache_timeout = int, None, false
+ldap_id_use_start_tls = bool, None, false
+ldap_id_mapping = bool, None, false
+ldap_user_search_base = str, None, false
+ldap_user_search_scope = str, None, false
+ldap_user_search_filter = str, None, false
+ldap_user_extra_attrs = str, None, false
+ldap_user_object_class = str, None, false
+ldap_user_name = str, None, false
+ldap_user_uid_number = str, None, false
+ldap_user_gid_number = str, None, false
+ldap_user_gecos = str, None, false
+ldap_user_home_directory = str, None, false
+ldap_user_shell = str, None, false
+ldap_user_uuid = str, None, false
+ldap_user_objectsid = str, None, false
+ldap_user_primary_group = str, None, false
+ldap_user_principal = str, None, false
+ldap_user_fullname = str, None, false
+ldap_user_member_of = str, None, false
+ldap_user_modify_timestamp = str, None, false
+ldap_user_entry_usn = str, None, false
+ldap_user_shadow_last_change = str, None, false
+ldap_user_shadow_min = str, None, false
+ldap_user_shadow_max = str, None, false
+ldap_user_shadow_warning = str, None, false
+ldap_user_shadow_inactive = str, None, false
+ldap_user_shadow_expire = str, None, false
+ldap_user_shadow_flag = str, None, false
+ldap_user_krb_last_pwd_change = str, None, false
+ldap_user_krb_password_expiration = str, None, false
+ldap_pwd_attribute = str, None, false
+ldap_user_ssh_public_key = str, None, false
+ldap_user_auth_type = str, None, false
+ldap_user_certificate = str, None, false
+ldap_user_email = str, None, false
+ldap_user_passkey = str, None, false
+ldap_group_search_base = str, None, false
+ldap_group_search_scope = str, None, false
+ldap_group_search_filter = str, None, false
+ldap_group_object_class = str, None, false
+ldap_group_name = str, None, false
+ldap_group_gid_number = str, None, false
+ldap_group_member = str, None, false
+ldap_group_uuid = str, None, false
+ldap_group_objectsid = str, None, false
+ldap_group_modify_timestamp = str, None, false
+ldap_group_entry_usn = str, None, false
+ldap_group_type = str, None, false
+ldap_group_external_member = str, None, false
+ldap_force_upper_case_realm = bool, None, false
+ldap_group_nesting_level = int, None, false
+ldap_netgroup_search_base = str, None, false
+ipa_netgroup_object_class = str, None, false
+ipa_netgroup_name = str, None, false
+ipa_netgroup_member = str, None, false
+ipa_netgroup_member_of = str, None, false
+ipa_netgroup_member_user = str, None, false
+ipa_netgroup_member_host = str, None, false
+ipa_netgroup_member_ext_host = str, None, false
+ipa_netgroup_domain = str, None, false
+ipa_netgroup_uuid = str, None, false
+ldap_service_object_class = str, None, false
+ldap_service_name = str, None, false
+ldap_service_port = str, None, false
+ldap_service_proto = str, None, false
+ldap_service_search_base = str, None, false
+ldap_service_entry_usn = str, None, false
+ipa_host_object_class = str, None, false
+ipa_host_fqdn = str, None, false
+ipa_host_ssh_public_key = str, None, false
+ldap_idmap_range_min = int, None, false
+ldap_idmap_range_max = int, None, false
+ldap_idmap_range_size = int, None, false
+ldap_idmap_autorid_compat = bool, None, false
+ldap_idmap_default_domain = str, None, false
+ldap_idmap_default_domain_sid = str, None, false
+ldap_idmap_helper_table_size = int, None, false
+ldap_use_tokengroups = bool, None, false
+ldap_rfc2307_fallback_to_local_users = bool, None, false
+ipa_server_mode = bool, None, false
+ldap_pwdlockout_dn = str, None, false
+ipa_views_search_base = str, None, false
+ipa_view_class = str, None, false
+ipa_view_name = str, None, false
+ipa_override_object_class = str, None, false
+ipa_anchor_uuid = str, None, false
+ipa_user_override_object_class = str, None, false
+ipa_group_override_object_class = str, None, false
+
+[provider/ipa/auth]
+krb5_ccachedir = str, None, false
+krb5_ccname_template = str, None, false
+krb5_keytab = str, None, false
+krb5_validate = bool, None, false
+ldap_pwd_policy = str, None, false
+krb5_store_password_if_offline = bool, None, false
+krb5_renewable_lifetime = str, None, false
+krb5_lifetime = str, None, false
+krb5_renew_interval = str, None, false
+krb5_use_fast = str, None, false
+krb5_fast_principal = str, None, false
+krb5_fast_use_anonymous_pkinit = bool, None, false
+krb5_use_enterprise_principal = bool, None, false
+krb5_use_subdomain_realm = bool, None, false
+krb5_map_user = str, None, false
+
+[provider/ipa/access]
+ipa_hbac_refresh = int, None, false
+ipa_selinux_refresh = int, None, false
+ipa_hbac_support_srchost = bool, None, false
+ipa_host_object_class = str, None, false
+ipa_host_name = str, None, false
+ipa_host_fqdn = str, None, false
+ipa_host_serverhostname = str, None, false
+ipa_host_member_of = str, None, false
+ipa_host_ssh_public_key = str, None, false
+ipa_host_uuid = str, None, false
+ipa_hostgroup_objectclass = str, None, false
+ipa_hostgroup_name = str, None, false
+ipa_hostgroup_member = str, None, false
+ipa_hostgroup_memberof = str, None, false
+ipa_hostgroup_uuid = str, None, false
+
+[provider/ipa/autofs]
+ipa_automount_location = str, None, false
+ldap_autofs_map_master_name = str, None, false
+ldap_autofs_map_object_class = str, None, false
+ldap_autofs_map_name = str, None, false
+ldap_autofs_entry_object_class = str, None, false
+ldap_autofs_entry_key = str, None, false
+ldap_autofs_entry_value = str, None, false
+ldap_autofs_search_base = str, None, false
+
+[provider/ipa/chpass]
+
+[provider/ipa/session]
+ipa_deskprofile_refresh = int, None, false
+ipa_deskprofile_request_interval = int, None, false
+ipa_host_object_class = str, None, false
+ipa_host_name = str, None, false
+ipa_host_fqdn = str, None, false
+ipa_host_serverhostname = str, None, false
+ipa_host_member_of = str, None, false
+ipa_host_ssh_public_key = str, None, false
+ipa_host_uuid = str, None, false
+ipa_selinux_usermap_object_class = str, None, false
+ipa_selinux_usermap_name = str, None, false
+ipa_selinux_usermap_member_user = str, None, false
+ipa_selinux_usermap_member_host = str, None, false
+ipa_selinux_usermap_see_also = str, None, false
+ipa_selinux_usermap_selinux_user = str, None, false
+ipa_selinux_usermap_enabled = str, None, false
+ipa_selinux_usermap_user_category = str, None, false
+ipa_selinux_usermap_host_category = str, None, false
+ipa_selinux_usermap_uuid = str, None, false
+
+[provider/ipa/hostid]
+
+[provider/ipa/subdomains]
+ipa_subdomains_search_base = str, None, false
+
+[provider/ipa/sudo]
+ldap_sudo_search_base = str, None, false
+ldap_sudo_full_refresh_interval = int, None, false
+ldap_sudo_smart_refresh_interval = int, None, false
+ldap_sudo_random_offset = int, None, false
+ldap_sudo_use_host_filter = bool, None, false
+ldap_sudo_hostnames = str, None, false
+ldap_sudo_ip = str, None, false
+ldap_sudo_include_netgroups = bool, None, false
+ldap_sudo_include_regexp = bool, None, false
+ldap_sudorule_object_class = str, None, false
+ldap_sudorule_object_class_attr = str, None, false
+ldap_sudorule_name = str, None, false
+ldap_sudorule_command = str, None, false
+ldap_sudorule_host = str, None, false
+ldap_sudorule_user = str, None, false
+ldap_sudorule_option = str, None, false
+ldap_sudorule_runas = str, None, false
+ldap_sudorule_runasuser = str, None, false
+ldap_sudorule_runasgroup = str, None, false
+ldap_sudorule_notbefore = str, None, false
+ldap_sudorule_notafter = str, None, false
+ldap_sudorule_order = str, None, false
+ipa_sudorule_object_class = str, None, false
+ipa_sudorule_name = str, None, false
+ipa_sudorule_uuid = str, None, false
+ipa_sudorule_enabled_flag = str, None, false
+ipa_sudorule_option = str, None, false
+ipa_sudorule_runasgroup = str, None, false
+ipa_sudorule_runasgroup = str, None, false
+ipa_sudorule_allowcmd = str, None, false
+ipa_sudorule_denycmd = str, None, false
+ipa_sudorule_host = str, None, false
+ipa_sudorule_user = str, None, false
+ipa_sudorule_notafter = str, None, false
+ipa_sudorule_notbefore = str, None, false
+ipa_sudorule_sudoorder = str, None, false
+ipa_sudorule_cmdcategory = str, None, false
+ipa_sudorule_hostcategory = str, None, false
+ipa_sudorule_usercategory = str, None, false
+ipa_sudorule_runasusercategory = str, None, false
+ipa_sudorule_runasgroupcategory = str, None, false
+ipa_sudorule_runasextuser = str, None, false
+ipa_sudorule_runasextgroup = str, None, false
+ipa_sudorule_runasextusergroup = str, None, false
+ipa_sudorule_externaluser = str, None, false
+ipa_sudorule_entry_usn = str, None, false
+ipa_sudocmdgroup_object_class = str, None, false
+ipa_sudocmdgroup_uuid = str, None, false
+ipa_sudocmdgroup_name = str, None, false
+ipa_sudocmdgroup_member = str, None, false
+ipa_sudocmdgroup_entry_usn = str, None, false
+ipa_sudocmd_object_class = str, None, false
+ipa_sudocmd_uuid = str, None, false
+ipa_sudocmd_sudoCmd = str, None, false
+ipa_sudocmd_memberof = str, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-krb5.conf b/src/config/etc/sssd.api.d/sssd-krb5.conf
new file mode 100644
index 0000000..0ae9ec5
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-krb5.conf
@@ -0,0 +1,31 @@
+[provider/krb5]
+krb5_kdcip = str, None, false
+krb5_server = str, None, false
+krb5_backup_server = str, None, false
+krb5_realm = str, None, true
+krb5_auth_timeout = int, None, false
+krb5_use_kdcinfo = bool, None, false
+krb5_kpasswd = str, None, false
+krb5_backup_kpasswd = str, None, false
+
+[provider/krb5/auth]
+krb5_ccachedir = str, None, false
+krb5_ccname_template = str, None, false
+krb5_keytab = str, None, false
+krb5_validate = bool, None, false
+krb5_store_password_if_offline = bool, None, false
+krb5_renewable_lifetime = str, None, false
+krb5_lifetime = str, None, false
+krb5_renew_interval = str, None, false
+krb5_use_fast = str, None, false
+krb5_fast_principal = str, None, false
+krb5_fast_use_anonymous_pkinit = bool, None, false
+krb5_canonicalize = bool, None, false
+krb5_use_enterprise_principal = bool, None, false
+krb5_use_subdomain_realm = bool, None, false
+krb5_map_user = str, None, false
+
+[provider/krb5/access]
+
+[provider/krb5/chpass]
+
diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf
new file mode 100644
index 0000000..237cf40
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-ldap.conf
@@ -0,0 +1,195 @@
+[provider/ldap]
+ldap_uri = str, None, false
+ldap_backup_uri = str, None, false
+ldap_search_base = str, None, false
+ldap_schema = str, None, false
+ldap_pwmodify_mode = str, None, false
+ldap_default_bind_dn = str, None, false
+ldap_default_authtok_type = str, None, false
+ldap_default_authtok = str, None, false
+ldap_network_timeout = int, None, false
+ldap_opt_timeout = int, None, false
+ldap_offline_timeout = int, None, false
+ldap_tls_cacert = str, None, false
+ldap_tls_cacertdir = str, None, false
+ldap_tls_cert = str, None, false
+ldap_tls_key = str, None, false
+ldap_tls_cipher_suite = str, None, false
+ldap_tls_reqcert = str, None, false
+ldap_sasl_mech = str, None, false
+ldap_sasl_authid = str, None, false
+krb5_kdcip = str, None, false
+krb5_server = str, None, false
+krb5_realm = str, None, false
+krb5_canonicalize = bool, None, false
+krb5_use_kdcinfo = bool, None, false
+ldap_krb5_keytab = str, None, false
+ldap_krb5_init_creds = bool, None, false
+ldap_entry_usn = str, None, false
+ldap_rootdse_last_usn = str, None, false
+ldap_referrals = bool, None, false
+ldap_krb5_ticket_lifetime = int, None, false
+ldap_dns_service_name = str, None, false
+ldap_deref = str, None, false
+ldap_page_size = int, None, false
+ldap_deref_threshold = int, None, false
+ldap_ignore_unreadable_references = bool, None, false
+ldap_sasl_canonicalize = bool, None, false
+ldap_sasl_minssf = int, None, false
+ldap_sasl_maxssf = int, None, false
+ldap_connection_expire_timeout = int, None, false
+ldap_connection_expire_offset = int, None, false
+ldap_connection_idle_timeout = int, None, false
+ldap_disable_paging = bool, None, false
+ldap_disable_range_retrieval = bool, None, false
+wildcard_limit = int, None, false
+
+[provider/ldap/id]
+ldap_search_timeout = int, None, false
+ldap_enumeration_search_timeout = int, None, false
+ldap_enumeration_refresh_timeout = int, None, false
+ldap_purge_cache_timeout = int, None, false
+ldap_id_use_start_tls = bool, None, false
+ldap_id_mapping = bool, None, false
+ldap_user_search_base = str, None, false
+ldap_user_search_scope = str, None, false
+ldap_user_search_filter = str, None, false
+ldap_user_extra_attrs = str, None, false
+ldap_user_object_class = str, None, false
+ldap_user_name = str, None, false
+ldap_user_uid_number = str, None, false
+ldap_user_gid_number = str, None, false
+ldap_user_gecos = str, None, false
+ldap_user_home_directory = str, None, false
+ldap_user_shell = str, None, false
+ldap_user_uuid = str, None, false
+ldap_user_objectsid = str, None, false
+ldap_user_primary_group = str, None, false
+ldap_user_principal = str, None, false
+ldap_user_fullname = str, None, false
+ldap_user_member_of = str, None, false
+ldap_user_modify_timestamp = str, None, false
+ldap_user_entry_usn = str, None, false
+ldap_user_shadow_last_change = str, None, false
+ldap_user_shadow_min = str, None, false
+ldap_user_shadow_max = str, None, false
+ldap_user_shadow_warning = str, None, false
+ldap_user_shadow_inactive = str, None, false
+ldap_user_shadow_expire = str, None, false
+ldap_user_shadow_flag = str, None, false
+ldap_user_krb_last_pwd_change = str, None, false
+ldap_user_krb_password_expiration = str, None, false
+ldap_user_authorized_service = str, None, false
+ldap_user_authorized_host = str, None, false
+ldap_user_authorized_rhost = str, None, false
+ldap_pwd_attribute = str, None, false
+ldap_user_ad_account_expires = str, None, false
+ldap_user_ad_user_account_control = str, None, false
+ldap_ns_account_lock = str, None, false
+ldap_user_nds_login_disabled = str, None, false
+ldap_user_nds_login_expiration_time = str, None, false
+ldap_user_nds_login_allowed_time_map = str, None, false
+ldap_user_ssh_public_key = str, None, false
+ldap_user_auth_type = str, None, false
+ldap_user_certificate = str, None, false
+ldap_user_email = str, None, false
+ldap_user_passkey = str, None, false
+ldap_group_search_base = str, None, false
+ldap_group_search_scope = str, None, false
+ldap_group_search_filter = str, None, false
+ldap_group_object_class = str, None, false
+ldap_group_name = str, None, false
+ldap_group_gid_number = str, None, false
+ldap_group_member = str, None, false
+ldap_group_uuid = str, None, false
+ldap_group_objectsid = str, None, false
+ldap_group_modify_timestamp = str, None, false
+ldap_group_entry_usn = str, None, false
+ldap_group_type = str, None, false
+ldap_group_external_member = str, None, false
+ldap_group_nesting_level = int, None, false
+ldap_force_upper_case_realm = bool, None, false
+ldap_netgroup_search_base = str, None, false
+ldap_netgroup_object_class = str, None, false
+ldap_netgroup_name = str, None, false
+ldap_netgroup_member = str, None, false
+ldap_netgroup_triple = str, None, false
+ldap_netgroup_modify_timestamp = str, None, false
+ldap_service_object_class = str, None, false
+ldap_service_name = str, None, false
+ldap_service_port = str, None, false
+ldap_service_proto = str, None, false
+ldap_service_search_base = str, None, false
+ldap_service_entry_usn = str, None, false
+ldap_idmap_range_min = int, None, false
+ldap_idmap_range_max = int, None, false
+ldap_idmap_range_size = int, None, false
+ldap_idmap_autorid_compat = bool, None, false
+ldap_idmap_default_domain = str, None, false
+ldap_idmap_default_domain_sid = str, None, false
+ldap_idmap_helper_table_size = int, None, false
+ldap_use_tokengroups = bool, None, false
+ldap_rfc2307_fallback_to_local_users = bool, None, false
+ldap_min_id = int, None, false
+ldap_max_id = int, None, false
+ldap_pwdlockout_dn = str, None, false
+ldap_library_debug_level = int, None, false
+
+[provider/ldap/auth]
+ldap_pwd_policy = str, None, false
+
+[provider/ldap/access]
+ldap_access_filter = str, None, false
+ldap_account_expire_policy = str, None, false
+ldap_access_order = str, None, false
+
+[provider/ldap/chpass]
+ldap_chpass_uri = str, None, false
+ldap_chpass_backup_uri = str, None, false
+ldap_chpass_dns_service_name = str, None, false
+ldap_chpass_update_last_change = bool, None, false
+
+[provider/ldap/sudo]
+ldap_sudo_search_base = str, None, false
+ldap_sudo_full_refresh_interval = int, None, false
+ldap_sudo_smart_refresh_interval = int, None, false
+ldap_sudo_random_offset = int, None, false
+ldap_sudo_use_host_filter = bool, None, false
+ldap_sudo_hostnames = str, None, false
+ldap_sudo_ip = str, None, false
+ldap_sudo_include_netgroups = bool, None, false
+ldap_sudo_include_regexp = bool, None, false
+ldap_sudorule_object_class = str, None, false
+ldap_sudorule_object_class_attr = str, None, false
+ldap_sudorule_name = str, None, false
+ldap_sudorule_command = str, None, false
+ldap_sudorule_host = str, None, false
+ldap_sudorule_user = str, None, false
+ldap_sudorule_option = str, None, false
+ldap_sudorule_runas = str, None, false
+ldap_sudorule_runasuser = str, None, false
+ldap_sudorule_runasgroup = str, None, false
+ldap_sudorule_notbefore = str, None, false
+ldap_sudorule_notafter = str, None, false
+ldap_sudorule_order = str, None, false
+
+[provider/ldap/autofs]
+ldap_autofs_map_master_name = str, None, false
+ldap_autofs_map_object_class = str, None, false
+ldap_autofs_map_name = str, None, false
+ldap_autofs_entry_object_class = str, None, false
+ldap_autofs_entry_key = str, None, false
+ldap_autofs_entry_value = str, None, false
+ldap_autofs_search_base = str, None, false
+
+[provider/ldap/resolver]
+ldap_iphost_search_base = str, None, false
+ldap_iphost_object_class = str, None, false
+ldap_iphost_name = str, None, false
+ldap_iphost_number = str, None, false
+ldap_iphost_entry_usn = str, None, false
+ldap_ipnetwork_search_base = str, None, false
+ldap_ipnetwork_object_class = str, None, false
+ldap_ipnetwork_name = str, None, false
+ldap_ipnetwork_number = str, None, false
+ldap_ipnetwork_entry_usn = str, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-proxy.conf b/src/config/etc/sssd.api.d/sssd-proxy.conf
new file mode 100644
index 0000000..09bf82a
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-proxy.conf
@@ -0,0 +1,12 @@
+[provider/proxy]
+proxy_max_children = int, None, false
+
+[provider/proxy/id]
+proxy_lib_name = str, None, true
+proxy_fast_alias = bool, None, true
+
+[provider/proxy/auth]
+proxy_pam_target = str, None, true
+
+[provider/proxy/chpass]
+
diff --git a/src/config/etc/sssd.api.d/sssd-simple.conf b/src/config/etc/sssd.api.d/sssd-simple.conf
new file mode 100644
index 0000000..e14ea45
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-simple.conf
@@ -0,0 +1,7 @@
+[provider/simple]
+
+[provider/simple/access]
+simple_allow_users = str, None, false
+simple_deny_users = str, None, false
+simple_allow_groups = str, None, false
+simple_deny_groups = str, None, false
diff --git a/src/config/setup.py b/src/config/setup.py
new file mode 100644
index 0000000..6d83d18
--- /dev/null
+++ b/src/config/setup.py
@@ -0,0 +1,33 @@
+# Authors:
+# Stephen Gallagher <sgallagh@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#
+
+"""
+Python-level packaging using distutils.
+"""
+
+from distutils.core import setup
+
+setup(
+ name='SSSDConfig',
+ version='2.9.4',
+ license='GPLv3+',
+ url='https://github.com/SSSD/sssd/',
+ packages=['SSSDConfig'],
+)
diff --git a/src/config/setup.py.in b/src/config/setup.py.in
new file mode 100644
index 0000000..27f63c4
--- /dev/null
+++ b/src/config/setup.py.in
@@ -0,0 +1,33 @@
+# Authors:
+# Stephen Gallagher <sgallagh@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#
+
+"""
+Python-level packaging using distutils.
+"""
+
+from distutils.core import setup
+
+setup(
+ name='SSSDConfig',
+ version='@VERSION@',
+ license='GPLv3+',
+ url='https://github.com/SSSD/sssd/',
+ packages=['SSSDConfig'],
+)
diff --git a/src/config/testconfigs/noparse.api.conf b/src/config/testconfigs/noparse.api.conf
new file mode 100644
index 0000000..5065100
--- /dev/null
+++ b/src/config/testconfigs/noparse.api.conf
@@ -0,0 +1,7 @@
+# Format:
+# option = type, subtype[, default]
+
+[service]
+# Options available to all services
+debug_level = int, None, 0
+command \ No newline at end of file
diff --git a/src/config/testconfigs/sssd-badversion.conf b/src/config/testconfigs/sssd-badversion.conf
new file mode 100644
index 0000000..5a0f61c
--- /dev/null
+++ b/src/config/testconfigs/sssd-badversion.conf
@@ -0,0 +1,37 @@
+[nss]
+nss_filter_groups = root
+nss_entry_negative_timeout = 15
+debug_level = 0
+nss_filter_users_in_groups = true
+nss_filter_users = root
+nss_entry_cache_no_wait_timeout = 60
+nss_entry_cache_timeout = 600
+nss_enum_cache_timeout = 120
+
+[sssd]
+services = nss, pam
+reconnection_retries = 3
+domains = PROXY, IPA
+config_file_version = 1
+
+[domain/PROXY]
+id_provider = proxy
+auth_provider = proxy
+debug_level = 0
+
+[domain/IPA]
+id_provider = ldap
+auth_provider = krb5
+debug_level = 0
+
+[domain/LDAP]
+id_provider = ldap
+auth_provider = ldap
+debug_level = 0
+
+[pam]
+debug_level = 0
+
+[dp]
+debug_level = 0
+
diff --git a/src/config/testconfigs/sssd-enabled-option.conf b/src/config/testconfigs/sssd-enabled-option.conf
new file mode 100644
index 0000000..a20f20e
--- /dev/null
+++ b/src/config/testconfigs/sssd-enabled-option.conf
@@ -0,0 +1,34 @@
+[nss]
+debug_level = 0
+
+[sssd]
+services = nss, pam
+reconnection_retries = 3
+domains = enabled_1, enabled_3, disabled_3
+config_file_version = 2
+debug_timestamps = False
+
+[domain/enabled_1]
+id_provider = proxy
+
+[domain/enabled_2]
+enabled = true
+id_provider = proxy
+
+[domain/enabled_3]
+enabled = true
+id_provider = proxy
+
+[domain/disabled_1]
+id_provider = proxy
+
+[domain/disabled_2]
+enabled = false
+id_provider = proxy
+
+[domain/disabled_3]
+enabled = false
+id_provider = proxy
+
+[pam]
+debug_level = 2
diff --git a/src/config/testconfigs/sssd-invalid-badbool.conf b/src/config/testconfigs/sssd-invalid-badbool.conf
new file mode 100644
index 0000000..a0b8f6d
--- /dev/null
+++ b/src/config/testconfigs/sssd-invalid-badbool.conf
@@ -0,0 +1,38 @@
+[nss]
+nss_filter_groups = root
+nss_entry_negative_timeout = 15
+debug_level = 0
+nss_filter_users_in_groups = true
+nss_filter_users = root
+nss_entry_cache_no_wait_timeout = 60
+nss_entry_cache_timeout = 600
+nss_enum_cache_timeout = 120
+
+[sssd]
+services = nss, pam
+reconnection_retries = 3
+domains = PROXY, IPA
+config_file_version = 2
+
+[domain/PROXY]
+id_provider = proxy
+auth_provider = proxy
+debug_level = 0
+
+[domain/IPA]
+id_provider = ldap
+ldap_id_use_start_tls = Fal
+auth_provider = krb5
+debug_level = 0
+
+[domain/LDAP]
+id_provider = ldap
+auth_provider=ldap
+debug_level = 0
+
+[pam]
+debug_level = 0
+
+[dp]
+debug_level = 0
+
diff --git a/src/config/testconfigs/sssd-invalid.conf b/src/config/testconfigs/sssd-invalid.conf
new file mode 100644
index 0000000..3a84ae1
--- /dev/null
+++ b/src/config/testconfigs/sssd-invalid.conf
@@ -0,0 +1,3 @@
+[sssd]
+services
+config_file_version = 2
diff --git a/src/config/testconfigs/sssd-nonexisting-services-domains.conf b/src/config/testconfigs/sssd-nonexisting-services-domains.conf
new file mode 100644
index 0000000..d1e2480
--- /dev/null
+++ b/src/config/testconfigs/sssd-nonexisting-services-domains.conf
@@ -0,0 +1,13 @@
+[domain/active]
+
+[domain/inactive]
+
+[sssd]
+domains = nonexistent, active
+services = nonexistent, nss
+
+[nss]
+debug_level = 1
+
+[pam]
+debug_level = 2
diff --git a/src/config/testconfigs/sssd-noversion.conf b/src/config/testconfigs/sssd-noversion.conf
new file mode 100644
index 0000000..aaeed6d
--- /dev/null
+++ b/src/config/testconfigs/sssd-noversion.conf
@@ -0,0 +1,58 @@
+[nss]
+nss_filter_groups = root
+nss_entry_negative_timeout = 15
+debug_level = 0
+nss_filter_users_in_groups = true
+nss_filter_users = root
+nss_entry_cache_no_wait_timeout = 60
+nss_entry_cache_timeout = 600
+nss_enum_cache_timeout = 120
+
+[sssd]
+services = nss, pam
+reconnection_retries = 3
+domains = PROXY, IPA
+
+[domain/PROXY]
+id_provider = proxy
+auth_provider = proxy
+debug_level = 0
+
+[domain/IPA]
+id_provider = ldap
+auth_provider = krb5
+debug_level = 0
+
+[domain/LDAP]
+id_provider = ldap
+auth_provider = ldap
+debug_level = 0
+
+[pam]
+debug_level = 0
+
+[dp]
+debug_level = 0
+
+[domain/ad.example.com]
+cache_credentials = true
+
+id_provider = ad
+auth_provider = ad
+access_provider = ad
+
+# Uncomment if service discovery is not working
+# ad_server = server.ad.example.com
+
+# Uncomment if you want to use POSIX UIDs and GIDs set on the AD side
+# ldap_id_mapping = False
+
+# Comment out if the users have the shell and home dir set on the AD side
+default_shell = /bin/bash
+fallback_homedir = /home/%d/%u
+
+# Uncomment and adjust if the default principal SHORTNAME$@REALM is not available
+# ldap_sasl_authid = host/client.ad.example.com@AD.EXAMPLE.COM
+
+# Comment out if you prefer to user shortnames.
+use_fully_qualified_names = True
diff --git a/src/config/testconfigs/sssd-test-parse.conf b/src/config/testconfigs/sssd-test-parse.conf
new file mode 100644
index 0000000..b48221b
--- /dev/null
+++ b/src/config/testconfigs/sssd-test-parse.conf
@@ -0,0 +1,12 @@
+[domain/active]
+
+[domain/inactive]
+
+[sssd]
+domains = active
+services = nss
+
+[nss]
+debug_level = 1
+
+[pam]
diff --git a/src/config/testconfigs/sssd-valid.conf b/src/config/testconfigs/sssd-valid.conf
new file mode 100644
index 0000000..0e08720
--- /dev/null
+++ b/src/config/testconfigs/sssd-valid.conf
@@ -0,0 +1,57 @@
+[nss]
+nss_filter_groups = root
+nss_entry_negative_timeout = 15
+debug_level = 0
+nss_filter_users_in_groups = true
+nss_filter_users = root
+nss_entry_cache_no_wait_timeout = 60
+nss_entry_cache_timeout = 600
+nss_enum_cache_timeout = 120
+
+[sssd]
+services = nss, pam
+reconnection_retries = 3
+domains = IPA
+config_file_version = 2
+debug_timestamps = False
+
+[domain/PROXY]
+id_provider = proxy
+auth_provider = proxy
+debug_level = 1
+
+[domain/IPA]
+id_provider = ldap
+auth_provider = krb5
+debug_level = 0xFF0
+
+[domain/LDAP]
+ldap_id_use_start_tls = true
+ldap_sudo_include_regexp = true
+ldap_autofs_map_master_name = "auto.master"
+id_provider = ldap
+auth_provider=ldap
+debug_level = 0
+
+# Domain containing an invalid provider
+[domain/INVALIDPROVIDER]
+ldap_id_use_start_tls = true
+id_provider = ldap
+auth_provider=ldap
+debug_level = 0
+chpass_provider = chpass
+
+# Domain containing an invalid option
+[domain/INVALIDOPTION]
+ldap_id_use_start_tls = true
+id_provider = ldap
+auth_provider=ldap
+debug_level = 0
+nosuchoption = True
+
+[pam]
+debug_level = 2
+nosuchoption = True
+
+[sudo]
+debug_level = 0xfC10