diff options
Diffstat (limited to 'src/providers/ipa/ipa_subdomains_server.c')
-rw-r--r-- | src/providers/ipa/ipa_subdomains_server.c | 1215 |
1 files changed, 1215 insertions, 0 deletions
diff --git a/src/providers/ipa/ipa_subdomains_server.c b/src/providers/ipa/ipa_subdomains_server.c new file mode 100644 index 0000000..aaedf62 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_server.c @@ -0,0 +1,1215 @@ +/* + SSSD + + IPA Subdomains Module - server mode + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2015 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" + +/* These constants are defined in MS-ADTS 6.1.6.7.1 + * https://msdn.microsoft.com/en-us/library/cc223768.aspx + */ +#define LSA_TRUST_DIRECTION_INBOUND 0x00000001 +#define LSA_TRUST_DIRECTION_OUTBOUND 0x00000002 +#define LSA_TRUST_DIRECTION_MASK (LSA_TRUST_DIRECTION_INBOUND | LSA_TRUST_DIRECTION_OUTBOUND) + +static char *forest_keytab(TALLOC_CTX *mem_ctx, const char *forest) +{ + return talloc_asprintf(mem_ctx, + "%s/%s.keytab", IPA_TRUST_KEYTAB_DIR, forest); +} + +static char *subdomain_trust_princ(TALLOC_CTX *mem_ctx, + const char *forest_realm, + struct sss_domain_info *sd) +{ + if (sd->parent->flat_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown flat name for parent %s\n", sd->parent->name); + return NULL; + } + + return talloc_asprintf(mem_ctx, "%s$@%s", + sd->parent->flat_name, forest_realm); +} + +static uint32_t default_direction(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct sysdb_attrs *attrs) +{ + struct ldb_dn *dn = NULL; + uint32_t direction; + + dn = ipa_subdom_ldb_dn(mem_ctx, ldb_ctx, attrs); + if (dn == NULL) { + /* Shouldn't happen, but let's try system keytab in this case */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot determine subdomain DN, falling back to two-way trust\n"); + return (LSA_TRUST_DIRECTION_INBOUND|LSA_TRUST_DIRECTION_OUTBOUND); + } + + if (ipa_subdom_is_member_dom(dn) == true) { + /* It's expected member domains do not have the direction */ + direction = 0; + } else { + /* Old server? Default to 2way trust */ + direction = (LSA_TRUST_DIRECTION_INBOUND|LSA_TRUST_DIRECTION_OUTBOUND); + } + + talloc_free(dn); + return direction; +} + +errno_t ipa_server_get_trust_direction(struct sysdb_attrs *sd, + struct ldb_context *ldb_ctx, + uint32_t *_direction) +{ + uint32_t ipa_trust_direction = 0; + uint32_t direction; + int ret; + + ret = sysdb_attrs_get_uint32_t(sd, IPA_TRUST_DIRECTION, + &ipa_trust_direction); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Raw %s value: %d\n", IPA_TRUST_DIRECTION, ipa_trust_direction); + if (ret == ENOENT) { + direction = default_direction(sd, ldb_ctx, sd); + } else if (ret == EOK) { + /* Just store the AD value in SYSDB, we will check it while we're + * trying to use the trust */ + direction = ipa_trust_direction; + } else { + return ret; + } + + *_direction = direction; + return EOK; +} + +const char *ipa_trust_dir2str(uint32_t direction) +{ + if ((direction & LSA_TRUST_DIRECTION_OUTBOUND) + && (direction & LSA_TRUST_DIRECTION_INBOUND)) { + return "two-way trust"; + } else if (direction & LSA_TRUST_DIRECTION_OUTBOUND) { + return "one-way outbound: local domain is trusted by remote domain"; + } else if (direction & LSA_TRUST_DIRECTION_INBOUND) { + return "one-way inbound: local domain trusts the remote domain"; + } else if (direction == 0) { + return "not set"; + } + + return "unknown"; +} + +#ifndef IPA_GETKEYTAB_TIMEOUT +#define IPA_GETKEYTAB_TIMEOUT 5 +#endif /* IPA_GETKEYTAB_TIMEOUT */ + +static struct ad_options * +ipa_create_1way_trust_ctx(struct ipa_id_ctx *id_ctx, + struct be_ctx *be_ctx, + const char *subdom_conf_path, + const char *forest, + const char *forest_realm, + struct sss_domain_info *subdom) +{ + char *keytab; + char *principal; + struct ad_options *ad_options; + + keytab = forest_keytab(id_ctx, forest); + principal = subdomain_trust_princ(id_ctx, forest_realm, subdom); + if (keytab == NULL || principal == NULL) { + return NULL; + } + + ad_options = ad_create_1way_trust_options(id_ctx, + be_ctx->cdb, + subdom_conf_path, + be_ctx->provider, + subdom, + id_ctx->server_mode->hostname, + keytab, + principal); + if (ad_options == NULL) { + talloc_free(keytab); + talloc_free(principal); + return NULL; + } + + return ad_options; +} + +static struct ad_options *ipa_ad_options_new(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom) +{ + struct ad_options *ad_options = NULL; + uint32_t direction; + const char *forest; + const char *forest_realm; + char *subdom_conf_path; + int ret; + + /* Trusts are only established with forest roots */ + direction = subdom->forest_root->trust_direction; + forest_realm = subdom->forest_root->realm; + forest = subdom->forest_root->forest; + + subdom_conf_path = subdomain_create_conf_path(id_ctx, subdom); + if (subdom_conf_path == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "subdom_conf_path failed\n"); + return NULL; + } + + /* In both inbound and outbound trust cases we should be + * using trusted domain object in a trusted domain space, + * thus we always should be initializing principals/keytabs + * as if we are running one-way trust */ + if (direction & LSA_TRUST_DIRECTION_MASK) { + ad_options = ipa_create_1way_trust_ctx(id_ctx, be_ctx, + subdom_conf_path, forest, + forest_realm, subdom); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported trust direction!\n"); + ad_options = NULL; + } + + if (ad_options == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD options\n"); + talloc_free(subdom_conf_path); + return NULL; + } + + ret = ad_inherit_opts_if_needed(id_ctx->ipa_options->id->basic, + ad_options->id->basic, be_ctx->cdb, + subdom_conf_path, SDAP_SASL_MECH); + talloc_free(subdom_conf_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to inherit option [%s] to sub-domain [%s]. " + "This error is ignored but might cause issues or unexpected " + "behavior later on.\n", + id_ctx->ipa_options->id->basic[SDAP_SASL_MECH].opt_name, + subdom->name); + + return NULL; + } + + return ad_options; +} + + +static errno_t +ipa_ad_ctx_new(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom, + struct ad_id_ctx **_ad_id_ctx) +{ + struct ad_options *ad_options; + struct ad_id_ctx *ad_id_ctx; + const char *gc_service_name; + const char *service_name; + struct ad_srv_plugin_ctx *srv_ctx; + const char *ad_domain; + const char *ad_site_override; + const char *ad_servers; + const char *ad_backup_servers; + struct sdap_domain *sdom; + errno_t ret; + const char *extra_attrs; + bool use_kdcinfo = false; + size_t n_lookahead_primary = (size_t)-1; + size_t n_lookahead_backup = (size_t)-1; + + ad_domain = subdom->name; + DEBUG(SSSDBG_TRACE_LIBS, "Setting up AD subdomain %s\n", subdom->name); + + ad_options = ipa_ad_options_new(be_ctx, id_ctx, subdom); + if (ad_options == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD options\n"); + talloc_free(ad_options); + return ENOMEM; + } + + extra_attrs = dp_opt_get_string(id_ctx->sdap_id_ctx->opts->basic, + SDAP_USER_EXTRA_ATTRS); + if (extra_attrs != NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Setting extra attrs for subdomain [%s] to [%s].\n", ad_domain, + extra_attrs); + + ret = dp_opt_set_string(ad_options->id->basic, SDAP_USER_EXTRA_ATTRS, + extra_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_opt_set_string failed.\n"); + talloc_free(ad_options); + return ret; + } + + ret = sdap_extend_map_with_list(ad_options->id, ad_options->id, + SDAP_USER_EXTRA_ATTRS, + ad_options->id->user_map, + SDAP_OPTS_USER, + &ad_options->id->user_map, + &ad_options->id->user_map_cnt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_extend_map_with_list failed.\n"); + talloc_free(ad_options); + return ret; + } + } else { + DEBUG(SSSDBG_TRACE_ALL, "No extra attrs set.\n"); + } + + gc_service_name = talloc_asprintf(ad_options, "sd_gc_%s", subdom->name); + if (gc_service_name == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + + service_name = talloc_asprintf(ad_options, "sd_%s", subdom->name); + if (service_name == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + + ad_servers = dp_opt_get_string(ad_options->basic, AD_SERVER); + ad_backup_servers = dp_opt_get_string(ad_options->basic, AD_BACKUP_SERVER); + + if (id_ctx->ipa_options != NULL && id_ctx->ipa_options->auth != NULL) { + use_kdcinfo = dp_opt_get_bool(id_ctx->ipa_options->auth, + KRB5_USE_KDCINFO); + sss_krb5_parse_lookahead( + dp_opt_get_string(id_ctx->ipa_options->auth, KRB5_KDCINFO_LOOKAHEAD), + &n_lookahead_primary, + &n_lookahead_backup); + } + + DEBUG(SSSDBG_TRACE_ALL, + "Init failover for [%s][%s] with use_kdcinfo [%s].\n", + subdom->name, subdom->realm, use_kdcinfo ? "true" : "false"); + + /* Set KRB5 realm to same as the one of IPA when IPA + * is able to attach PAC. For testing, use hardcoded. */ + /* Why? */ + ret = ad_failover_init(ad_options, be_ctx, ad_servers, ad_backup_servers, + subdom->realm, + service_name, gc_service_name, + subdom->name, use_kdcinfo, false, + n_lookahead_primary, n_lookahead_backup, + &ad_options->service); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD failover\n"); + talloc_free(ad_options); + return ret; + } + + ad_id_ctx = ad_id_ctx_init(ad_options, be_ctx); + if (ad_id_ctx == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + ad_id_ctx->sdap_id_ctx->opts = ad_options->id; + ad_options->id_ctx = ad_id_ctx; + + ad_site_override = dp_opt_get_string(ad_options->basic, AD_SITE); + + /* use AD plugin */ + srv_ctx = ad_srv_plugin_ctx_init(be_ctx, be_ctx, be_ctx->be_res, + default_host_dbs, + ad_id_ctx->ad_options->id, + ad_id_ctx->ad_options, + id_ctx->server_mode->hostname, + ad_domain, + ad_site_override); + if (srv_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + be_fo_set_srv_lookup_plugin(be_ctx, ad_srv_plugin_send, + ad_srv_plugin_recv, srv_ctx, "AD"); + + ret = sdap_domain_subdom_add(ad_id_ctx->sdap_id_ctx, + ad_id_ctx->sdap_id_ctx->opts->sdom, + subdom->parent); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize sdap domain\n"); + talloc_free(ad_options); + return ret; + } + + sdom = sdap_domain_get(ad_id_ctx->sdap_id_ctx->opts, subdom); + if (sdom == NULL) { + return EFAULT; + } + + ret = ad_set_search_bases(ad_options->id, sdom); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD search bases\n"); + talloc_free(ad_options); + return ret; + } + + sdap_inherit_options(subdom->parent->sd_inherit, + id_ctx->sdap_id_ctx->opts, + ad_id_ctx->sdap_id_ctx->opts); + + ret = sdap_id_setup_tasks(be_ctx, + ad_id_ctx->sdap_id_ctx, + sdom, + ldap_id_enumeration_send, + ldap_id_enumeration_recv, + ad_id_ctx->sdap_id_ctx); + if (ret != EOK) { + talloc_free(ad_options); + return ret; + } + + sdom->pvt = ad_id_ctx; + + /* Set up the ID mapping object */ + ad_id_ctx->sdap_id_ctx->opts->idmap_ctx = + id_ctx->sdap_id_ctx->opts->idmap_ctx; + + /* Set up the certificate mapping context */ + ad_id_ctx->sdap_id_ctx->opts->sdap_certmap_ctx = + id_ctx->sdap_id_ctx->opts->sdap_certmap_ctx; + + *_ad_id_ctx = ad_id_ctx; + return EOK; +} + +struct ipa_getkeytab_state { + int child_status; + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; +}; + +static void ipa_getkeytab_exec(const char *ccache, + const char *server, + const char *principal, + const char *keytab_path); +static void ipa_getkeytab_done(int child_status, + struct tevent_signal *sige, + void *pvt); +static void ipa_getkeytab_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +static struct tevent_req *ipa_getkeytab_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ccache, + const char *server, + const char *principal, + const char *keytab) + + +{ + errno_t ret; + struct tevent_req *req = NULL; + struct ipa_getkeytab_state *state; + pid_t child_pid; + struct timeval tv; + + req = tevent_req_create(mem_ctx, &state, struct ipa_getkeytab_state); + if (req == NULL) { + return NULL; + } + state->child_status = EFAULT; + + if (server == NULL || principal == NULL || keytab == NULL) { + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Retrieving keytab for %s from %s into %s using ccache %s\n", + principal, server, keytab, ccache); + + child_pid = fork(); + if (child_pid == 0) { /* child */ + ipa_getkeytab_exec(ccache, server, principal, keytab); + } else if (child_pid > 0) { /* parent */ + /* Set up SIGCHLD handler */ + ret = child_handler_setup(ev, child_pid, ipa_getkeytab_done, req, + &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_IPA_GETKEYTAB_FAILED; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(IPA_GETKEYTAB_TIMEOUT, 0); + state->timeout_handler = tevent_add_timer(ev, req, tv, + ipa_getkeytab_timeout, req); + if(state->timeout_handler == NULL) { + ret = ERR_IPA_GETKEYTAB_FAILED; + goto done; + } + + /* Now either wait for the timeout to fire or the child + * to finish + */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void ipa_getkeytab_exec(const char *ccache, + const char *server, + const char *principal, + const char *keytab_path) +{ + errno_t ret; + int debug_fd; + const char *gkt_env[3] = { NULL, "_SSS_LOOPS=NO", NULL }; + + if (debug_level >= SSSDBG_TRACE_LIBS) { + debug_fd = get_fd_from_debug_file(); + ret = dup2(debug_fd, STDERR_FILENO); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "dup2 failed [%d][%s].\n", ret, sss_strerror(ret)); + /* stderr is not fatal */ + } + } + + gkt_env[0] = talloc_asprintf(NULL, "KRB5CCNAME=%s", ccache); + if (gkt_env[0] == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to format KRB5CCNAME\n"); + exit(1); + } + + /* ipa-getkeytab cannot add keys to an empty file, let's unlink it and only + * use the filename */ + ret = unlink(keytab_path); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to unlink the temporary ccname [%d][%s]\n", + ret, sss_strerror(ret)); + exit(1); + } + + errno = 0; + ret = execle(IPA_GETKEYTAB_PATH, IPA_GETKEYTAB_PATH, + "-r", "-s", server, "-p", principal, "-k", keytab_path, NULL, + gkt_env); + + DEBUG(SSSDBG_FATAL_FAILURE, + "execle returned %d, this shouldn't happen!\n", ret); + + /* The child should never end up here */ + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "execle failed [%d][%s].\n", ret, sss_strerror(ret)); + exit(1); +} + +static void ipa_getkeytab_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct ipa_getkeytab_state *state = + tevent_req_data(req, struct ipa_getkeytab_state); + + state->child_status = child_status; + + if (WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa-getkeytab failed with status [%d]\n", child_status); + tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED); + return; + } + + if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa-getkeytab was terminated by signal [%d]\n", + WTERMSIG(child_status)); + tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED); + return; + } + + tevent_req_done(req); +} + +static void ipa_getkeytab_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct ipa_getkeytab_state *state = + tevent_req_data(req, struct ipa_getkeytab_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for retrieving keytab from IPA server\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED); +} + +static errno_t ipa_getkeytab_recv(struct tevent_req *req, int *child_status) +{ + struct ipa_getkeytab_state *state = + tevent_req_data(req, struct ipa_getkeytab_state); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "ipa-getkeytab status %d\n", state->child_status); + if (child_status) { + *child_status = state->child_status; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t ipa_check_keytab(const char *keytab, + uid_t kt_owner_uid, + gid_t kt_owner_gid) +{ + errno_t ret; + + ret = check_file(keytab, getuid(), getgid(), S_IFREG|0600, 0, NULL, false); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "Keytab %s is not present\n", keytab); + goto done; + } else if (ret != EOK) { + if (kt_owner_uid) { + ret = check_file(keytab, kt_owner_uid, kt_owner_gid, + S_IFREG|0600, 0, NULL, false); + } + + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to check for %s\n", keytab); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Keytab %s is not present\n", keytab); + } + } + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "keytab %s already exists\n", keytab); + ret = EOK; +done: + return ret; +} + +struct ipa_server_trusted_dom_setup_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *subdom; + + uint32_t direction; + const char *forest; + const char *keytab; + char *new_keytab; + const char *principal; + const char *forest_realm; + const char *ccache; +}; + +static errno_t ipa_server_trusted_dom_setup_1way(struct tevent_req *req); +static void ipa_server_trust_1way_kt_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_server_trusted_dom_setup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom) +{ + struct tevent_req *req = NULL; + struct ipa_server_trusted_dom_setup_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_server_trusted_dom_setup_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->be_ctx = be_ctx; + state->id_ctx = id_ctx; + state->subdom = subdom; + + /* Trusts are only established with forest roots */ + if (subdom->forest_root == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Subdomain %s has no forest root?\n", subdom->name); + ret = ERR_TRUST_FOREST_UNKNOWN; + goto immediate; + } + + state->direction = subdom->forest_root->trust_direction; + state->forest = subdom->forest_root->forest; + state->forest_realm = subdom->forest_root->realm; + state->ccache = talloc_asprintf(state, "%s/ccache_%s", + DB_PATH, subdom->parent->realm); + if (state->ccache == NULL) { + ret = ENOMEM; + goto immediate; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Trust direction of subdom %s from forest %s is: %s\n", + subdom->name, state->forest, + ipa_trust_dir2str(state->direction)); + + /* For both inbound and outbound trusts use a special keytab + * as this allows us to reuse the same logic in FreeIPA for + * both Microsoft AD and Samba AD */ + if (state->direction & LSA_TRUST_DIRECTION_MASK) { + /* Need special keytab */ + ret = ipa_server_trusted_dom_setup_1way(req); + if (ret == EAGAIN) { + /* In progress.. */ + return req; + } else if (ret == EOK) { + /* Keytab available, shortcut */ + ret = EOK; + goto immediate; + } + } else { + /* Even unset is an error at this point */ + DEBUG(SSSDBG_OP_FAILURE, + "Subdomain %s has trust direction %d\n", + subdom->name, subdom->trust_direction); + ret = ERR_TRUST_NOT_SUPPORTED; + } + +immediate: + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not add trusted subdomain %s from forest %s\n", + subdom->name, state->forest); + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_server_trusted_dom_setup_1way(struct tevent_req *req) +{ + errno_t ret; + struct tevent_req *subreq = NULL; + struct ipa_server_trusted_dom_setup_state *state = + tevent_req_data(req, struct ipa_server_trusted_dom_setup_state); + const char *hostname; + + state->keytab = forest_keytab(state, state->forest); + if (state->keytab == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up ipa_get_keytab\n"); + return EIO; + } + + state->new_keytab = talloc_asprintf(state, "%sXXXXXX", state->keytab); + if (state->new_keytab == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set up ipa_get_keytab. talloc_asprintf() failed\n"); + return ENOMEM; + } + + ret = sss_unique_filename(state, state->new_keytab); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create temporary keytab name\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Will re-fetch keytab for %s\n", state->subdom->name); + + hostname = dp_opt_get_string(state->id_ctx->ipa_options->basic, + IPA_HOSTNAME); + + state->principal = subdomain_trust_princ(state, + state->forest_realm, + state->subdom); + if (state->principal == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up ipa_get_keytab\n"); + return EIO; + } + + subreq = ipa_getkeytab_send(state->be_ctx, state->be_ctx->ev, + state->ccache, + hostname, + state->principal, + state->new_keytab); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_server_trust_1way_kt_done, req); + return EAGAIN; +} + +static void ipa_server_trust_1way_kt_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_server_trusted_dom_setup_state *state = + tevent_req_data(req, struct ipa_server_trusted_dom_setup_state); + + ret = ipa_getkeytab_recv(subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + /* Do not fail here, but try to check and use the previous keytab, + * if any */ + DEBUG(SSSDBG_MINOR_FAILURE, "ipa_getkeytab_recv failed: %d\n", ret); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Keytab successfully retrieved to %s\n", state->new_keytab); + } + + ret = ipa_check_keytab(state->new_keytab, + state->id_ctx->server_mode->kt_owner_uid, + state->id_ctx->server_mode->kt_owner_gid); + if (ret == EOK) { + ret = rename(state->new_keytab, state->keytab); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Keytab renamed to %s\n", state->keytab); + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Trying to recover and use the previous keytab, if available\n"); + ret = ipa_check_keytab(state->keytab, + state->id_ctx->server_mode->kt_owner_uid, + state->id_ctx->server_mode->kt_owner_gid); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "The previous keytab %s contains the expected principal\n", + state->keytab); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot use the old keytab: %d\n", ret); + /* Nothing we can do now */ + tevent_req_error(req, ret); + return; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Keytab %s contains the expected principals\n", state->new_keytab); + + DEBUG(SSSDBG_TRACE_FUNC, + "Established trust context for %s\n", state->subdom->name); + tevent_req_done(req); +} + +errno_t ipa_server_trusted_dom_setup_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ipa_server_create_trusts_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *domiter; +}; + +static errno_t ipa_server_create_trusts_step(struct tevent_req *req); +static errno_t ipa_server_create_trusts_ctx(struct tevent_req *req); +static void ipa_server_create_trusts_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_server_create_trusts_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *parent) +{ + struct tevent_req *req = NULL; + struct ipa_server_create_trusts_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_server_create_trusts_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->id_ctx = id_ctx; + state->domiter = parent; + + ret = ipa_server_create_trusts_step(req); + if (ret != EAGAIN) { + goto immediate; + } + + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_server_create_trusts_step(struct tevent_req *req) +{ + struct tevent_req *subreq = NULL; + struct ipa_ad_server_ctx *trust_iter; + struct ipa_ad_server_ctx *trust_i; + struct ipa_server_create_trusts_state *state = NULL; + + state = tevent_req_data(req, struct ipa_server_create_trusts_state); + + for (state->domiter = get_next_domain(state->domiter, SSS_GND_DESCEND); + state->domiter && IS_SUBDOMAIN(state->domiter); + state->domiter = get_next_domain(state->domiter, 0)) { + + /* Check if we already have an ID context for this subdomain */ + DLIST_FOR_EACH(trust_iter, state->id_ctx->server_mode->trusts) { + if (trust_iter->dom == state->domiter) { + break; + } + } + + /* Newly detected trust */ + if (trust_iter == NULL) { + subreq = ipa_server_trusted_dom_setup_send(state, + state->ev, + state->be_ctx, + state->id_ctx, + state->domiter); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_server_create_trusts_done, req); + return EAGAIN; + } + } + + /* Refresh all sdap_dom lists in all ipa_ad_server_ctx contexts */ + DLIST_FOR_EACH(trust_iter, state->id_ctx->server_mode->trusts) { + struct sdap_domain *sdom_a; + + sdom_a = sdap_domain_get(trust_iter->ad_id_ctx->sdap_id_ctx->opts, + trust_iter->dom); + if (sdom_a == NULL) { + continue; + } + + DLIST_FOR_EACH(trust_i, state->id_ctx->server_mode->trusts) { + struct sdap_domain *sdom_b; + + if (strcmp(trust_iter->dom->name, trust_i->dom->name) == 0) { + continue; + } + + sdom_b = sdap_domain_get(trust_i->ad_id_ctx->sdap_id_ctx->opts, + sdom_a->dom); + if (sdom_b == NULL) { + continue; + } + + /* Replace basedn and search bases from sdom_b with values + * from sdom_a */ + sdap_domain_copy_search_bases(sdom_b, sdom_a); + } + } + + return EOK; +} + +static void ipa_server_create_trusts_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = ipa_server_trusted_dom_setup_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = ipa_server_create_trusts_ctx(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = ipa_server_create_trusts_step(req); + if (ret == EOK) { + tevent_req_done(req); + return; + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + + /* Will cycle back */ +} + +static errno_t ipa_server_create_trusts_ctx(struct tevent_req *req) +{ + struct ipa_ad_server_ctx *trust_ctx; + struct ad_id_ctx *ad_id_ctx; + errno_t ret; + struct ipa_server_create_trusts_state *state = NULL; + + state = tevent_req_data(req, struct ipa_server_create_trusts_state); + + ret = ipa_ad_ctx_new(state->be_ctx, state->id_ctx, state->domiter, &ad_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create ad_id_ctx for subdomain %s\n", state->domiter->name); + return ret; + } + + trust_ctx = talloc(state->id_ctx->server_mode, struct ipa_ad_server_ctx); + if (trust_ctx == NULL) { + return ENOMEM; + } + trust_ctx->dom = state->domiter; + trust_ctx->ad_id_ctx = ad_id_ctx; + + DLIST_ADD(state->id_ctx->server_mode->trusts, trust_ctx); + return EOK; +} + +errno_t ipa_server_create_trusts_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +void ipa_ad_subdom_remove(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom) +{ + struct ipa_ad_server_ctx *iter; + struct sdap_domain *sdom; + + if (dp_opt_get_bool(id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + return; + } + + DLIST_FOR_EACH(iter, id_ctx->server_mode->trusts) { + if (iter->dom == subdom) break; + } + + if (iter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No IPA-AD context for subdomain %s\n", + subdom->name); + return; + } + + sdom = sdap_domain_get(iter->ad_id_ctx->sdap_id_ctx->opts, subdom); + if (sdom == NULL) return; + + sdap_domain_remove(iter->ad_id_ctx->sdap_id_ctx->opts, subdom); + DLIST_REMOVE(id_ctx->server_mode->trusts, iter); + + /* terminate all requests for this subdomain so we can free it */ + dp_terminate_domain_requests(be_ctx->provider, subdom->name); + talloc_zfree(sdom); +} + +struct ipa_ad_subdom_reinit_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *parent; +}; + +static void create_trusts_at_startup_done(struct tevent_req *req) +{ + errno_t ret; + + ret = ipa_server_create_trusts_recv(req); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ipa_server_create_trusts_send request failed [%d]: %s\n", + ret, sss_strerror(ret)); + } +} + +static void create_trusts_at_startup(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct tevent_req *req; + struct ipa_ad_subdom_reinit_state *state; + + state = talloc_get_type(pvt, struct ipa_ad_subdom_reinit_state); + + req = ipa_server_create_trusts_send(state, state->ev, state->be_ctx, + state->id_ctx, state->parent); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_server_create_trusts_send failed.\n"); + talloc_free(state); + return; + } + + tevent_req_set_callback(req, create_trusts_at_startup_done, state); + return; +} + +static errno_t ipa_ad_subdom_reinit(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *parent) +{ + struct tevent_immediate *imm; + struct ipa_ad_subdom_reinit_state *state; + + state = talloc(mem_ctx, struct ipa_ad_subdom_reinit_state); + if (state == NULL) { + return ENOMEM; + } + state->ev = ev; + state->be_ctx = be_ctx; + state->id_ctx = id_ctx; + state->parent = parent; + + if (dp_opt_get_bool(id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + return EOK; + } + + imm = tevent_create_immediate(mem_ctx); + if (imm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_create_immediate failed.\n"); + talloc_free(state); + return ENOMEM; + } + + tevent_schedule_immediate(imm, ev, create_trusts_at_startup, state); + return EOK; +} + +int ipa_ad_subdom_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx) +{ + char *realm; + char *hostname; + errno_t ret; + + if (dp_opt_get_bool(id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + return EOK; + } + + /* The IPA code relies on the default FQDN format to unparse user + * names. Warn loudly if the full_name_format was customized on the + * IPA server + */ + if ((strcmp(be_ctx->domain->names->fq_fmt, + CONFDB_DEFAULT_FULL_NAME_FORMAT) != 0) + && (strcmp(be_ctx->domain->names->fq_fmt, + CONFDB_DEFAULT_FULL_NAME_FORMAT_INTERNAL) != 0)) { + DEBUG(SSSDBG_FATAL_FAILURE, "%s is set to a non-default value [%s] " \ + "lookups of subdomain users will likely fail!\n", + CONFDB_FULL_NAME_FORMAT, be_ctx->domain->names->fq_fmt); + sss_log(SSS_LOG_ERR, "%s is set to a non-default value [%s] " \ + "lookups of subdomain users will likely fail!\n", + CONFDB_FULL_NAME_FORMAT, be_ctx->domain->names->fq_fmt); + /* Attempt to continue */ + } + + realm = dp_opt_get_string(id_ctx->ipa_options->basic, IPA_KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n"); + return EINVAL; + } + + hostname = dp_opt_get_string(id_ctx->ipa_options->basic, IPA_HOSTNAME); + if (hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No host name for IPA?\n"); + return EINVAL; + } + + id_ctx->server_mode = talloc_zero(id_ctx, struct ipa_server_mode_ctx); + if (id_ctx->server_mode == NULL) { + return ENOMEM; + } + id_ctx->server_mode->realm = realm; + id_ctx->server_mode->hostname = hostname; + id_ctx->server_mode->trusts = NULL; + id_ctx->server_mode->ext_groups = NULL; + id_ctx->server_mode->kt_owner_uid = 0; + id_ctx->server_mode->kt_owner_gid = 0; + + if (getuid() == 0) { + /* We need to handle keytabs created by IPA oddjob script gracefully + * even if we're running as root and IPA creates them as the SSSD user + */ + ret = sss_user_by_name_or_uid(SSSD_USER, + &id_ctx->server_mode->kt_owner_uid, + &id_ctx->server_mode->kt_owner_gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get ID of %s\n", SSSD_USER); + } + } + + ret = ipa_ad_subdom_reinit(be_ctx, be_ctx->ev, + be_ctx, id_ctx, be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_ad_subdom_refresh failed.\n"); + return ret; + } + + return EOK; +} |