/* 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" #include "providers/be_dyndns.h" #include "providers/fail_over.h" struct ad_server_data { bool gc; }; errno_t ad_set_search_bases(struct sdap_options *id_opts, struct sdap_domain *sdap); static errno_t ad_set_sdap_options(struct ad_options *ad_opts, struct sdap_options *id_opts); static struct sdap_options * ad_create_default_sdap_options(TALLOC_CTX *mem_ctx, struct data_provider *dp) { struct sdap_options *id_opts; errno_t ret; id_opts = talloc_zero(mem_ctx, struct sdap_options); if (!id_opts) { return NULL; } id_opts->dp = dp; ret = dp_copy_defaults(id_opts, ad_def_ldap_opts, SDAP_OPTS_BASIC, &id_opts->basic); if (ret != EOK) { goto fail; } /* Get sdap option maps */ /* General Attribute Map */ ret = sdap_copy_map(id_opts, ad_2008r2_attr_map, SDAP_AT_GENERAL, &id_opts->gen_map); if (ret != EOK) { goto fail; } /* User map */ ret = sdap_copy_map(id_opts, ad_2008r2_user_map, SDAP_OPTS_USER, &id_opts->user_map); if (ret != EOK) { goto fail; } id_opts->user_map_cnt = SDAP_OPTS_USER; /* Group map */ ret = sdap_copy_map(id_opts, ad_2008r2_group_map, SDAP_OPTS_GROUP, &id_opts->group_map); if (ret != EOK) { goto fail; } /* Netgroup map */ ret = sdap_copy_map(id_opts, ad_netgroup_map, SDAP_OPTS_NETGROUP, &id_opts->netgroup_map); if (ret != EOK) { goto fail; } /* Services map */ ret = sdap_copy_map(id_opts, ad_service_map, SDAP_OPTS_SERVICES, &id_opts->service_map); if (ret != EOK) { goto fail; } /* IP host map */ ret = sdap_copy_map(id_opts, ad_iphost_map, SDAP_OPTS_IPHOST, &id_opts->iphost_map); if (ret != EOK) { goto fail; } /* IP network map */ ret = sdap_copy_map(id_opts, ad_ipnetwork_map, SDAP_OPTS_IPNETWORK, &id_opts->ipnetwork_map); if (ret != EOK) { goto fail; } return id_opts; fail: talloc_free(id_opts); return NULL; } static errno_t ad_create_sdap_options(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, const char *conf_path, struct data_provider *dp, struct sdap_options **_id_opts) { struct sdap_options *id_opts; errno_t ret = EOK; if (cdb == NULL || conf_path == NULL) { /* Fallback to defaults if there is no confdb */ id_opts = ad_create_default_sdap_options(mem_ctx, dp); if (id_opts == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to initialize default sdap options\n"); ret = EIO; } /* Nothing to do without cdb */ goto done; } id_opts = talloc_zero(mem_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; } /* sssd-ad can't use simple bind, ignore option that potentially can be set * for sssd-ldap in the same domain */ ret = dp_opt_set_string(id_opts->basic, SDAP_DEFAULT_AUTHTOK_TYPE, NULL); if (ret != EOK) { goto done; } /* 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; } ret = sdap_extend_map_with_list(id_opts, id_opts, SDAP_USER_EXTRA_ATTRS, id_opts->user_map, SDAP_OPTS_USER, &id_opts->user_map, &id_opts->user_map_cnt); 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; } /* IP host map */ ret = sdap_get_map(id_opts, cdb, conf_path, ad_iphost_map, SDAP_OPTS_IPHOST, &id_opts->iphost_map); if (ret != EOK) { goto done; } /* IP network map */ ret = sdap_get_map(id_opts, cdb, conf_path, ad_ipnetwork_map, SDAP_OPTS_IPNETWORK, &id_opts->ipnetwork_map); if (ret != EOK) { goto done; } ret = EOK; done: if (ret == EOK) { *_id_opts = id_opts; } else { talloc_free(id_opts); } return ret; } struct ad_options * ad_create_options(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, const char *conf_path, struct data_provider *dp, struct sss_domain_info *subdom) { struct ad_options *ad_options; errno_t ret; ad_options = talloc_zero(mem_ctx, struct ad_options); if (ad_options == NULL) return NULL; if (cdb != NULL && conf_path != NULL) { ret = dp_get_options(ad_options, cdb, conf_path, ad_basic_opts, AD_OPTS_BASIC, &ad_options->basic); } else { /* Fallback to reading the defaults only if no confdb * is available */ ret = dp_copy_defaults(ad_options, ad_basic_opts, AD_OPTS_BASIC, &ad_options->basic); } if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get basic AD options\n"); talloc_free(ad_options); return NULL; } ret = ad_create_sdap_options(ad_options, cdb, conf_path, dp, &ad_options->id); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD LDAP options\n"); talloc_free(ad_options); return NULL; } return ad_options; } static errno_t set_common_ad_trust_opts(struct ad_options *ad_options, const char *realm, const char *ad_domain, const char *hostname, const char *keytab) { errno_t ret; ret = dp_opt_set_string(ad_options->basic, AD_KRB5_REALM, realm); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot set AD krb5 realm\n"); return ret; } ret = dp_opt_set_string(ad_options->basic, AD_DOMAIN, ad_domain); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot set AD domain\n"); return ret; } ret = dp_opt_set_string(ad_options->basic, AD_HOSTNAME, hostname); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot set AD hostname\n"); return ret; } if (keytab != NULL) { ret = dp_opt_set_string(ad_options->basic, AD_KEYTAB, keytab); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot set keytab\n"); return ret; } } return EOK; } struct ad_options * ad_create_2way_trust_options(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, const char *conf_path, struct data_provider *dp, const char *realm, struct sss_domain_info *subdom, const char *hostname, const char *keytab) { struct ad_options *ad_options; errno_t ret; DEBUG(SSSDBG_TRACE_FUNC, "2way trust is defined to domain '%s'\n", subdom->name); ad_options = ad_create_options(mem_ctx, cdb, conf_path, dp, subdom); if (ad_options == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "ad_create_options failed\n"); return NULL; } ret = set_common_ad_trust_opts(ad_options, realm, subdom->name, hostname, keytab); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "set_common_ad_trust_opts failed\n"); talloc_free(ad_options); return NULL; } ret = ad_set_sdap_options(ad_options, ad_options->id); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "ad_set_sdap_options failed\n"); talloc_free(ad_options); return NULL; } return ad_options; } struct ad_options * ad_create_1way_trust_options(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, const char *subdom_conf_path, struct data_provider *dp, struct sss_domain_info *subdom, const char *hostname, const char *keytab, const char *sasl_authid) { struct ad_options *ad_options; const char *realm; errno_t ret; DEBUG(SSSDBG_TRACE_FUNC, "1way trust is defined to domain '%s'\n", subdom->name); ad_options = ad_create_options(mem_ctx, cdb, subdom_conf_path, dp, subdom); if (ad_options == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "ad_create_options failed\n"); return NULL; } realm = get_uppercase_realm(ad_options, subdom->name); if (!realm) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get uppercase realm\n"); talloc_free(ad_options); return NULL; } ret = set_common_ad_trust_opts(ad_options, realm, subdom->name, hostname, keytab); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "set_common_ad_trust_opts failed [%d]: %s\n", ret, sss_strerror(ret)); talloc_free(ad_options); return NULL; } /* Set SDAP_SASL_AUTHID to the trust principal */ ret = dp_opt_set_string(ad_options->id->basic, SDAP_SASL_AUTHID, sasl_authid); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot set SASL authid\n"); talloc_free(ad_options); return NULL; } ret = ad_set_sdap_options(ad_options, ad_options->id); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "ad_set_sdap_options failed [%d]: %s\n", ret, sss_strerror(ret)); talloc_free(ad_options); return NULL; } return ad_options; } static errno_t ad_try_to_get_fqdn(const char *hostname, char *buf, size_t buflen) { int ret; struct addrinfo *res; struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_CANONNAME; ret = getaddrinfo(hostname, NULL, &hints, &res); if (ret != 0) { DEBUG(SSSDBG_CRIT_FAILURE, "getaddrinfo failed: %s\n", gai_strerror(ret)); return ret; } strncpy(buf, res->ai_canonname, buflen-1); buf[buflen-1] = '\0'; freeaddrinfo(res); return EOK; } 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; struct ad_options *opts = NULL; char *domain; char *server; char *realm; char *ad_hostname; char hostname[HOST_NAME_MAX + 1]; char fqdn[HOST_NAME_MAX + 1]; char *case_sensitive_opt; const char *opt_override; 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, sizeof(hostname)); if (gret != 0) { ret = errno; DEBUG(SSSDBG_FATAL_FAILURE, "gethostname failed [%s].\n", strerror(ret)); goto done; } hostname[HOST_NAME_MAX] = '\0'; if (strchr(hostname, '.') == NULL) { ret = ad_try_to_get_fqdn(hostname, fqdn, sizeof(fqdn)); if (ret == EOK) { DEBUG(SSSDBG_CONF_SETTINGS, "The hostname [%s] has been expanded to FQDN [%s]. " "If sssd should really use the short hostname, please " "set ad_hostname explicitly.\n", hostname, fqdn); strncpy(hostname, fqdn, HOST_NAME_MAX); 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 = get_uppercase_realm(opts, domain); if (!realm) { ret = ENOMEM; goto done; } ret = dp_opt_set_string(opts->basic, AD_KRB5_REALM, realm); if (ret != EOK) { goto done; } /* Active Directory is always case-insensitive */ ret = confdb_get_string(cdb, mem_ctx, conf_path, CONFDB_DOMAIN_CASE_SENSITIVE, "false", &case_sensitive_opt); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "condb_get_string failed.\n"); goto done; } if (strcasecmp(case_sensitive_opt, "true") == 0) { DEBUG(SSSDBG_CRIT_FAILURE, "Warning: AD domain can not be set as case-sensitive.\n"); dom->case_sensitive = false; dom->case_preserve = false; } else if (strcasecmp(case_sensitive_opt, "false") == 0) { dom->case_sensitive = false; dom->case_preserve = false; } else if (strcasecmp(case_sensitive_opt, "preserving") == 0) { dom->case_sensitive = false; dom->case_preserve = true; } else { DEBUG(SSSDBG_FATAL_FAILURE, "Invalid value for %s\n", CONFDB_DOMAIN_CASE_SENSITIVE); goto done; } opt_override = dom->case_preserve ? "preserving" : "false"; /* Set this in the confdb so that the responders pick it * up when they start up. */ ret = confdb_set_string(cdb, conf_path, "case_sensitive", opt_override); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not set domain option case_sensitive: [%s]\n", strerror(ret)); goto done; } DEBUG(SSSDBG_CONF_SETTINGS, "Setting domain option case_sensitive to [%s]\n", opt_override); 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); static errno_t _ad_servers_init(struct ad_service *service, struct be_ctx *bectx, const char *fo_service, const char *fo_gc_service, const char *servers, const char *ad_domain, bool primary) { size_t i; size_t j; errno_t ret = 0; char **list; struct ad_server_data *sdata; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) return ENOMEM; /* Split the server list */ ret = split_on_separator(tmp_ctx, servers, ',', true, true, &list, NULL); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); goto done; } for (j = 0; list[j]; j++) { if (resolv_is_address(list[j])) { DEBUG(SSSDBG_IMPORTANT_INFO, "ad_server [%s] is detected as IP address, " "this can cause GSSAPI/GSS-SPNEGO problems\n", list[j]); } } /* Add each of these servers to the failover service */ for (i = 0; list[i]; i++) { if (be_fo_is_srv_identifier(list[i])) { if (!primary) { DEBUG(SSSDBG_MINOR_FAILURE, "Failed to add server [%s] to failover service: " "SRV resolution only allowed for primary servers!\n", list[i]); continue; } sdata = talloc(service, struct ad_server_data); if (sdata == NULL) { ret = ENOMEM; goto done; } sdata->gc = true; ret = be_fo_add_srv_server(bectx, fo_gc_service, "gc", ad_domain, BE_FO_PROTO_TCP, false, sdata); if (ret != EOK) { DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add service discovery to failover: [%s]\n", strerror(ret)); goto done; } sdata = talloc(service, struct ad_server_data); if (sdata == NULL) { ret = ENOMEM; goto done; } sdata->gc = false; ret = be_fo_add_srv_server(bectx, fo_service, "ldap", ad_domain, BE_FO_PROTO_TCP, false, sdata); if (ret != EOK) { DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add service discovery to failover: [%s]\n", strerror(ret)); goto done; } DEBUG(SSSDBG_CONF_SETTINGS, "Added service discovery for AD\n"); continue; } /* It could be ipv6 address in square brackets. Remove * the brackets if needed. */ ret = remove_ipv6_brackets(list[i]); if (ret != EOK) { goto done; } sdata = talloc(service, struct ad_server_data); if (sdata == NULL) { ret = ENOMEM; goto done; } sdata->gc = true; ret = be_fo_add_server(bectx, fo_gc_service, list[i], 0, sdata, primary); if (ret && ret != EEXIST) { DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); goto done; } sdata = talloc(service, struct ad_server_data); if (sdata == NULL) { ret = ENOMEM; goto done; } sdata->gc = false; ret = be_fo_add_server(bectx, fo_service, list[i], 0, sdata, primary); 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]); } done: talloc_free(tmp_ctx); return ret; } static inline errno_t ad_primary_servers_init(struct ad_service *service, struct be_ctx *bectx, const char *servers, const char *fo_service, const char *fo_gc_service, const char *ad_domain) { return _ad_servers_init(service, bectx, fo_service, fo_gc_service, servers, ad_domain, true); } static inline errno_t ad_backup_servers_init(struct ad_service *service, struct be_ctx *bectx, const char *servers, const char *fo_service, const char *fo_gc_service, const char *ad_domain) { return _ad_servers_init(service, bectx, fo_service, fo_gc_service, servers, ad_domain, false); } static int ad_user_data_cmp(void *ud1, void *ud2) { struct ad_server_data *sd1, *sd2; sd1 = talloc_get_type(ud1, struct ad_server_data); sd2 = talloc_get_type(ud2, struct ad_server_data); if (sd1 == NULL || sd2 == NULL) { DEBUG(SSSDBG_TRACE_FUNC, "No user data\n"); return sd1 == sd2 ? 0 : 1; } if (sd1->gc == sd2->gc) { return 0; } return 1; } static void ad_online_cb(void *pvt) { struct ad_service *service = talloc_get_type(pvt, struct ad_service); if (service == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Invalid private pointer\n"); return; } DEBUG(SSSDBG_TRACE_FUNC, "The AD provider is online\n"); } errno_t ad_failover_init(TALLOC_CTX *mem_ctx, struct be_ctx *bectx, const char *primary_servers, const char *backup_servers, const char *krb5_realm, const char *ad_service, const char *ad_gc_service, const char *ad_domain, bool use_kdcinfo, bool ad_use_ldaps, size_t n_lookahead_primary, size_t n_lookahead_backup, struct ad_service **_service) { errno_t ret; TALLOC_CTX *tmp_ctx; struct ad_service *service; 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; } if (ad_use_ldaps) { service->ldap_scheme = "ldaps"; service->port = LDAPS_PORT; service->gc_port = AD_GC_LDAPS_PORT; } else { service->ldap_scheme = "ldap"; service->port = LDAP_PORT; service->gc_port = AD_GC_PORT; } service->sdap = talloc_zero(service, struct sdap_service); service->gc = talloc_zero(service, struct sdap_service); if (!service->sdap || !service->gc) { ret = ENOMEM; goto done; } service->sdap->name = talloc_strdup(service->sdap, ad_service); service->gc->name = talloc_strdup(service->gc, ad_gc_service); if (!service->sdap->name || !service->gc->name) { ret = ENOMEM; goto done; } service->krb5_service = krb5_service_new(service, bectx, ad_service, krb5_realm, use_kdcinfo, n_lookahead_primary, n_lookahead_backup); if (!service->krb5_service) { ret = ENOMEM; goto done; } ret = be_fo_add_service(bectx, ad_service, ad_user_data_cmp); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n"); goto done; } ret = be_fo_add_service(bectx, ad_gc_service, ad_user_data_cmp); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create GC failover service!\n"); goto done; } service->sdap->kinit_service_name = service->krb5_service->name; service->gc->kinit_service_name = service->krb5_service->name; if (!krb5_realm) { DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm set\n"); ret = EINVAL; goto done; } if (!primary_servers) { DEBUG(SSSDBG_CONF_SETTINGS, "No primary servers defined, using service discovery\n"); primary_servers = BE_SRV_IDENTIFIER; } ret = ad_primary_servers_init(service, bectx, primary_servers, ad_service, ad_gc_service, ad_domain); if (ret != EOK) { goto done; } if (backup_servers) { ret = ad_backup_servers_init(service, bectx, backup_servers, ad_service, ad_gc_service, ad_domain); if (ret != EOK) { goto done; } } ret = be_add_online_cb(bectx, bectx, ad_online_cb, service, NULL); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not set up AD online callback\n"); goto done; } ret = be_fo_service_add_callback(mem_ctx, bectx, ad_service, ad_resolve_callback, service); if (ret != EOK) { DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add failover callback! [%s]\n", strerror(ret)); goto done; } ret = be_fo_service_add_callback(mem_ctx, bectx, ad_gc_service, 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; } void ad_failover_reset(struct be_ctx *bectx, struct ad_service *adsvc) { if (adsvc == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "NULL service\n"); return; } sdap_service_reset_fo(bectx, adsvc->sdap); sdap_service_reset_fo(bectx, adsvc->gc); } static bool ad_krb5info_file_filter(struct fo_server *server) { struct ad_server_data *sdata = NULL; if (server == NULL) return true; sdata = fo_get_server_user_data(server); if (sdata && sdata->gc) { /* Only write kdcinfo files for local servers */ return true; } return false; } 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 *sockaddr; char *address; char *new_uri; int new_port; socklen_t sockaddr_len; const char *srv_name; struct ad_server_data *sdata = NULL; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) { DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); return; } sdata = fo_get_server_user_data(server); if (fo_is_srv_lookup(server) == false && sdata == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "No user data?\n"); ret = EINVAL; goto done; } 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; } 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->sdap, "%s://%s", service->ldap_scheme, 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); sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, service->port, &sockaddr_len); if (sockaddr == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_sockaddr_address failed.\n"); ret = EIO; goto done; } /* free old one and replace with new one */ if (sdata == NULL || !sdata->gc) { /* do not update LDAP data during GC lookups because the selected server * might be from a different domain. */ talloc_zfree(service->sdap->uri); service->sdap->uri = new_uri; talloc_zfree(service->sdap->sockaddr); service->sdap->sockaddr = talloc_steal(service->sdap, sockaddr); service->sdap->sockaddr_len = sockaddr_len; } talloc_zfree(service->gc->uri); talloc_zfree(service->gc->sockaddr); if (sdata && sdata->gc) { if (service->gc_port == AD_GC_LDAPS_PORT) { new_port = service->gc_port; } else { new_port = fo_get_server_port(server); new_port = (new_port == 0) ? service->gc_port : new_port; } service->gc->uri = talloc_asprintf(service->gc, "%s:%d", new_uri, new_port); service->gc->sockaddr = resolv_get_sockaddr_address(service->gc, srvaddr, new_port, &sockaddr_len); service->gc->sockaddr_len = sockaddr_len; } else { /* Make sure there always is an URI even if we know that this * server doesn't support GC. That way the lookup would go through * just not return anything */ service->gc->uri = talloc_strdup(service->gc, service->sdap->uri); service->gc->sockaddr = talloc_memdup(service->gc, service->sdap->sockaddr, service->sdap->sockaddr_len); service->gc->sockaddr_len = service->sdap->sockaddr_len; } if (!service->gc->uri) { DEBUG(SSSDBG_CRIT_FAILURE, "NULL GC URI\n"); ret = ENOMEM; goto done; } DEBUG(SSSDBG_CONF_SETTINGS, "Constructed GC uri '%s'\n", service->gc->uri); if (service->gc->sockaddr == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "NULL GC sockaddr\n"); ret = EIO; goto done; } if (service->krb5_service->write_kdcinfo && !(sdata != NULL && sdata->gc)) { /* write KDC info file only if this is not GC lookup */ ret = write_krb5info_file_from_fo_server(service->krb5_service, server, true, SSS_KRB5KDC_FO_SRV, ad_krb5info_file_filter); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "write to %s/kdcinfo.%s failed, authentication might fail.\n", PUBCONF_PATH, service->krb5_service->realm); } } ret = EOK; done: if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Error: %d [%s]\n", ret, strerror(ret)); } talloc_free(tmp_ctx); return; } void ad_set_ssf_and_mech_for_ldaps(struct sdap_options *id_opts) { int ret; DEBUG(SSSDBG_TRACE_ALL, "Setting ssf and mech for ldaps usage.\n"); ret = dp_opt_set_int(id_opts->basic, SDAP_SASL_MINSSF, 0); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set SASL minssf for ldaps usage, ignored.\n"); } ret = dp_opt_set_int(id_opts->basic, SDAP_SASL_MAXSSF, 0); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set SASL maxssf for ldaps usage, ignored.\n"); } #ifndef ALLOW_GSS_SPNEGO_FOR_ZERO_MAXSSF /* There is an issue in cyrus-sasl with respect to GSS-SPNEGO and * maxssf==0. Until the fix * https://github.com/cyrusimap/cyrus-sasl/pull/603 is widely used we * switch to GSSAPI by default when using AD with LDAPS where maxssf==0 is * required. */ ret = dp_opt_set_string(id_opts->basic, SDAP_SASL_MECH, "GSSAPI"); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set SASL mech for ldaps usage, ignored.\n"); } #endif } static errno_t ad_set_sdap_options(struct ad_options *ad_opts, struct sdap_options *id_opts) { errno_t ret; char *krb5_realm; char *keytab_path; const char *schema; /* 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 or GSS-SPNEGO */ 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); keytab_path = dp_opt_get_string(ad_opts->basic, AD_KEYTAB); if (keytab_path) { ret = dp_opt_set_string(id_opts->basic, SDAP_KRB5_KEYTAB, keytab_path); if (ret != EOK) goto done; DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", id_opts->basic[SDAP_KRB5_KEYTAB].opt_name, keytab_path); } id_opts->allow_remote_domain_local_groups = dp_opt_get_bool(ad_opts->basic, AD_ALLOW_REMOTE_DOMAIN_LOCAL); ret = sdap_set_sasl_options(id_opts, dp_opt_get_string(ad_opts->basic, AD_HOSTNAME), dp_opt_get_string(ad_opts->basic, AD_KRB5_REALM), keytab_path); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot set the SASL-related options\n"); goto done; } if (dp_opt_get_bool(ad_opts->basic, AD_USE_LDAPS)) { ad_set_ssf_and_mech_for_ldaps(id_opts); } /* Warn if the user is doing something silly like overriding the schema * with the AD provider */ schema = dp_opt_get_string(id_opts->basic, SDAP_SCHEMA); if (schema != NULL && strcasecmp(schema, "ad") != 0) { DEBUG(SSSDBG_IMPORTANT_INFO, "The AD provider only supports the AD LDAP schema. " "SSSD will ignore the ldap_schema option value and proceed " "with ldap_schema=ad\n"); } /* fix schema to AD */ id_opts->schema_type = SDAP_SCHEMA_AD; ad_opts->id = id_opts; ret = EOK; done: return ret; } errno_t ad_get_id_options(struct ad_options *ad_opts, struct confdb_ctx *cdb, const char *conf_path, struct data_provider *dp, struct sdap_options **_opts) { struct sdap_options *id_opts; errno_t ret; ret = ad_create_sdap_options(ad_opts, cdb, conf_path, dp, &id_opts); if (ret != EOK) { return ENOMEM; } ret = ad_set_sdap_options(ad_opts, id_opts); if (ret != EOK) { talloc_free(id_opts); return ret; } ret = sdap_domain_add(id_opts, ad_opts->id_ctx->sdap_id_ctx->be->domain, NULL); if (ret != EOK) { talloc_free(id_opts); return ret; } /* Set up search bases if they were assigned explicitly */ ret = ad_set_search_bases(id_opts, NULL); if (ret != EOK) { talloc_free(id_opts); return ret; } *_opts = id_opts; return EOK; } errno_t ad_get_autofs_options(struct ad_options *ad_opts, struct confdb_ctx *cdb, const char *conf_path) { errno_t ret; /* autofs maps */ ret = sdap_get_map(ad_opts->id, cdb, conf_path, ad_autofs_mobject_map, SDAP_OPTS_AUTOFS_MAP, &ad_opts->id->autofs_mobject_map); if (ret != EOK) { return ret; } ret = sdap_get_map(ad_opts->id, cdb, conf_path, ad_autofs_entry_map, SDAP_OPTS_AUTOFS_ENTRY, &ad_opts->id->autofs_entry_map); if (ret != EOK) { return ret; } return EOK; } errno_t ad_set_search_bases(struct sdap_options *id_opts, struct sdap_domain *sdom) { errno_t ret; char *default_search_base = NULL; size_t o; struct sdap_domain *sdap_dom; bool has_default; struct ldb_context *ldb; 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. */ if (sdom != NULL) { sdap_dom = sdom; } else { /* If no specific sdom was given, use the first in the list. */ sdap_dom = id_opts->sdom; } ldb = sysdb_ctx_get_ldb(sdap_dom->dom->sysdb); has_default = sdap_dom->search_bases != NULL; if (has_default == false) { default_search_base = dp_opt_get_string(id_opts->basic, SDAP_SEARCH_BASE); } if (default_search_base && has_default == false) { /* 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, ldb, id_opts->basic, SDAP_SEARCH_BASE, &sdap_dom->search_bases); if (ret != EOK && ret != ENOENT) goto done; /* User search */ ret = sdap_parse_search_base(id_opts, ldb, id_opts->basic, SDAP_USER_SEARCH_BASE, &sdap_dom->user_search_bases); if (ret != EOK && ret != ENOENT) goto done; /* Group search base */ ret = sdap_parse_search_base(id_opts, ldb, id_opts->basic, SDAP_GROUP_SEARCH_BASE, &sdap_dom->group_search_bases); if (ret != EOK && ret != ENOENT) goto done; /* Netgroup search */ ret = sdap_parse_search_base(id_opts, ldb, id_opts->basic, SDAP_NETGROUP_SEARCH_BASE, &sdap_dom->netgroup_search_bases); if (ret != EOK && ret != ENOENT) goto done; /* Service search */ ret = sdap_parse_search_base(id_opts, ldb, id_opts->basic, SDAP_SERVICE_SEARCH_BASE, &sdap_dom->service_search_bases); if (ret != EOK && ret != ENOENT) goto done; ret = EOK; done: return ret; } errno_t ad_get_auth_options(TALLOC_CTX *mem_ctx, struct ad_options *ad_opts, struct be_ctx *bectx, struct dp_option **_opts) { errno_t ret; struct dp_option *krb5_options; const char *ad_servers; const char *krb5_realm; TALLOC_CTX *tmp_ctx = talloc_new(NULL); if (!tmp_ctx) return ENOMEM; /* Get krb5 options */ ret = dp_get_options(tmp_ctx, bectx->cdb, bectx->conf_path, ad_def_krb5_opts, KRB5_OPTS, &krb5_options); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not read Kerberos options from the configuration\n"); goto done; } ad_servers = dp_opt_get_string(ad_opts->basic, AD_SERVER); /* Force the krb5_servers to match the ad_servers */ ret = dp_opt_set_string(krb5_options, KRB5_KDC, ad_servers); if (ret != EOK) goto done; DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", krb5_options[KRB5_KDC].opt_name, ad_servers); /* Set krb5 realm */ /* Set the Kerberos Realm for GSSAPI/GSS-SPNEGO */ 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; } /* Force the kerberos realm to match the AD_KRB5_REALM (which may have * been upper-cased in ad_common_options() */ ret = dp_opt_set_string(krb5_options, KRB5_REALM, krb5_realm); if (ret != EOK) goto done; DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", krb5_options[KRB5_REALM].opt_name, krb5_realm); /* Set flag that controls whether we want to write the * kdcinfo files at all */ ad_opts->service->krb5_service->write_kdcinfo = \ dp_opt_get_bool(krb5_options, KRB5_USE_KDCINFO); DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", krb5_options[KRB5_USE_KDCINFO].opt_name, ad_opts->service->krb5_service->write_kdcinfo ? "true" : "false"); sss_krb5_parse_lookahead( dp_opt_get_string(krb5_options, KRB5_KDCINFO_LOOKAHEAD), &ad_opts->service->krb5_service->lookahead_primary, &ad_opts->service->krb5_service->lookahead_backup); *_opts = talloc_steal(mem_ctx, krb5_options); ret = EOK; done: talloc_free(tmp_ctx); return ret; } errno_t ad_get_dyndns_options(struct be_ctx *be_ctx, struct ad_options *ad_opts) { errno_t ret; ret = be_nsupdate_init(ad_opts, be_ctx, ad_dyndns_opts, &ad_opts->dyndns_ctx); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD dyndns opts [%d]: %s\n", ret, sss_strerror(ret)); return ret; } return EOK; } struct ad_id_ctx * ad_id_ctx_init(struct ad_options *ad_opts, struct be_ctx *bectx) { struct sdap_id_ctx *sdap_ctx; struct ad_id_ctx *ad_ctx; ad_ctx = talloc_zero(ad_opts, struct ad_id_ctx); if (ad_ctx == NULL) { return NULL; } ad_ctx->ad_options = ad_opts; sdap_ctx = sdap_id_ctx_new(ad_ctx, bectx, ad_opts->service->sdap); if (sdap_ctx == NULL) { talloc_free(ad_ctx); return NULL; } ad_ctx->sdap_id_ctx = sdap_ctx; ad_ctx->ldap_ctx = sdap_ctx->conn; ad_ctx->gc_ctx = sdap_id_ctx_conn_add(sdap_ctx, ad_opts->service->gc); if (ad_ctx->gc_ctx == NULL) { talloc_free(ad_ctx); return NULL; } return ad_ctx; } errno_t ad_resolver_ctx_init(TALLOC_CTX *mem_ctx, struct ad_id_ctx *ad_id_ctx, struct ad_resolver_ctx **out_ctx) { struct sdap_resolver_ctx *sdap_ctx; struct ad_resolver_ctx *ad_ctx; errno_t ret; ad_ctx = talloc_zero(mem_ctx, struct ad_resolver_ctx); if (ad_ctx == NULL) { return ENOMEM; } ad_ctx->ad_id_ctx = ad_id_ctx; ret = sdap_resolver_ctx_new(ad_ctx, ad_id_ctx->sdap_id_ctx, &sdap_ctx); if (ret != EOK) { talloc_free(ad_ctx); return ret; } ad_ctx->sdap_resolver_ctx = sdap_ctx; *out_ctx = ad_ctx; return EOK; } struct sdap_id_conn_ctx * ad_get_dom_ldap_conn(struct ad_id_ctx *ad_ctx, struct sss_domain_info *dom) { struct sdap_id_conn_ctx *conn; struct sdap_domain *sdom; struct ad_id_ctx *subdom_id_ctx; sdom = sdap_domain_get(ad_ctx->sdap_id_ctx->opts, dom); if (sdom == NULL || sdom->pvt == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", dom->name); return NULL; } subdom_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); conn = subdom_id_ctx->ldap_ctx; if (IS_SUBDOMAIN(sdom->dom) == true && conn != NULL) { /* Regardless of connection types, a subdomain error must not be * allowed to set the whole back end offline, rather report an error * and let the caller deal with it (normally disable the subdomain */ conn->ignore_mark_offline = true; } return conn; } struct sdap_id_conn_ctx ** ad_gc_conn_list(TALLOC_CTX *mem_ctx, struct ad_id_ctx *ad_ctx, struct sss_domain_info *dom) { struct sdap_id_conn_ctx **clist; int cindex = 0; clist = talloc_zero_array(mem_ctx, struct sdap_id_conn_ctx *, 3); if (clist == NULL) return NULL; /* Always try GC first */ if (dp_opt_get_bool(ad_ctx->ad_options->basic, AD_ENABLE_GC)) { clist[cindex] = ad_ctx->gc_ctx; clist[cindex]->ignore_mark_offline = true; clist[cindex]->no_mpg_user_fallback = true; cindex++; } clist[cindex] = ad_get_dom_ldap_conn(ad_ctx, dom); return clist; } struct sdap_id_conn_ctx ** ad_ldap_conn_list(TALLOC_CTX *mem_ctx, struct ad_id_ctx *ad_ctx, struct sss_domain_info *dom) { struct sdap_id_conn_ctx **clist; clist = talloc_zero_array(mem_ctx, struct sdap_id_conn_ctx *, 2); if (clist == NULL) { return NULL; } clist[0] = ad_get_dom_ldap_conn(ad_ctx, dom); clist[1] = NULL; return clist; } struct sdap_id_conn_ctx ** ad_user_conn_list(TALLOC_CTX *mem_ctx, struct ad_id_ctx *ad_ctx, struct sss_domain_info *dom) { struct sdap_id_conn_ctx **clist; int cindex = 0; clist = talloc_zero_array(mem_ctx, struct sdap_id_conn_ctx *, 3); if (clist == NULL) { return NULL; } /* Try GC first for users from trusted domains, but go to LDAP * for users from non-trusted domains to get all POSIX attrs */ if (dp_opt_get_bool(ad_ctx->ad_options->basic, AD_ENABLE_GC) && IS_SUBDOMAIN(dom)) { clist[cindex] = ad_ctx->gc_ctx; clist[cindex]->ignore_mark_offline = true; cindex++; } /* Users from primary domain can be just downloaded from LDAP. * The domain's LDAP connection also works as a fallback */ clist[cindex] = ad_get_dom_ldap_conn(ad_ctx, dom); return clist; } errno_t ad_inherit_opts_if_needed(struct dp_option *parent_opts, struct dp_option *subdom_opts, struct confdb_ctx *cdb, const char *subdom_conf_path, int opt_id) { int ret; bool is_default = true; char *dummy = NULL; switch (parent_opts[opt_id].type) { case DP_OPT_STRING: is_default = (dp_opt_get_cstring(parent_opts, opt_id) == NULL); break; case DP_OPT_BOOL: /* For booleans it is hard to say if the option is set or not since * both possible values are valid ones. So we check if the value is * different from the default and skip if it is the default. In this * case the sub-domain option would either be the default as well or * manully set and in both cases we do not have to change it. */ is_default = (parent_opts[opt_id].val.boolean == parent_opts[opt_id].def_val.boolean); break; default: DEBUG(SSSDBG_TRACE_FUNC, "Unsupported type, skipping.\n"); } if (!is_default) { ret = confdb_get_string(cdb, NULL, subdom_conf_path, parent_opts[opt_id].opt_name, NULL, &dummy); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed.\n"); goto done; } if (dummy == NULL) { DEBUG(SSSDBG_CONF_SETTINGS, "Option [%s] is set in parent domain but not set for " "sub-domain, inheriting it from parent.\n", parent_opts[opt_id].opt_name); dp_option_inherit(opt_id, parent_opts, subdom_opts); } } ret = EOK; done: talloc_free(dummy); return ret; } errno_t ad_options_switch_site(struct ad_options *ad_options, struct be_ctx *be_ctx, const char *new_site, const char *new_forest) { const char *site; const char *forest; errno_t ret; /* Switch forest. */ if (new_forest != NULL && (ad_options->current_forest == NULL || strcmp(ad_options->current_forest, new_forest) != 0)) { forest = talloc_strdup(ad_options, new_forest); if (forest == NULL) { return ENOMEM; } talloc_zfree(ad_options->current_forest); ad_options->current_forest = forest; } if (new_site == NULL) { return EOK; } if (ad_options->current_site != NULL && strcmp(ad_options->current_site, new_site) == 0) { return EOK; } site = talloc_strdup(ad_options, new_site); if (site == NULL) { return ENOMEM; } talloc_zfree(ad_options->current_site); ad_options->current_site = site; ret = sysdb_set_site(be_ctx->domain, ad_options->current_site); if (ret != EOK) { /* Not fatal. */ DEBUG(SSSDBG_MINOR_FAILURE, "Unable to store site information " "[%d]: %s\n", ret, sss_strerror(ret)); } return EOK; }