/* 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/dp_dyndns.h" struct ad_server_data { bool gc; }; 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]; 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 = 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 */ dom->case_sensitive = false; /* Set this in the confdb so that the responders pick it * up when they start up. */ ret = confdb_set_bool(cdb, conf_path, "case_sensitive", dom->case_sensitive); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Could not set domain case-sensitive: [%s]\n", strerror(ret))); goto done; } DEBUG(SSSDBG_CONF_SETTINGS, ("Setting domain case-insensitive\n")); 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(TALLOC_CTX *mem_ctx, struct ad_service *service, struct be_ctx *bectx, const char *servers, struct ad_options *options, bool primary) { size_t i; errno_t ret = 0; char **list; char *ad_domain; 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; } 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])) { 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, AD_SERVICE_NAME, "gc", ad_domain, BE_FO_PROTO_TCP, false, sdata); if (ret != EOK) { DEBUG(SSSDBG_FATAL_FAILURE, ("Failed to add service discovery to failover: [%s]", 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, AD_SERVICE_NAME, "ldap", ad_domain, BE_FO_PROTO_TCP, false, sdata); 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; } /* 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, AD_SERVICE_NAME, 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, AD_SERVICE_NAME, 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(TALLOC_CTX *mem_ctx, struct ad_service *service, struct be_ctx *bectx, const char *servers, struct ad_options *options) { return _ad_servers_init(mem_ctx, service, bectx, servers, options, true); } static inline errno_t ad_backup_servers_init(TALLOC_CTX *mem_ctx, struct ad_service *service, struct be_ctx *bectx, const char *servers, struct ad_options *options) { return _ad_servers_init(mem_ctx, service, bectx, servers, options, 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; } DEBUG(SSSDBG_TRACE_LIBS, ("Comparing %s with %s\n", sd1->gc ? "GC" : "LDAP", sd2->gc ? "GC" : "LDAP")); 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, struct ad_options *options, struct ad_service **_service) { errno_t ret; TALLOC_CTX *tmp_ctx; struct ad_service *service; char *realm; 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); 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_NAME); service->gc->name = talloc_strdup(service->gc, AD_SERVICE_NAME); if (!service->sdap->name || !service->gc->name) { 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, ad_user_data_cmp); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to create failover service!\n")); goto done; } service->krb5_service->name = talloc_strdup(service->krb5_service, AD_SERVICE_NAME); if (!service->krb5_service->name) { ret = ENOMEM; goto done; } service->sdap->kinit_service_name = service->krb5_service->name; service->gc->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 (!primary_servers) { DEBUG(SSSDBG_CONF_SETTINGS, ("No primary servers defined, using service discovery\n")); primary_servers = BE_SRV_IDENTIFIER; } ret = ad_primary_servers_init(mem_ctx, service, bectx, primary_servers, options); if (ret != EOK) { goto done; } if (backup_servers) { ret = ad_backup_servers_init(mem_ctx, service, bectx, backup_servers, options); 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")); return ret; } 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; 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")); 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; } 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, "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)); 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; } /* 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->sdap, sockaddr); talloc_zfree(service->gc->uri); talloc_zfree(service->gc->sockaddr); if (sdata && sdata->gc) { service->gc->uri = talloc_asprintf(service->gc, "%s:%d", new_uri, AD_GC_PORT); service->gc->sockaddr = resolv_get_sockaddr_address(service->gc, srvaddr, AD_GC_PORT); } 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, sizeof(struct sockaddr_storage)); } if (!service->gc->uri) { DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to append to 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, ("resolv_get_sockaddr_address failed.\n")); ret = EIO; goto done; } if (service->krb5_service->write_kdcinfo) { /* Write krb5 info files */ 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 *keytab_path; 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 = sdap_domain_add(id_opts, ad_opts->id_ctx->sdap_id_ctx->be->domain, NULL); if (ret != EOK) { 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)); 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)); } 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; } /* 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->sdom->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->sdom->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->sdom->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->sdom->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->sdom->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 */ 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")); *_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; }