diff options
-rw-r--r-- | src/providers/ad/ad_subdomains.c | 371 |
1 files changed, 362 insertions, 9 deletions
diff --git a/src/providers/ad/ad_subdomains.c b/src/providers/ad/ad_subdomains.c index 6c2e7de12..b191f733b 100644 --- a/src/providers/ad/ad_subdomains.c +++ b/src/providers/ad/ad_subdomains.c @@ -54,7 +54,9 @@ * the same forest. See http://msdn.microsoft.com/en-us/library/cc223786.aspx * for more information. */ -#define SLAVE_DOMAIN_FILTER "(&(objectclass=trustedDomain)(trustType=2)(!(msDS-TrustForestTrustInfo=*)))" +#define SLAVE_DOMAIN_FILTER_BASE "(objectclass=trustedDomain)(trustType=2)(!(msDS-TrustForestTrustInfo=*))" +#define SLAVE_DOMAIN_FILTER "(&"SLAVE_DOMAIN_FILTER_BASE")" +#define FOREST_ROOT_FILTER_FMT "(&"SLAVE_DOMAIN_FILTER_BASE"(cn=%s))" /* do not refresh more often than every 5 seconds for now */ #define AD_SUBDOMAIN_REFRESH_LIMIT 5 @@ -80,6 +82,11 @@ struct ad_subdomains_req_ctx { char *current_filter; size_t base_iter; + struct ad_id_ctx *root_id_ctx; + struct sdap_id_op *root_op; + size_t root_base_iter; + struct sysdb_attrs *root_domain; + size_t reply_count; struct sysdb_attrs **reply; @@ -461,6 +468,7 @@ static errno_t ad_subdom_reinit(struct ad_subdomains_ctx *ctx) static void ad_subdomains_get_conn_done(struct tevent_req *req); static void ad_subdomains_master_dom_done(struct tevent_req *req); +static errno_t ad_subdomains_get_root(struct ad_subdomains_req_ctx *ctx); static errno_t ad_subdomains_get_slave(struct ad_subdomains_req_ctx *ctx); static void ad_subdomains_retrieve(struct ad_subdomains_ctx *ctx, @@ -481,6 +489,10 @@ static void ad_subdomains_retrieve(struct ad_subdomains_ctx *ctx, req_ctx->sd_ctx = ctx; req_ctx->current_filter = NULL; req_ctx->base_iter = 0; + req_ctx->root_base_iter = 0; + req_ctx->root_id_ctx = NULL; + req_ctx->root_op = NULL; + req_ctx->root_domain = NULL; req_ctx->reply_count = 0; req_ctx->reply = NULL; @@ -575,7 +587,20 @@ static void ad_subdomains_master_dom_done(struct tevent_req *req) goto done; } - ret = ad_subdomains_get_slave(ctx); + if (strcasecmp(ctx->sd_ctx->be_ctx->domain->name, ctx->forest) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "SSSD needs to look up the forest root domain\n"); + ret = ad_subdomains_get_root(ctx); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Connected to forest root, looking up child domains..\n"); + + ctx->root_op = ctx->sdap_op; + ctx->root_id_ctx = ctx->sd_ctx->ad_id_ctx; + + ret = ad_subdomains_get_slave(ctx); + } + if (ret == EAGAIN) { return; } else if (ret != EOK) { @@ -586,6 +611,243 @@ done: be_req_terminate(ctx->be_req, DP_ERR_FATAL, ret, NULL); } +static void ad_subdomains_get_root_domain_done(struct tevent_req *req); + +static errno_t ad_subdomains_get_root(struct ad_subdomains_req_ctx *ctx) +{ + struct tevent_req *req; + struct sdap_search_base *base; + struct sdap_id_ctx *sdap_id_ctx; + char *filter; + const char *forest_root_attrs[] = { AD_AT_FLATNAME, AD_AT_TRUST_PARTNER, + AD_AT_SID, AD_AT_TRUST_TYPE, + AD_AT_TRUST_ATTRS, NULL }; + + sdap_id_ctx = ctx->sd_ctx->sdap_id_ctx; + base = sdap_id_ctx->opts->sdom->search_bases[ctx->root_base_iter]; + if (base == NULL) { + return EOK; + } + + filter = talloc_asprintf(ctx, FOREST_ROOT_FILTER_FMT, ctx->forest); + if (filter == NULL) { + return ENOMEM; + } + + req = sdap_get_generic_send(ctx, ctx->sd_ctx->be_ctx->ev, + sdap_id_ctx->opts, + sdap_id_op_handle(ctx->sdap_op), + base->basedn, LDAP_SCOPE_SUBTREE, + filter, forest_root_attrs, + NULL, 0, + dp_opt_get_int(sdap_id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, ad_subdomains_get_root_domain_done, ctx); + return EAGAIN; +} + +static struct ad_id_ctx *ads_get_root_id_ctx(struct ad_subdomains_req_ctx *ctx); +static void ad_subdomains_root_conn_done(struct tevent_req *req); + +static void ad_subdomains_get_root_domain_done(struct tevent_req *req) +{ + int ret; + size_t reply_count; + struct sysdb_attrs **reply = NULL; + struct ad_subdomains_req_ctx *ctx; + int dp_error = DP_ERR_FATAL; + bool has_changes; + + ctx = tevent_req_callback_data(req, struct ad_subdomains_req_ctx); + + ret = sdap_get_generic_recv(req, ctx, &reply_count, &reply); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send request failed.\n"); + goto fail; + } + + if (reply_count == 0) { + /* If no root domain was found in the default search base, try the + * next one, if available + */ + ctx->root_base_iter++; + ret = ad_subdomains_get_root(ctx); + if (ret == EAGAIN) { + return; + } + + goto fail; + } else if (reply_count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Multiple results for root domain search, " + "domain list might be incomplete!\n"); + + ctx->root_op = ctx->sdap_op; + ctx->root_id_ctx = ctx->sd_ctx->ad_id_ctx; + + ret = ad_subdomains_get_slave(ctx); + if (ret == EAGAIN) { + return; + } + + goto fail; + } + /* Exactly one result, good. */ + + /* We won't use the operation to the local LDAP anymore, but + * read from the forest root + */ + ret = sdap_id_op_done(ctx->sdap_op, ret, &dp_error); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No AD server is available, cannot get the " + "subdomain list while offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to search the AD server: [%d](%s)\n", + ret, strerror(ret)); + } + goto fail; + } + + ret = ad_subdomains_refresh(ctx->sd_ctx, 1, reply, &has_changes); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_subdomains_refresh failed.\n"); + goto fail; + } + + if (has_changes) { + ret = ad_subdom_reinit(ctx->sd_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not reinitialize subdomains\n"); + goto fail; + } + } + + ctx->root_domain = reply[0]; + ctx->root_id_ctx = ads_get_root_id_ctx(ctx); + if (ctx->root_id_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot create id ctx for the root domain\n"); + ret = EFAULT; + goto fail; + } + + ctx->root_op = sdap_id_op_create(ctx, + ctx->root_id_ctx->ldap_ctx->conn_cache); + if (ctx->root_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + ret = ENOMEM; + goto fail; + } + + req = sdap_id_op_connect_send(ctx->root_op, ctx, &ret); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto fail; + } + + tevent_req_set_callback(req, ad_subdomains_root_conn_done, ctx); + return; + +fail: + if (ret == EOK) { + ctx->sd_ctx->last_refreshed = time(NULL); + dp_error = DP_ERR_OK; + } + be_req_terminate(ctx->be_req, dp_error, ret, NULL); +} + +static struct ad_id_ctx *ads_get_root_id_ctx(struct ad_subdomains_req_ctx *ctx) +{ + errno_t ret; + const char *name; + struct sss_domain_info *root; + struct sdap_domain *sdom; + struct ad_id_ctx *root_id_ctx; + + ret = sysdb_attrs_get_string(ctx->root_domain, AD_AT_TRUST_PARTNER, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + return NULL; + } + + /* With a subsequent run, the root should already be known */ + root = find_subdomain_by_name(ctx->sd_ctx->be_ctx->domain, + name, false); + if (root == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Could not find the root domain\n"); + return NULL; + } + + sdom = sdap_domain_get(ctx->sd_ctx->ad_id_ctx->sdap_id_ctx->opts, root); + if (sdom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the sdom for %s!\n", root->name); + return NULL; + } + + if (sdom->pvt == NULL) { + ret = ad_subdom_ad_ctx_new(ctx->sd_ctx->be_ctx, + ctx->sd_ctx->ad_id_ctx, + root, + &root_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_subdom_ad_ctx_new failed.\n"); + return NULL; + } + sdom->pvt = root_id_ctx; + } else { + root_id_ctx = sdom->pvt; + } + + return root_id_ctx; +} + +static void ad_subdomains_root_conn_done(struct tevent_req *req) +{ + int ret; + int dp_error = DP_ERR_FATAL; + struct ad_subdomains_req_ctx *ctx; + + ctx = tevent_req_callback_data(req, struct ad_subdomains_req_ctx); + + ret = sdap_id_op_connect_recv(req, &dp_error); + talloc_zfree(req); + if (ret) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No AD server is available, cannot get the " + "subdomain list while offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to AD server: [%d](%s)\n", + ret, strerror(ret)); + } + + goto fail; + } + + ret = ad_subdomains_get_slave(ctx); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto fail; + } + +fail: + be_req_terminate(ctx->be_req, dp_error, ret, NULL); +} + static void ad_subdomains_get_slave_domain_done(struct tevent_req *req); static errno_t ad_subdomains_get_slave(struct ad_subdomains_req_ctx *ctx) @@ -596,18 +858,18 @@ static errno_t ad_subdomains_get_slave(struct ad_subdomains_req_ctx *ctx) AD_AT_SID, AD_AT_TRUST_TYPE, AD_AT_TRUST_ATTRS, NULL }; - base = ctx->sd_ctx->sdap_id_ctx->opts->sdom->search_bases[ctx->base_iter]; + base = ctx->root_id_ctx->sdap_id_ctx->opts->sdom->search_bases[ctx->base_iter]; if (base == NULL) { return EOK; } req = sdap_get_generic_send(ctx, ctx->sd_ctx->be_ctx->ev, - ctx->sd_ctx->sdap_id_ctx->opts, - sdap_id_op_handle(ctx->sdap_op), + ctx->root_id_ctx->sdap_id_ctx->opts, + sdap_id_op_handle(ctx->root_op), base->basedn, LDAP_SCOPE_SUBTREE, SLAVE_DOMAIN_FILTER, slave_dom_attrs, NULL, 0, - dp_opt_get_int(ctx->sd_ctx->sdap_id_ctx->opts->basic, + dp_opt_get_int(ctx->root_id_ctx->sdap_id_ctx->opts->basic, SDAP_SEARCH_TIMEOUT), false); @@ -620,6 +882,68 @@ static errno_t ad_subdomains_get_slave(struct ad_subdomains_req_ctx *ctx) return EAGAIN; } +static errno_t ad_subdomains_process(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + size_t nsd, struct sysdb_attrs **sd, + struct sysdb_attrs *root, + size_t *_nsd_out, + struct sysdb_attrs ***_sd_out) +{ + size_t i, sdi; + struct sysdb_attrs **sd_out; + const char *sd_name; + errno_t ret; + + if (root == NULL) { + /* We are connected directly to the root domain. The 'sd' + * list is complete and we can just use it + */ + *_nsd_out = nsd; + *_sd_out = sd; + return EOK; + } + + /* If we searched for root separately, we must: + * a) treat the root domain as a subdomain + * b) filter the subdomain we are connected to from the subdomain + * list, from our point of view, it's the master domain + */ + sd_out = talloc_zero_array(mem_ctx, struct sysdb_attrs *, nsd+1); + if (sd_out == NULL) { + return ENOMEM; + } + + sdi = 0; + for (i = 0; i < nsd; i++) { + ret = sysdb_attrs_get_string(sd[i], AD_AT_TRUST_PARTNER, &sd_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto fail; + } + + if (strcasecmp(sd_name, domain->name) == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Not including primary domain %s in the subdomain list\n", + domain->name); + continue; + } + + sd_out[sdi] = talloc_steal(sd_out, sd[i]); + sdi++; + } + + /* Now include the root */ + sd_out[sdi] = talloc_steal(sd_out, root); + + *_nsd_out = sdi+1; + *_sd_out = sd_out; + return EOK; + +fail: + talloc_free(sd_out); + return ret; +} + static void ad_subdomains_get_slave_domain_done(struct tevent_req *req) { int ret; @@ -628,6 +952,8 @@ static void ad_subdomains_get_slave_domain_done(struct tevent_req *req) struct ad_subdomains_req_ctx *ctx; int dp_error = DP_ERR_FATAL; bool refresh_has_changes = false; + size_t nsubdoms; + struct sysdb_attrs **subdoms; ctx = tevent_req_callback_data(req, struct ad_subdomains_req_ctx); @@ -653,13 +979,40 @@ static void ad_subdomains_get_slave_domain_done(struct tevent_req *req) ctx->base_iter++; ret = ad_subdomains_get_slave(ctx); if (ret == EAGAIN) { + /* Search in progress */ + return; + } + + ret = sdap_id_op_done(ctx->root_op, ret, &dp_error); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No AD server is available, cannot get the " + "subdomain list while offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to search the AD server: [%d](%s)\n", + ret, strerror(ret)); + } + tevent_req_error(req, ret); + return; + } + + /* Based on whether we are connected to the forest root or not, we might + * need to exclude the subdomain we are connected to from the list of + * subdomains + */ + ret = ad_subdomains_process(ctx, ctx->sd_ctx->be_ctx->domain, + ctx->reply_count, ctx->reply, + ctx->root_domain, &nsubdoms, &subdoms); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("Cannot process subdomain list\n")); + tevent_req_error(req, ret); return; - } else if (ret != EOK) { - goto done; } /* Got all the subdomains, let's process them */ - ret = ad_subdomains_refresh(ctx->sd_ctx, ctx->reply_count, ctx->reply, + ret = ad_subdomains_refresh(ctx->sd_ctx, nsubdoms, subdoms, &refresh_has_changes); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to refresh subdomains.\n"); |