diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
commit | 74aa0bc6779af38018a03fd2cf4419fe85917904 (patch) | |
tree | 9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/config | |
parent | Initial commit. (diff) | |
download | sssd-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')
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 |