From effcbdb12c7ef892f1fd92a745cb33a08ca4ba30 Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Mon, 26 Mar 2012 21:41:28 -0400 Subject: AD: Add AD identity provider This new identity provider takes advantage of existing code for the LDAP provider, but provides sensible defaults for operating against an Active Directory 2008 R2 or later server. --- src/providers/ad/ad_common.c | 600 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 600 insertions(+) create mode 100644 src/providers/ad/ad_common.c (limited to 'src/providers/ad/ad_common.c') diff --git a/src/providers/ad/ad_common.c b/src/providers/ad/ad_common.c new file mode 100644 index 000000000..92cd40eca --- /dev/null +++ b/src/providers/ad/ad_common.c @@ -0,0 +1,600 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 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 + +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_opts.h" + +errno_t +ad_get_common_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sss_domain_info *dom, + struct ad_options **_opts) +{ + errno_t ret; + int gret; + size_t i; + struct ad_options *opts = NULL; + char *domain; + char *server; + char *realm; + char *ad_hostname; + char hostname[HOST_NAME_MAX + 1]; + + opts = talloc_zero(mem_ctx, struct ad_options); + if (!opts) return ENOMEM; + + ret = dp_get_options(opts, cdb, conf_path, + ad_basic_opts, + AD_OPTS_BASIC, + &opts->basic); + if (ret != EOK) { + goto done; + } + + /* If the AD domain name wasn't explicitly set, assume that it + * matches the SSSD domain name + */ + domain = dp_opt_get_string(opts->basic, AD_DOMAIN); + if (!domain) { + ret = dp_opt_set_string(opts->basic, AD_DOMAIN, dom->name); + if (ret != EOK) { + goto done; + } + domain = dom->name; + } + + /* Did we get an explicit server name, or are we discovering it? */ + server = dp_opt_get_string(opts->basic, AD_SERVER); + if (!server) { + DEBUG(SSSDBG_CONF_SETTINGS, + ("No AD server set, will use service discovery!\n")); + } + + /* Set the machine's hostname to the local host name if it + * wasn't explicitly specified. + */ + ad_hostname = dp_opt_get_string(opts->basic, AD_HOSTNAME); + if (ad_hostname == NULL) { + gret = gethostname(hostname, HOST_NAME_MAX); + if (gret != 0) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + ("gethostname failed [%s].\n", + strerror(ret))); + goto done; + } + hostname[HOST_NAME_MAX] = '\0'; + DEBUG(SSSDBG_CONF_SETTINGS, + ("Setting ad_hostname to [%s].\n", hostname)); + ret = dp_opt_set_string(opts->basic, AD_HOSTNAME, hostname); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + ("Setting ad_hostname failed [%s].\n", + strerror(ret))); + goto done; + } + } + + + /* Always use the upper-case AD domain for the kerberos realm */ + realm = talloc_strdup(opts, domain); + if (!realm) { + ret = ENOMEM; + goto done; + } + + for (i = 0; realm[i]; i++) { + realm[i] = toupper(realm[i]); + } + + ret = dp_opt_set_string(opts->basic, AD_KRB5_REALM, realm); + if (ret != EOK) { + goto done; + } + + ret = EOK; + *_opts = opts; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + return ret; +} + +static void +ad_resolve_callback(void *private_data, struct fo_server *server); + +errno_t +ad_failover_init(TALLOC_CTX *mem_ctx, struct be_ctx *bectx, + const char *servers, + struct ad_options *options, + struct ad_service **_service) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct ad_service *service; + char *ad_domain; + char *realm; + char **list; + size_t i; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + service = talloc_zero(tmp_ctx, struct ad_service); + if (!service) { + ret = ENOMEM; + goto done; + } + + service->sdap = talloc_zero(service, struct sdap_service); + if (!service->sdap) { + ret = ENOMEM; + goto done; + } + + service->krb5_service = talloc_zero(service, struct krb5_service); + if (!service->krb5_service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(bectx, AD_SERVICE_NAME); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to create failover service!\n")); + goto done; + } + + service->sdap->name = talloc_strdup(service, AD_SERVICE_NAME); + if (!service->sdap->name) { + ret = ENOMEM; + goto done; + } + + service->krb5_service->name = talloc_strdup(service, AD_SERVICE_NAME); + if (!service->krb5_service->name) { + ret = ENOMEM; + goto done; + } + service->sdap->kinit_service_name = service->krb5_service->name; + + realm = dp_opt_get_string(options->basic, AD_KRB5_REALM); + if (!realm) { + DEBUG(SSSDBG_CRIT_FAILURE, ("No Kerberos realm set\n")); + ret = EINVAL; + goto done; + } + service->krb5_service->realm = + talloc_strdup(service->krb5_service, realm); + if (!service->krb5_service->realm) { + ret = ENOMEM; + goto done; + } + + if (!servers) { + servers = BE_SRV_IDENTIFIER; + } + + /* Split the server list */ + ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to parse server list!\n")); + goto done; + } + + ad_domain = dp_opt_get_string(options->basic, AD_DOMAIN); + + /* Add each of these servers to the failover service */ + for (i = 0; list[i]; i++) { + if (be_fo_is_srv_identifier(list[i])) { + ret = be_fo_add_srv_server(bectx, AD_SERVICE_NAME, "ldap", + ad_domain, BE_FO_PROTO_TCP, + false, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + ("Failed to add service discovery to failover: [%s]", + strerror(ret))); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, ("Added service discovery for AD\n")); + continue; + } + + ret = be_fo_add_server(bectx, AD_SERVICE_NAME, list[i], 0, NULL); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Failed to add server\n")); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, ("Added failover server %s\n", list[i])); + } + + ret = be_fo_service_add_callback(mem_ctx, bectx, AD_SERVICE_NAME, + ad_resolve_callback, service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + ("Failed to add failover callback! [%s]\n", strerror(ret))); + goto done; + } + + *_service = talloc_steal(mem_ctx, service); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void +ad_resolve_callback(void *private_data, struct fo_server *server) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct ad_service *service; + struct resolv_hostent *srvaddr; + struct sockaddr_storage *sockaddr; + char *address; + const char *safe_address; + char *new_uri; + const char *srv_name; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Out of memory\n")); + return; + } + + service = talloc_get_type(private_data, struct ad_service); + if (!service) { + ret = EINVAL; + goto done; + } + + srvaddr = fo_get_server_hostent(server); + if (!srvaddr) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("No hostent available for server (%s)\n", + fo_get_server_str_name(server))); + ret = EINVAL; + goto done; + } + + sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, LDAP_PORT); + if (sockaddr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("resolv_get_sockaddr_address failed.\n")); + ret = EIO; + goto done; + } + + address = resolv_get_string_address(tmp_ctx, srvaddr); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("resolv_get_string_address failed.\n")); + ret = EIO; + goto done; + } + + srv_name = fo_get_server_name(server); + if (srv_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Could not get server host name\n")); + ret = EINVAL; + goto done; + } + + new_uri = talloc_asprintf(service, "ldap://%s", srv_name); + if (!new_uri) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to copy URI\n")); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_CONF_SETTINGS, ("Constructed uri '%s'\n", new_uri)); + + /* free old one and replace with new one */ + talloc_zfree(service->sdap->uri); + service->sdap->uri = new_uri; + talloc_zfree(service->sdap->sockaddr); + service->sdap->sockaddr = talloc_steal(service, sockaddr); + + safe_address = sss_escape_ip_address(tmp_ctx, + srvaddr->family, + address); + if (safe_address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("sss_escape_ip_address failed.\n")); + ret = ENOMEM; + goto done; + } + + ret = write_krb5info_file(service->krb5_service->realm, safe_address, + SSS_KRB5KDC_FO_SRV); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("write_krb5info_file failed, authentication might fail.\n")); + } + + ret = EOK; +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("Error: [%s]\n", strerror(ret))); + } + talloc_free(tmp_ctx); + return; +} + +errno_t +ad_set_search_bases(struct sdap_options *id_opts); + +errno_t +ad_get_id_options(struct ad_options *ad_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options **_opts) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct sdap_options *id_opts; + char *krb5_realm; + char *sasl_primary; + char *desired_primary; + char *sasl_realm; + char *desired_realm; + char *keytab_path; + bool primary_requested = true; + bool realm_requested = true; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + id_opts = talloc_zero(tmp_ctx, struct sdap_options); + if (!id_opts) { + ret = ENOMEM; + goto done; + } + + ret = dp_get_options(id_opts, cdb, conf_path, + ad_def_ldap_opts, + SDAP_OPTS_BASIC, + &id_opts->basic); + if (ret != EOK) { + goto done; + } + + /* Set up search bases if they were assigned explicitly */ + ret = ad_set_search_bases(id_opts); + if (ret != EOK) goto done; + + /* We only support Kerberos password policy with AD, so + * force that on. + */ + ret = dp_opt_set_string(id_opts->basic, + SDAP_PWD_POLICY, + PWD_POL_OPT_MIT); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Could not set password policy\n")); + goto done; + } + + /* Set the Kerberos Realm for GSSAPI */ + krb5_realm = dp_opt_get_string(ad_opts->basic, AD_KRB5_REALM); + if (!krb5_realm) { + /* Should be impossible, this is set in ad_get_common_options() */ + DEBUG(SSSDBG_FATAL_FAILURE, ("No Kerberos realm\n")); + ret = EINVAL; + goto done; + } + + ret = dp_opt_set_string(id_opts->basic, SDAP_KRB5_REALM, krb5_realm); + if (ret != EOK) goto done; + DEBUG(SSSDBG_CONF_SETTINGS, + ("Option %s set to %s\n", + id_opts->basic[SDAP_KRB5_REALM].opt_name, + krb5_realm)); + + /* Configuration of SASL auth ID and realm */ + desired_primary = dp_opt_get_string(id_opts->basic, SDAP_SASL_AUTHID); + if (!desired_primary) { + primary_requested = false; + desired_primary = dp_opt_get_string(ad_opts->basic, AD_HOSTNAME); + } + + desired_realm = dp_opt_get_string(id_opts->basic, SDAP_SASL_REALM); + if (!desired_realm) { + realm_requested = false; + desired_realm = dp_opt_get_string(ad_opts->basic, AD_KRB5_REALM); + } + + keytab_path = dp_opt_get_string(id_opts->basic, SDAP_KRB5_KEYTAB); + /* It's okay if this is NULL here */ + + ret = select_principal_from_keytab(tmp_ctx, + desired_primary, desired_realm, + keytab_path, NULL, + &sasl_primary, &sasl_realm); + if (ret != EOK) goto done; + + if ((primary_requested && strcmp(desired_primary, sasl_primary) != 0) || + (realm_requested && strcmp(desired_realm, sasl_realm) != 0)) { + DEBUG(SSSDBG_FATAL_FAILURE, + ("Configured SASL auth ID/realm not found in keytab.\n")); + ret = ENOENT; + goto done; + } + + ret = dp_opt_set_string(id_opts->basic, SDAP_SASL_AUTHID, sasl_primary); + if (ret != EOK) goto done; + DEBUG(SSSDBG_CONF_SETTINGS, + ("Option %s set to %s\n", + id_opts->basic[SDAP_SASL_AUTHID].opt_name, + sasl_primary)); + + ret = dp_opt_set_string(id_opts->basic, SDAP_SASL_REALM, sasl_realm); + if (ret != EOK) goto done; + DEBUG(SSSDBG_CONF_SETTINGS, + ("Option %s set to %s\n", + id_opts->basic[SDAP_SASL_REALM].opt_name, + sasl_realm)); + + /* fix schema to AD */ + id_opts->schema_type = SDAP_SCHEMA_AD; + + /* Get sdap option maps */ + + /* General Attribute Map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_2008r2_attr_map, + SDAP_AT_GENERAL, + &id_opts->gen_map); + if (ret != EOK) { + goto done; + } + + /* User map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_2008r2_user_map, + SDAP_OPTS_USER, + &id_opts->user_map); + if (ret != EOK) { + goto done; + } + + /* Group map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_2008r2_group_map, + SDAP_OPTS_GROUP, + &id_opts->group_map); + if (ret != EOK) { + goto done; + } + + /* Netgroup map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_netgroup_map, + SDAP_OPTS_NETGROUP, + &id_opts->netgroup_map); + if (ret != EOK) { + goto done; + } + + /* Services map */ + ret = sdap_get_map(id_opts, + cdb, conf_path, + ad_service_map, + SDAP_OPTS_SERVICES, + &id_opts->service_map); + if (ret != EOK) { + goto done; + } + + ad_opts->id = talloc_steal(ad_opts, id_opts); + *_opts = id_opts; + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ad_set_search_bases(struct sdap_options *id_opts) +{ + errno_t ret; + char *default_search_base; + size_t o; + const int search_base_options[] = { SDAP_USER_SEARCH_BASE, + SDAP_GROUP_SEARCH_BASE, + SDAP_NETGROUP_SEARCH_BASE, + SDAP_SERVICE_SEARCH_BASE, + -1 }; + + /* AD servers provide defaultNamingContext, so we will + * rely on that to specify the search base unless it has + * been specifically overridden. + */ + + default_search_base = + dp_opt_get_string(id_opts->basic, SDAP_SEARCH_BASE); + + if (default_search_base) { + /* set search bases if they are not */ + for (o = 0; search_base_options[o] != -1; o++) { + if (NULL == dp_opt_get_string(id_opts->basic, + search_base_options[o])) { + ret = dp_opt_set_string(id_opts->basic, + search_base_options[o], + default_search_base); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_CONF_SETTINGS, + ("Option %s set to %s\n", + id_opts->basic[search_base_options[o]].opt_name, + dp_opt_get_string(id_opts->basic, + search_base_options[o]))); + } + } + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + ("Search base not set. SSSd will attempt to discover it later, " + "when connecting to the LDAP server.\n")); + } + + /* Default search */ + ret = sdap_parse_search_base(id_opts, id_opts->basic, + SDAP_SEARCH_BASE, + &id_opts->search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* User search */ + ret = sdap_parse_search_base(id_opts, id_opts->basic, + SDAP_USER_SEARCH_BASE, + &id_opts->user_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Group search base */ + ret = sdap_parse_search_base(id_opts, id_opts->basic, + SDAP_GROUP_SEARCH_BASE, + &id_opts->group_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Netgroup search */ + ret = sdap_parse_search_base(id_opts, id_opts->basic, + SDAP_NETGROUP_SEARCH_BASE, + &id_opts->netgroup_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + /* Service search */ + ret = sdap_parse_search_base(id_opts, id_opts->basic, + SDAP_SERVICE_SEARCH_BASE, + &id_opts->service_search_bases); + if (ret != EOK && ret != ENOENT) goto done; + + ret = EOK; +done: + return ret; +} -- cgit