/* Authors: Simo Sorce Copyright (C) 2008-2010 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 . */ #include "providers/ldap/ldap_common.h" #include "providers/ldap/ldap_opts.h" #include "providers/ldap/sdap_async_private.h" #include "util/crypto/sss_crypto.h" int ldap_get_options(TALLOC_CTX *memctx, struct sss_domain_info *dom, struct confdb_ctx *cdb, const char *conf_path, struct sdap_options **_opts) { struct sdap_attr_map *default_attr_map; struct sdap_attr_map *default_user_map; struct sdap_attr_map *default_group_map; struct sdap_attr_map *default_netgroup_map; struct sdap_attr_map *default_service_map; struct sdap_options *opts; char *schema; const char *search_base; const char *pwd_policy; int ret; int account_cache_expiration; int offline_credentials_expiration; const char *ldap_deref; int ldap_deref_val; int o; const char *authtok_type; struct dp_opt_blob authtok_blob; char *cleartext; const int search_base_options[] = { SDAP_USER_SEARCH_BASE, SDAP_GROUP_SEARCH_BASE, SDAP_NETGROUP_SEARCH_BASE, SDAP_SERVICE_SEARCH_BASE, -1 }; opts = talloc_zero(memctx, struct sdap_options); if (!opts) return ENOMEM; ret = sdap_domain_add(opts, dom, NULL); if (ret != EOK) { goto done; } ret = dp_get_options(opts, cdb, conf_path, default_basic_opts, SDAP_OPTS_BASIC, &opts->basic); if (ret != EOK) { goto done; } /* Handle search bases */ search_base = dp_opt_get_string(opts->basic, SDAP_SEARCH_BASE); if (search_base != NULL) { /* set user/group/netgroup search bases if they are not */ for (o = 0; search_base_options[o] != -1; o++) { if (NULL == dp_opt_get_string(opts->basic, search_base_options[o])) { ret = dp_opt_set_string(opts->basic, search_base_options[o], search_base); if (ret != EOK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", opts->basic[search_base_options[o]].opt_name, dp_opt_get_string(opts->basic, search_base_options[o])); } } } else { DEBUG(SSSDBG_FUNC_DATA, "Search base not set, trying to discover it later when " "connecting to the LDAP server.\n"); } /* Default search */ ret = sdap_parse_search_base(opts, opts->basic, SDAP_SEARCH_BASE, &opts->sdom->search_bases); if (ret != EOK && ret != ENOENT) goto done; /* User search */ ret = sdap_parse_search_base(opts, opts->basic, SDAP_USER_SEARCH_BASE, &opts->sdom->user_search_bases); if (ret != EOK && ret != ENOENT) goto done; /* Group search base */ ret = sdap_parse_search_base(opts, opts->basic, SDAP_GROUP_SEARCH_BASE, &opts->sdom->group_search_bases); if (ret != EOK && ret != ENOENT) goto done; /* Netgroup search */ ret = sdap_parse_search_base(opts, opts->basic, SDAP_NETGROUP_SEARCH_BASE, &opts->sdom->netgroup_search_bases); if (ret != EOK && ret != ENOENT) goto done; /* Service search */ ret = sdap_parse_search_base(opts, opts->basic, SDAP_SERVICE_SEARCH_BASE, &opts->sdom->service_search_bases); if (ret != EOK && ret != ENOENT) goto done; pwd_policy = dp_opt_get_string(opts->basic, SDAP_PWD_POLICY); if (pwd_policy == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing password policy, this may not happen.\n"); ret = EINVAL; goto done; } if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) != 0 && strcasecmp(pwd_policy, PWD_POL_OPT_SHADOW) != 0 && strcasecmp(pwd_policy, PWD_POL_OPT_MIT) != 0) { DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported password policy [%s].\n", pwd_policy); ret = EINVAL; goto done; } /* account_cache_expiration must be >= than offline_credentials_expiration */ ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_CRED_TIMEOUT, 0, &offline_credentials_expiration); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get value of %s from confdb \n", CONFDB_PAM_CRED_TIMEOUT); goto done; } account_cache_expiration = dp_opt_get_int(opts->basic, SDAP_ACCOUNT_CACHE_EXPIRATION); /* account cache_expiration must not be smaller than * offline_credentials_expiration to prevent deleting entries that * still contain credentials valid for offline login. * * offline_credentials_expiration == 0 is a special case that says * that the cached credentials are valid forever. Therefore, the cached * entries must not be purged from cache. */ if (!offline_credentials_expiration && account_cache_expiration) { DEBUG(SSSDBG_CRIT_FAILURE, "Conflicting values for options %s (unlimited) " "and %s (%d)\n", opts->basic[SDAP_ACCOUNT_CACHE_EXPIRATION].opt_name, CONFDB_PAM_CRED_TIMEOUT, offline_credentials_expiration); ret = EINVAL; goto done; } if (offline_credentials_expiration && account_cache_expiration && offline_credentials_expiration > account_cache_expiration) { DEBUG(SSSDBG_CRIT_FAILURE, "Value of %s (now %d) must be larger " "than value of %s (now %d)\n", opts->basic[SDAP_ACCOUNT_CACHE_EXPIRATION].opt_name, account_cache_expiration, CONFDB_PAM_CRED_TIMEOUT, offline_credentials_expiration); ret = EINVAL; goto done; } ldap_deref = dp_opt_get_string(opts->basic, SDAP_DEREF); if (ldap_deref != NULL) { ret = deref_string_to_val(ldap_deref, &ldap_deref_val); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to verify ldap_deref option.\n"); goto done; } } #ifndef HAVE_LDAP_CONNCB bool ldap_referrals; ldap_referrals = dp_opt_get_bool(opts->basic, SDAP_REFERRALS); if (ldap_referrals) { DEBUG(SSSDBG_CRIT_FAILURE, "LDAP referrals are not supported, because the LDAP library " "is too old, see sssd-ldap(5) for details.\n"); ret = dp_opt_set_bool(opts->basic, SDAP_REFERRALS, false); } #endif /* schema type */ schema = dp_opt_get_string(opts->basic, SDAP_SCHEMA); if (strcasecmp(schema, "rfc2307") == 0) { opts->schema_type = SDAP_SCHEMA_RFC2307; default_attr_map = generic_attr_map; default_user_map = rfc2307_user_map; default_group_map = rfc2307_group_map; default_netgroup_map = netgroup_map; default_service_map = service_map; } else if (strcasecmp(schema, "rfc2307bis") == 0) { opts->schema_type = SDAP_SCHEMA_RFC2307BIS; default_attr_map = generic_attr_map; default_user_map = rfc2307bis_user_map; default_group_map = rfc2307bis_group_map; default_netgroup_map = netgroup_map; default_service_map = service_map; } else if (strcasecmp(schema, "IPA") == 0) { opts->schema_type = SDAP_SCHEMA_IPA_V1; default_attr_map = gen_ipa_attr_map; default_user_map = rfc2307bis_user_map; default_group_map = rfc2307bis_group_map; default_netgroup_map = netgroup_map; default_service_map = service_map; } else if (strcasecmp(schema, "AD") == 0) { opts->schema_type = SDAP_SCHEMA_AD; default_attr_map = gen_ad_attr_map; default_user_map = gen_ad2008r2_user_map; default_group_map = gen_ad2008r2_group_map; default_netgroup_map = netgroup_map; default_service_map = service_map; } else { DEBUG(SSSDBG_FATAL_FAILURE, "Unrecognized schema type: %s\n", schema); ret = EINVAL; goto done; } ret = sdap_get_map(opts, cdb, conf_path, default_attr_map, SDAP_AT_GENERAL, &opts->gen_map); if (ret != EOK) { goto done; } ret = sdap_get_map(opts, cdb, conf_path, default_user_map, SDAP_OPTS_USER, &opts->user_map); if (ret != EOK) { goto done; } ret = sdap_extend_map_with_list(opts, opts, SDAP_USER_EXTRA_ATTRS, opts->user_map, SDAP_OPTS_USER, &opts->user_map, &opts->user_map_cnt); if (ret != EOK) { goto done; } ret = sdap_get_map(opts, cdb, conf_path, default_group_map, SDAP_OPTS_GROUP, &opts->group_map); if (ret != EOK) { goto done; } ret = sdap_get_map(opts, cdb, conf_path, default_netgroup_map, SDAP_OPTS_NETGROUP, &opts->netgroup_map); if (ret != EOK) { goto done; } ret = sdap_get_map(opts, cdb, conf_path, default_service_map, SDAP_OPTS_SERVICES, &opts->service_map); if (ret != EOK) { goto done; } /* If there is no KDC, try the deprecated krb5_kdcip option, too */ /* FIXME - this can be removed in a future version */ ret = krb5_try_kdcip(cdb, conf_path, opts->basic, SDAP_KRB5_KDC); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_try_kdcip failed.\n"); goto done; } authtok_type = dp_opt_get_string(opts->basic, SDAP_DEFAULT_AUTHTOK_TYPE); if (authtok_type != NULL && strcasecmp(authtok_type,"obfuscated_password") == 0) { DEBUG(SSSDBG_TRACE_ALL, "Found obfuscated password, " "trying to convert to cleartext.\n"); authtok_blob = dp_opt_get_blob(opts->basic, SDAP_DEFAULT_AUTHTOK); if (authtok_blob.data == NULL || authtok_blob.length == 0) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing obfuscated password string.\n"); return EINVAL; } ret = sss_password_decrypt(memctx, (char *) authtok_blob.data, &cleartext); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot convert the obfuscated " "password back to cleartext\n"); return ret; } authtok_blob.data = (uint8_t *) cleartext; authtok_blob.length = strlen(cleartext); ret = dp_opt_set_blob(opts->basic, SDAP_DEFAULT_AUTHTOK, authtok_blob); talloc_free(cleartext); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); return ret; } ret = dp_opt_set_string(opts->basic, SDAP_DEFAULT_AUTHTOK_TYPE, "password"); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "dp_opt_set_string failed.\n"); return ret; } } ret = EOK; *_opts = opts; done: if (ret != EOK) { talloc_zfree(opts); } return ret; } int ldap_get_sudo_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb, const char *conf_path, struct sdap_options *opts, bool *use_host_filter, bool *include_regexp, bool *include_netgroups) { const char *search_base; int ret; /* search base */ search_base = dp_opt_get_string(opts->basic, SDAP_SEARCH_BASE); if (search_base != NULL) { /* set sudo search bases if they are not */ if (dp_opt_get_string(opts->basic, SDAP_SUDO_SEARCH_BASE) == NULL) { ret = dp_opt_set_string(opts->basic, SDAP_SUDO_SEARCH_BASE, search_base); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Could not set SUDO search base" "to default value\n"); return ret; } DEBUG(SSSDBG_FUNC_DATA, "Option %s set to %s\n", opts->basic[SDAP_SUDO_SEARCH_BASE].opt_name, dp_opt_get_string(opts->basic, SDAP_SUDO_SEARCH_BASE)); } } else { DEBUG(SSSDBG_TRACE_FUNC, "Search base not set, trying to discover it later " "connecting to the LDAP server.\n"); } ret = sdap_parse_search_base(opts, opts->basic, SDAP_SUDO_SEARCH_BASE, &opts->sdom->sudo_search_bases); if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_OP_FAILURE, "Could not parse SUDO search base\n"); return ret; } /* attrs map */ ret = sdap_get_map(opts, cdb, conf_path, native_sudorule_map, SDAP_OPTS_SUDO, &opts->sudorule_map); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Could not get SUDO attribute map\n"); return ret; } /* host filter */ *use_host_filter = dp_opt_get_bool(opts->basic, SDAP_SUDO_USE_HOST_FILTER); *include_netgroups = dp_opt_get_bool(opts->basic, SDAP_SUDO_INCLUDE_NETGROUPS); *include_regexp = dp_opt_get_bool(opts->basic, SDAP_SUDO_INCLUDE_REGEXP); return EOK; } int ldap_get_autofs_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb, const char *conf_path, struct sdap_options *opts) { const char *search_base; struct sdap_attr_map *default_entry_map; struct sdap_attr_map *default_mobject_map; int ret; /* search base */ search_base = dp_opt_get_string(opts->basic, SDAP_SEARCH_BASE); if (search_base != NULL) { /* set autofs search bases if they are not */ if (dp_opt_get_string(opts->basic, SDAP_AUTOFS_SEARCH_BASE) == NULL) { ret = dp_opt_set_string(opts->basic, SDAP_AUTOFS_SEARCH_BASE, search_base); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Could not set autofs search base" "to default value\n"); return ret; } DEBUG(SSSDBG_FUNC_DATA, "Option %s set to %s\n", opts->basic[SDAP_AUTOFS_SEARCH_BASE].opt_name, dp_opt_get_string(opts->basic, SDAP_AUTOFS_SEARCH_BASE)); } } else { DEBUG(SSSDBG_TRACE_FUNC, "Search base not set, trying to discover it later " "connecting to the LDAP server.\n"); } ret = sdap_parse_search_base(opts, opts->basic, SDAP_AUTOFS_SEARCH_BASE, &opts->sdom->autofs_search_bases); if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_OP_FAILURE, "Could not parse autofs search base\n"); return ret; } /* attribute maps */ switch (opts->schema_type) { case SDAP_SCHEMA_RFC2307: default_mobject_map = rfc2307_autofs_mobject_map; default_entry_map = rfc2307_autofs_entry_map; break; case SDAP_SCHEMA_RFC2307BIS: case SDAP_SCHEMA_IPA_V1: case SDAP_SCHEMA_AD: default_mobject_map = rfc2307bis_autofs_mobject_map; default_entry_map = rfc2307bis_autofs_entry_map; break; default: DEBUG(SSSDBG_CRIT_FAILURE, "Unknown LDAP schema!\n"); return EINVAL; } ret = sdap_get_map(opts, cdb, conf_path, default_mobject_map, SDAP_OPTS_AUTOFS_MAP, &opts->autofs_mobject_map); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Could not get autofs map object attribute map\n"); return ret; } ret = sdap_get_map(opts, cdb, conf_path, default_entry_map, SDAP_OPTS_AUTOFS_ENTRY, &opts->autofs_entry_map); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Could not get autofs entry object attribute map\n"); return ret; } return EOK; } errno_t sdap_parse_search_base(TALLOC_CTX *mem_ctx, struct dp_option *opts, int class, struct sdap_search_base ***_search_bases) { const char *class_name; char *unparsed_base; const char *old_filter = NULL; *_search_bases = NULL; switch (class) { case SDAP_SEARCH_BASE: class_name = "DEFAULT"; break; case SDAP_USER_SEARCH_BASE: class_name = "USER"; old_filter = dp_opt_get_string(opts, SDAP_USER_SEARCH_FILTER); break; case SDAP_GROUP_SEARCH_BASE: class_name = "GROUP"; old_filter = dp_opt_get_string(opts, SDAP_GROUP_SEARCH_FILTER); break; case SDAP_NETGROUP_SEARCH_BASE: class_name = "NETGROUP"; break; case SDAP_SUDO_SEARCH_BASE: class_name = "SUDO"; break; case SDAP_SERVICE_SEARCH_BASE: class_name = "SERVICE"; break; case SDAP_AUTOFS_SEARCH_BASE: class_name = "AUTOFS"; break; default: DEBUG(SSSDBG_CONF_SETTINGS, "Unknown search base type: [%d]\n", class); class_name = "UNKNOWN"; /* Non-fatal */ break; } unparsed_base = dp_opt_get_string(opts, class); if (!unparsed_base || unparsed_base[0] == '\0') return ENOENT; return common_parse_search_base(mem_ctx, unparsed_base, class_name, old_filter, _search_bases); } errno_t sdap_create_search_base(TALLOC_CTX *mem_ctx, const char *unparsed_base, int scope, const char *filter, struct sdap_search_base **_base) { struct sdap_search_base *base; TALLOC_CTX *tmp_ctx; errno_t ret; struct ldb_dn *ldn; struct ldb_context *ldb; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { ret = ENOMEM; goto done; } /* Create a throwaway LDB context for validating the DN */ ldb = ldb_init(tmp_ctx, NULL); if (!ldb) { ret = ENOMEM; goto done; } base = talloc_zero(tmp_ctx, struct sdap_search_base); if (base == NULL) { ret = ENOMEM; goto done; } base->basedn = talloc_strdup(base, unparsed_base); if (base->basedn == NULL) { ret = ENOMEM; goto done; } /* Validate the basedn */ ldn = ldb_dn_new(tmp_ctx, ldb, unparsed_base); if (!ldn) { ret = ENOMEM; goto done; } if (!ldb_dn_validate(ldn)) { DEBUG(SSSDBG_CRIT_FAILURE, "Invalid base DN [%s]\n", unparsed_base); ret = EINVAL; goto done; } base->scope = scope; base->filter = filter; *_base = talloc_steal(mem_ctx, base); ret = EOK; done: talloc_free(tmp_ctx); return ret; } errno_t common_parse_search_base(TALLOC_CTX *mem_ctx, const char *unparsed_base, const char *class_name, const char *old_filter, struct sdap_search_base ***_search_bases) { errno_t ret; struct sdap_search_base **search_bases; TALLOC_CTX *tmp_ctx; struct ldb_context *ldb; struct ldb_dn *ldn; struct ldb_parse_tree *tree; char **split_bases; char *filter; int count; int i, c; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { ret = ENOMEM; goto done; } /* Create a throwaway LDB context for validating the DN */ ldb = ldb_init(tmp_ctx, NULL); if (!ldb) { ret = ENOMEM; goto done; } ret = split_on_separator(tmp_ctx, unparsed_base, '?', false, false, &split_bases, &count); if (ret != EOK) goto done; /* The split must be either exactly one value or a multiple of * three in order to be valid. * One value: just a base, backwards-compatible with pre-1.7.0 versions * Multiple: search_base?scope?filter[?search_base?scope?filter]* */ if (count > 1 && (count % 3)) { DEBUG(SSSDBG_CRIT_FAILURE, "Unparseable search base: [%s][%d]\n", unparsed_base, count); ret = EINVAL; goto done; } if (count == 1) { search_bases = talloc_array(tmp_ctx, struct sdap_search_base *, 2); if (!search_bases) { ret = ENOMEM; goto done; } if (old_filter != NULL) { /* Using a deprecated ldap_{user,group}_search_filter */ DEBUG(SSSDBG_IMPORTANT_INFO, "WARNING: Using a deprecated filter " "option for %s. Please see the documentation on LDAP search " "bases to see how the obsolete option can be migrated\n", class_name); sss_log(SSS_LOG_NOTICE, "WARNING: Using a deprecated filter option" "for %s. Please see the documentation on LDAP search bases " "to see how the obsolete option can be migrated\n", class_name); } ret = sdap_create_search_base(search_bases, unparsed_base, LDAP_SCOPE_SUBTREE, old_filter, &search_bases[0]); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot create new sdap search base\n"); goto done; } DEBUG(SSSDBG_CONF_SETTINGS, "Search base added: [%s][%s][%s][%s]\n", class_name, search_bases[0]->basedn, "SUBTREE", search_bases[0]->filter ? search_bases[0]->filter : ""); search_bases[1] = NULL; } else { search_bases = talloc_array(tmp_ctx, struct sdap_search_base *, (count / 3) + 1); if (!search_bases) { ret = ENOMEM; goto done; } i = 0; for (c = 0; c < count; c += 3) { search_bases[i] = talloc_zero(search_bases, struct sdap_search_base); if (!search_bases[i]) { ret = ENOMEM; goto done; } if (split_bases[c][0] == '\0') { DEBUG(SSSDBG_CRIT_FAILURE, "Zero-length search base: [%s]\n", unparsed_base); ret = EINVAL; goto done; } /* Validate the basedn */ ldn = ldb_dn_new(tmp_ctx, ldb, split_bases[c]); if (!ldn) { ret = ENOMEM; goto done; } if (!ldb_dn_validate(ldn)) { DEBUG(SSSDBG_CRIT_FAILURE, "Invalid base DN [%s]\n", split_bases[c]); ret = EINVAL; goto done; } talloc_zfree(ldn); /* Set the search base DN */ search_bases[i]->basedn = talloc_strdup(search_bases[i], split_bases[c]); if (!search_bases[i]->basedn) { ret = ENOMEM; goto done; } /* Set the search scope for this base DN */ if ((split_bases[c+1][0] == '\0') || strcasecmp(split_bases[c+1], "sub") == 0 || strcasecmp(split_bases[c+1], "subtree") == 0) { /* If unspecified, default to subtree */ search_bases[i]->scope = LDAP_SCOPE_SUBTREE; } else if (strcasecmp(split_bases[c+1], "one") == 0 || strcasecmp(split_bases[c+1], "onelevel") == 0) { search_bases[i]->scope = LDAP_SCOPE_ONELEVEL; } else if (strcasecmp(split_bases[c+1], "base") == 0) { search_bases[i]->scope = LDAP_SCOPE_BASE; } else { DEBUG(SSSDBG_CRIT_FAILURE, "Unknown search scope: [%s]\n", split_bases[c+1]); ret = EINVAL; goto done; } /* Get a specialized filter if provided */ if (split_bases[c+2][0] == '\0') { search_bases[i]->filter = NULL; } else { if (split_bases[c+2][0] != '(') { /* Filters need to be enclosed in parentheses * to be validated properly by ldb_parse_tree() */ filter = talloc_asprintf(tmp_ctx, "(%s)", split_bases[c+2]); } else { filter = talloc_strdup(tmp_ctx, split_bases[c+2]); } if (!filter) { ret = ENOMEM; goto done; } tree = ldb_parse_tree(tmp_ctx, filter); if(!tree) { DEBUG(SSSDBG_CRIT_FAILURE, "Invalid search filter: [%s]\n", filter); ret = EINVAL; goto done; } talloc_zfree(tree); search_bases[i]->filter = talloc_steal(search_bases[i], filter); } DEBUG(SSSDBG_CONF_SETTINGS, "Search base added: [%s][%s][%s][%s]\n", class_name, search_bases[i]->basedn, split_bases[c+1][0] ? split_bases[c+1] : "SUBTREE", search_bases[i]->filter ? search_bases[i]->filter : ""); i++; } search_bases[i] = NULL; } *_search_bases = talloc_steal(mem_ctx, search_bases); ret = EOK; done: talloc_free(tmp_ctx); return ret; }