diff options
Diffstat (limited to 'src/providers/ad/ad_id.c')
-rw-r--r-- | src/providers/ad/ad_id.c | 1547 |
1 files changed, 1547 insertions, 0 deletions
diff --git a/src/providers/ad/ad_id.c b/src/providers/ad/ad_id.c new file mode 100644 index 0000000..09280f7 --- /dev/null +++ b/src/providers/ad/ad_id.c @@ -0,0 +1,1547 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + 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 <http://www.gnu.org/licenses/>. +*/ +#include "util/util.h" +#include "util/strtonum.h" +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_id.h" +#include "providers/ad/ad_domain_info.h" +#include "providers/ad/ad_pac.h" +#include "providers/ldap/sdap_async_enum.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_async.h" + +static bool ad_account_can_shortcut(struct sdap_idmap_ctx *idmap_ctx, + struct sss_domain_info *domain, + int filter_type, + const char *filter_value) +{ + struct sss_domain_info *dom_head = NULL; + struct sss_domain_info *sid_dom = NULL; + enum idmap_error_code err; + char *sid = NULL; + const char *csid = NULL; + uint32_t id; + bool shortcut = false; + errno_t ret; + char *endptr; + + if (!sdap_idmap_domain_has_algorithmic_mapping(idmap_ctx, domain->name, + domain->domain_id)) { + goto done; + } + + switch (filter_type) { + case BE_FILTER_IDNUM: + /* convert value to ID */ + id = strtouint32(filter_value, &endptr, 10); + if ((errno != 0) || *endptr || (filter_value == endptr)) { + ret = errno ? errno : EINVAL; + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to convert filter value to " + "number [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + /* convert the ID to its SID equivalent */ + err = sss_idmap_unix_to_sid(idmap_ctx->map, id, &sid); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, "Mapping ID [%s] to SID failed: " + "[%s]\n", filter_value, idmap_error_string(err)); + /* assume id is from a different domain */ + shortcut = true; + goto done; + } + /* fall through */ + SSS_ATTRIBUTE_FALLTHROUGH; + case BE_FILTER_SECID: + csid = sid == NULL ? filter_value : sid; + + dom_head = get_domains_head(domain); + if (dom_head == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find domain head\n"); + goto done; + } + + sid_dom = find_domain_by_sid(dom_head, csid); + if (sid_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid domain for SID:%s\n", csid); + goto done; + } + + if (strcasecmp(sid_dom->name, domain->name) != 0) { + shortcut = true; + } + break; + default: + break; + } + +done: + if (sid != NULL) { + sss_idmap_free_sid(idmap_ctx->map, sid); + } + + return shortcut; +} + +struct ad_handle_acct_info_state { + struct dp_id_data *ar; + struct sdap_id_ctx *ctx; + struct sdap_id_conn_ctx **conn; + struct sdap_domain *sdom; + size_t cindex; + struct ad_options *ad_options; + bool using_pac; + + int dp_error; + const char *err; +}; + +static errno_t ad_handle_acct_info_step(struct tevent_req *req); +static void ad_handle_acct_info_done(struct tevent_req *subreq); + +struct tevent_req * +ad_handle_acct_info_send(TALLOC_CTX *mem_ctx, + struct dp_id_data *ar, + struct sdap_id_ctx *ctx, + struct ad_options *ad_options, + struct sdap_domain *sdom, + struct sdap_id_conn_ctx **conn) +{ + struct tevent_req *req; + struct ad_handle_acct_info_state *state; + struct be_ctx *be_ctx = ctx->be; + errno_t ret; + bool shortcut; + + req = tevent_req_create(mem_ctx, &state, struct ad_handle_acct_info_state); + if (req == NULL) { + return NULL; + } + state->ar = ar; + state->ctx = ctx; + state->sdom = sdom; + state->conn = conn; + state->ad_options = ad_options; + state->cindex = 0; + + /* Try to shortcut if this is ID or SID search and it belongs to + * other domain range than is in ar->domain. */ + shortcut = ad_account_can_shortcut(ctx->opts->idmap_ctx, + sdom->dom, + ar->filter_type, + ar->filter_value); + if (shortcut) { + DEBUG(SSSDBG_TRACE_FUNC, "This ID is from different domain\n"); + ret = EOK; + goto immediate; + } + + if (sss_domain_get_state(sdom->dom) == DOM_INACTIVE) { + ret = ERR_SUBDOM_INACTIVE; + goto immediate; + } + + ret = ad_handle_acct_info_step(req); + if (ret != EAGAIN) { + goto immediate; + } + + /* Lookup in progress */ + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, be_ctx->ev); + return req; +} + +static errno_t +ad_handle_acct_info_step(struct tevent_req *req) +{ + struct tevent_req *subreq = NULL; + struct ad_handle_acct_info_state *state = tevent_req_data(req, + struct ad_handle_acct_info_state); + bool noexist_delete = false; + struct ldb_message *msg; + int ret; + + if (state->conn[state->cindex] == NULL) { + return EOK; + } + + if (state->conn[state->cindex+1] == NULL) { + noexist_delete = true; + } + + + state->using_pac = false; + if ((state->ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_INITGROUPS) { + ret = check_if_pac_is_available(state, state->sdom->dom, + state->ar, &msg); + + if (ret == EOK) { + /* evaluate PAC */ + state->using_pac = true; + subreq = ad_handle_pac_initgr_send(state, state->ctx->be, + state->ar, state->ctx, + state->sdom, + state->conn[state->cindex], + noexist_delete, + msg); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ad_handle_pac_initgr_send failed.\n"); + return ENOMEM; + } + + } + + /* Fall through if there is no PAC or any other error */ + } + + if (subreq == NULL) { + subreq = sdap_handle_acct_req_send(state, state->ctx->be, + state->ar, state->ctx, + state->sdom, + state->conn[state->cindex], + noexist_delete); + if (subreq == NULL) { + return ENOMEM; + } + } + + tevent_req_set_callback(subreq, ad_handle_acct_info_done, req); + return EAGAIN; +} + +static void +ad_handle_acct_info_done(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error; + int sdap_err; + const char *err; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_handle_acct_info_state *state = tevent_req_data(req, + struct ad_handle_acct_info_state); + + if (state->using_pac) { + ret = ad_handle_pac_initgr_recv(subreq, &dp_error, &err, &sdap_err); + } else { + ret = sdap_handle_acct_req_recv(subreq, &dp_error, &err, &sdap_err); + } + if (dp_error == DP_ERR_OFFLINE + && state->conn[state->cindex+1] != NULL + && state->conn[state->cindex]->ignore_mark_offline) { + /* This is a special case: GC does not work. + * We need to Fall back to ldap + */ + ret = EOK; + sdap_err = ENOENT; + } + talloc_zfree(subreq); + if (ret != EOK) { + /* if GC was not used dp error should be set */ + state->dp_error = dp_error; + state->err = err; + + goto fail; + } + + if (sdap_err == EOK) { + tevent_req_done(req); + return; + } else if (sdap_err != ENOENT) { + ret = EIO; + goto fail; + } + + /* Ret is only ENOENT now. Try the next connection */ + state->cindex++; + ret = ad_handle_acct_info_step(req); + if (ret != EAGAIN) { + /* No additional search in progress. Save the last + * error status, we'll be returning it. + */ + state->dp_error = dp_error; + state->err = err; + + if (ret == EOK) { + /* No more connections */ + tevent_req_done(req); + } else { + goto fail; + } + return; + } + + /* Another lookup in progress */ + return; + +fail: + if (IS_SUBDOMAIN(state->sdom->dom)) { + /* Deactivate subdomain on lookup errors instead of going + * offline completely. + * This is a stopgap, until our failover is per-domain, + * not per-backend. Unfortunately, we can't rewrite the error + * code on some reported codes only, because sdap_id_op code + * encapsulated the failover as well.. + */ + ret = ERR_SUBDOM_INACTIVE; + } + tevent_req_error(req, ret); + return; +} + +errno_t +ad_handle_acct_info_recv(struct tevent_req *req, + int *_dp_error, const char **_err) +{ + struct ad_handle_acct_info_state *state = tevent_req_data(req, + struct ad_handle_acct_info_state); + + if (_dp_error) { + *_dp_error = state->dp_error; + } + + if (_err) { + *_err = state->err; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct sdap_id_conn_ctx ** +get_conn_list(TALLOC_CTX *mem_ctx, struct ad_id_ctx *ad_ctx, + struct sss_domain_info *dom, struct dp_id_data *ar) +{ + struct sdap_id_conn_ctx **clist; + + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: /* user */ + clist = ad_user_conn_list(mem_ctx, ad_ctx, dom); + break; + case BE_REQ_BY_SECID: /* by SID */ + case BE_REQ_USER_AND_GROUP: /* get SID */ + case BE_REQ_GROUP: /* group */ + case BE_REQ_INITGROUPS: /* init groups for user */ + clist = ad_gc_conn_list(mem_ctx, ad_ctx, dom); + break; + default: + /* Requests for other object should only contact LDAP by default */ + clist = ad_ldap_conn_list(mem_ctx, ad_ctx, dom); + break; + } + + return clist; +} + +struct ad_account_info_state { + const char *err_msg; + int dp_error; +}; + +static void ad_account_info_done(struct tevent_req *subreq); + +struct tevent_req * +ad_account_info_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ad_id_ctx *id_ctx, + struct dp_id_data *data) +{ + struct sss_domain_info *domain = NULL; + struct ad_account_info_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct sdap_id_conn_ctx **clist = NULL; + struct sdap_id_ctx *sdap_id_ctx = NULL; + struct sdap_domain *sdom; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ad_account_info_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + sdap_id_ctx = id_ctx->sdap_id_ctx; + + domain = be_ctx->domain; + if (strcasecmp(data->domain, be_ctx->domain->name) != 0) { + /* Subdomain request, verify subdomain. */ + domain = find_domain_by_name(be_ctx->domain, data->domain, true); + } + + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain\n"); + ret = EINVAL; + goto immediately; + } + + /* Determine whether to connect to GC, LDAP or try both. */ + clist = get_conn_list(state, id_ctx, domain, data); + if (clist == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create conn list\n"); + ret = EIO; + goto immediately; + } + + sdom = sdap_domain_get(sdap_id_ctx->opts, domain); + if (sdom == NULL) { + ret = EIO; + goto immediately; + } + + subreq = ad_handle_acct_info_send(state, data, sdap_id_ctx, + id_ctx->ad_options, sdom, clist); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, ad_account_info_done, req); + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, be_ctx->ev); + return req; +} + +static void ad_account_info_done(struct tevent_req *subreq) +{ + struct ad_account_info_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_account_info_state); + + ret = ad_handle_acct_info_recv(subreq, &state->dp_error, &state->err_msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ad_handle_acct_info_recv failed [%d]: %s\n", + ret, sss_strerror(ret)); + /* The caller wouldn't fail either, just report the error up */ + } + talloc_zfree(subreq); + tevent_req_done(req); +} + +errno_t ad_account_info_recv(struct tevent_req *req, + int *_dp_error, + const char **_err_msg) +{ + struct ad_account_info_state *state = NULL; + + state = tevent_req_data(req, struct ad_account_info_state); + + if (_err_msg != NULL) { + *_err_msg = state->err_msg; + } + + if (_dp_error) { + *_dp_error = state->dp_error; + } + + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ad_account_info_handler_state { + struct sss_domain_info *domain; + struct dp_reply_std reply; +}; + +static void ad_account_info_handler_done(struct tevent_req *subreq); + +struct tevent_req * +ad_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct ad_account_info_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + + req = tevent_req_create(mem_ctx, &state, + struct ad_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sdap_is_enum_request(data)) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = ad_account_info_send(state, params->be_ctx, id_ctx, data); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ad_account_info_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ad_account_info_handler_done(struct tevent_req *subreq) +{ + struct ad_account_info_handler_state *state; + struct tevent_req *req; + const char *err_msg; + int dp_error = DP_ERR_FATAL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ad_account_info_handler_state); + + ret = ad_account_info_recv(subreq, &dp_error, &err_msg); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, err_msg); + tevent_req_done(req); +} + +errno_t ad_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ad_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct ad_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +struct ad_enumeration_state { + struct ad_id_ctx *id_ctx; + struct ldap_enum_ctx *ectx; + struct sdap_id_op *sdap_op; + struct tevent_context *ev; + + const char *realm; + struct sdap_domain *sdom; + struct sdap_domain *sditer; +}; + +static void ad_enumeration_conn_done(struct tevent_req *subreq); +static void ad_enumeration_master_done(struct tevent_req *subreq); +static errno_t ad_enum_sdom(struct tevent_req *req, struct sdap_domain *sd, + struct ad_id_ctx *id_ctx); +static void ad_enumeration_done(struct tevent_req *subreq); + +struct tevent_req * +ad_id_enumeration_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ad_enumeration_state *state; + struct ldap_enum_ctx *ectx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ad_enumeration_state); + if (req == NULL) return NULL; + + ectx = talloc_get_type(pvt, struct ldap_enum_ctx); + if (ectx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot retrieve ldap_enum_ctx!\n"); + ret = EFAULT; + goto fail; + } + + state->ectx = ectx; + state->ev = ev; + state->sdom = ectx->sdom; + state->sditer = state->sdom; + state->id_ctx = talloc_get_type(ectx->pvt, struct ad_id_ctx); + + state->realm = dp_opt_get_cstring(state->id_ctx->ad_options->basic, + AD_KRB5_REALM); + if (state->realm == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "Missing realm\n"); + ret = EINVAL; + goto fail; + } + + state->sdap_op = sdap_id_op_create(state, + state->id_ctx->ldap_ctx->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + ret = ENOMEM; + goto fail; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto fail; + } + tevent_req_set_callback(subreq, ad_enumeration_conn_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +ad_enumeration_conn_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_enumeration_state *state = tevent_req_data(req, + struct ad_enumeration_state); + int ret, dp_error; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_TRACE_FUNC, + "Backend is marked offline, retry later!\n"); + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Domain enumeration failed to connect to " \ + "LDAP server: (%d)[%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } + return; + } + + subreq = ad_domain_info_send(state, state->ev, + state->id_ctx->ldap_ctx, + state->sdap_op, + state->sdom->dom->name); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ad_domain_info_send failed.\n"); + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, ad_enumeration_master_done, req); +} + +static void +ad_enumeration_master_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_enumeration_state *state = tevent_req_data(req, + struct ad_enumeration_state); + char *flat_name; + char *dns_name; + char *master_sid; + char *forest; + + ret = ad_domain_info_recv(subreq, state, + &flat_name, &master_sid, NULL, &forest); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve master domain info\n"); + tevent_req_error(req, ret); + return; + } + + dns_name = dp_opt_get_string(state->id_ctx->ad_options->basic, AD_DOMAIN); + if (dns_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No domain name for AD?\n"); + ret = EIO; + tevent_req_error(req, ret); + return; + } + + ret = sysdb_master_domain_add_info(state->sdom->dom, state->realm, flat_name, + dns_name, master_sid, forest, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot save master domain info\n"); + tevent_req_error(req, ret); + return; + } + + ret = ad_enum_sdom(req, state->sdom, state->id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not enumerate domain %s\n", state->sdom->dom->name); + tevent_req_error(req, ret); + return; + } + + /* Execution will resume in ad_enumeration_done */ +} + +static errno_t +ad_enum_sdom(struct tevent_req *req, + struct sdap_domain *sd, + struct ad_id_ctx *id_ctx) +{ + struct sdap_id_conn_ctx *user_conn; + struct tevent_req *subreq; + struct ad_enumeration_state *state = tevent_req_data(req, + struct ad_enumeration_state); + + if (dp_opt_get_bool(id_ctx->ad_options->basic, AD_ENABLE_GC)) { + user_conn = id_ctx->gc_ctx; + } else { + user_conn = id_ctx->ldap_ctx; + } + + /* Groups are searched for in LDAP, users in GC. Services (if present, + * which is unlikely in AD) from LDAP as well + */ + subreq = sdap_dom_enum_ex_send(state, state->ev, + id_ctx->sdap_id_ctx, + sd, + user_conn, /* Users */ + id_ctx->ldap_ctx, /* Groups */ + id_ctx->ldap_ctx); /* Services */ + if (subreq == NULL) { + /* The ptask API will reschedule the enumeration on its own on + * failure */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to schedule enumeration, retrying later!\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ad_enumeration_done, req); + + return EOK; +} + +static errno_t ad_enum_cross_dom_members(struct sdap_options *opts, + struct sss_domain_info *dom); + +static void +ad_enumeration_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_enumeration_state *state = tevent_req_data(req, + struct ad_enumeration_state); + + ret = sdap_dom_enum_ex_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not enumerate domain %s\n", state->sditer->dom->name); + tevent_req_error(req, ret); + return; + } + + do { + state->sditer = state->sditer->next; + } while (state->sditer && + state->sditer->dom->enumerate == false); + + if (state->sditer != NULL) { + ret = ad_enum_sdom(req, state->sditer, state->sditer->pvt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not enumerate domain %s\n", + state->sditer->dom->name); + tevent_req_error(req, ret); + return; + } + + /* Execution will resume in ad_enumeration_done */ + return; + } + + /* No more subdomains to enumerate. Check if we need to fixup + * cross-domain membership + */ + if (state->sditer != state->sdom) { + /* We did enumerate at least one subdomain. Walk the subdomains + * and fixup members for each of them + */ + for (state->sditer = state->sdom; + state->sditer; + state->sditer = state->sditer->next) { + ret = ad_enum_cross_dom_members(state->id_ctx->ad_options->id, + state->sditer->dom); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not check cross-domain " + "memberships for %s, group memberships might be " + "incomplete!\n", state->sdom->dom->name); + continue; + } + } + } + + tevent_req_done(req); +} + +static errno_t ad_group_extra_members(TALLOC_CTX *mem_ctx, + const struct ldb_message *group, + struct sss_domain_info *dom, + char ***_group_only); +static errno_t ad_group_add_member(struct sdap_options *opts, + struct sss_domain_info *group_domain, + struct ldb_dn *group_dn, + const char *member); + +static errno_t +ad_enum_cross_dom_members(struct sdap_options *opts, + struct sss_domain_info *dom) +{ + errno_t ret; + errno_t sret; + char *filter; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = { + SYSDB_NAME, + SYSDB_MEMBER, + SYSDB_ORIG_MEMBER, + NULL + }; + size_t count, i, mi; + struct ldb_message **msgs; + bool in_transaction = false; + char **group_only; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + ret = sysdb_transaction_start(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + filter = talloc_asprintf(tmp_ctx, "(%s=*)", SYSDB_NAME); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_groups(tmp_ctx, dom, filter, attrs, &count, &msgs); + if (ret != EOK) { + goto done; + } + + for (i = 0; i < count; i++) { + ret = ad_group_extra_members(tmp_ctx, msgs[i], dom, &group_only); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to check extra members\n"); + continue; + } else if (group_only == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "No extra members\n"); + continue; + } + + /* Group has extra members */ + for (mi = 0; group_only[mi]; mi++) { + ret = ad_group_add_member(opts, dom, msgs[i]->dn, group_only[mi]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to add [%s]: %s\n", + group_only[mi], strerror(ret)); + continue; + } + } + + talloc_zfree(group_only); + } + + ret = sysdb_transaction_commit(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(dom->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ad_group_stored_orig_members(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + struct ldb_dn *dn, char ***_odn_list); + +static errno_t +ad_group_extra_members(TALLOC_CTX *mem_ctx, const struct ldb_message *group, + struct sss_domain_info *dom, char ***_group_only) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *m, *om; + const char *name; + errno_t ret; + char **sysdb_odn_list; + const char **group_odn_list; + char **group_only = NULL; + + if (_group_only == NULL) return EINVAL; + *_group_only = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + om = ldb_msg_find_element(group, SYSDB_ORIG_MEMBER); + m = ldb_msg_find_element(group, SYSDB_MEMBER); + name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "A group with no name!\n"); + ret = EFAULT; + goto done; + } + + if (om == NULL || om->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Group %s has no original members\n", name); + ret = EOK; + goto done; + } + + if (m == NULL || (m->num_values < om->num_values)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %s has %d members but %d original members\n", + name, m ? m->num_values : 0, om->num_values); + + /* Get the list of originalDN attributes that are already + * linked to the group + */ + ret = ad_group_stored_orig_members(tmp_ctx, dom, group->dn, + &sysdb_odn_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not retrieve list of original members for %s\n", + name); + goto done; + } + + /* Get the list of original DN attributes the group had in AD */ + group_odn_list = sss_ldb_el_to_string_list(tmp_ctx, om); + if (group_odn_list == NULL) { + ret = EFAULT; + goto done; + } + + /* Compare the two lists */ + ret = diff_string_lists(tmp_ctx, discard_const(group_odn_list), + sysdb_odn_list, &group_only, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not compare lists of members for %s\n", name); + goto done; + } + } + + ret = EOK; + *_group_only = talloc_steal(mem_ctx, group_only); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ad_group_stored_orig_members(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + struct ldb_dn *dn, char ***_odn_list) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + size_t m_count, i; + struct ldb_message **members; + const char *attrs[] = { + SYSDB_NAME, + SYSDB_ORIG_DN, + NULL + }; + char **odn_list; + const char *odn; + size_t oi; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + /* Get all entries member element points to */ + ret = sysdb_asq_search(tmp_ctx, dom, dn, NULL, SYSDB_MEMBER, + attrs, &m_count, &members); + if (ret != EOK) { + goto done; + } + + odn_list = talloc_zero_array(tmp_ctx, char *, m_count + 1); + if (odn_list == NULL) { + ret = ENOMEM; + goto done; + } + + /* Get a list of their original DNs */ + oi = 0; + for (i = 0; i < m_count; i++) { + odn = ldb_msg_find_attr_as_string(members[i], SYSDB_ORIG_DN, NULL); + if (odn == NULL) { + continue; + } + + odn_list[oi] = talloc_strdup(odn_list, odn); + if (odn_list[oi] == NULL) { + ret = ENOMEM; + goto done; + } + oi++; + DEBUG(SSSDBG_TRACE_INTERNAL, "Member %s already in sysdb\n", odn); + } + + ret = EOK; + *_odn_list = talloc_steal(mem_ctx, odn_list); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ad_group_add_member(struct sdap_options *opts, + struct sss_domain_info *group_domain, + struct ldb_dn *group_dn, + const char *member) +{ + struct sdap_domain *sd; + struct ldb_dn *base_dn; + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *mem_filter; + size_t msgs_count; + struct ldb_message **msgs; + + /* This member would be from a different domain */ + sd = sdap_domain_get_by_dn(opts, member); + if (sd == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No matching domain for %s\n", member); + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + mem_filter = talloc_asprintf(tmp_ctx, "(%s=%s)", + SYSDB_ORIG_DN, member); + if (mem_filter == NULL) { + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_domain_dn(tmp_ctx, sd->dom); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, sd->dom->sysdb, base_dn, + LDB_SCOPE_SUBTREE, mem_filter, NULL, + &msgs_count, &msgs); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "No member [%s] in sysdb\n", member); + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "[%s] found in sysdb\n", member); + + if (msgs_count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Search by orig DN returned %zd results!\n", msgs_count); + ret = EFAULT; + goto done; + } + + ret = sysdb_mod_group_member(group_domain, msgs[0]->dn, group_dn, SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add [%s] as a member of [%s]\n", + ldb_dn_get_linearized(msgs[0]->dn), + ldb_dn_get_linearized(group_dn)); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ad_id_enumeration_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static errno_t ad_get_account_domain_prepare_search(struct tevent_req *req); +static errno_t ad_get_account_domain_connect_retry(struct tevent_req *req); +static void ad_get_account_domain_connect_done(struct tevent_req *subreq); +static void ad_get_account_domain_search(struct tevent_req *req); +static void ad_get_account_domain_search_done(struct tevent_req *subreq); +static void ad_get_account_domain_evaluate(struct tevent_req *req); + +struct ad_get_account_domain_state { + struct tevent_context *ev; + struct ad_id_ctx *id_ctx; + struct sdap_id_ctx *sdap_id_ctx; + struct sdap_domain *sdom; + uint32_t entry_type; + uint32_t filter_type; + char *clean_filter; + + bool twopass; + + struct sdap_search_base **search_bases; + size_t base_iter; + const char *base_filter; + char *filter; + const char **attrs; + int dp_error; + struct dp_reply_std reply; + struct sdap_id_op *op; + struct sysdb_attrs **objects; + size_t count; + + const char *found_domain_name; +}; + +struct tevent_req * +ad_get_account_domain_send(TALLOC_CTX *mem_ctx, + struct ad_id_ctx *id_ctx, + struct dp_get_acct_domain_data *data, + struct dp_req_params *params) +{ + struct ad_get_account_domain_state *state; + struct tevent_req *req; + errno_t ret; + bool use_id_mapping; + struct sss_domain_info *domain; + + req = tevent_req_create(mem_ctx, &state, + struct ad_get_account_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + state->ev = params->ev; + state->id_ctx = id_ctx; + state->sdap_id_ctx = id_ctx->sdap_id_ctx; + state->entry_type = data->entry_type & BE_REQ_TYPE_MASK; + state->filter_type = data->filter_type; + state->attrs = talloc_array(state, const char *, 2); + if (state->attrs == NULL) { + ret = ENOMEM; + goto immediately; + } + state->attrs[0] = "objectclass"; + state->attrs[1] = NULL; + + if (sss_domain_is_mpg(params->be_ctx->domain) == true + || state->entry_type == BE_REQ_USER_AND_GROUP) { + state->twopass = true; + if (state->entry_type == BE_REQ_USER_AND_GROUP) { + state->entry_type = BE_REQ_GROUP; + } + } + + /* SID lookup does not require communication with backend */ + if (state->entry_type == BE_REQ_BY_SECID) { + domain = find_domain_by_sid(params->domain, data->filter_value); + if (domain == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "SID %s does not fit into any domain\n", data->filter_value); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ERR_NOT_FOUND, NULL); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "SID %s fits into domain %s\n", data->filter_value, domain->name); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, EOK, domain->name); + } + tevent_req_done(req); + tevent_req_post(req, params->ev); + return req; + } + + /* The get-account-domain request only works with GC */ + if (dp_opt_get_bool(id_ctx->ad_options->basic, AD_ENABLE_GC) == false) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Global catalog support is not enabled, " + "cannot locate the account domain\n"); + ret = ERR_GET_ACCT_DOM_NOT_SUPPORTED; + goto immediately; + } + + state->sdom = sdap_domain_get(id_ctx->sdap_id_ctx->opts, + params->be_ctx->domain); + if (state->sdom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find sdap_domain\n"); + ret = EIO; + goto immediately; + } + + /* Currently we only support locating the account domain + * if ID mapping is disabled. With ID mapping enabled, we can + * already shortcut the 'real' ID request + */ + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + state->sdap_id_ctx->opts->idmap_ctx, + state->sdom->dom->name, + state->sdom->dom->domain_id); + if (use_id_mapping == true) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No point in locating domain with GC if ID-mapping " + "is enabled\n"); + ret = ERR_GET_ACCT_DOM_NOT_SUPPORTED; + goto immediately; + } + + ret = sss_filter_sanitize(state, data->filter_value, &state->clean_filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot sanitize filter [%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + ret = ad_get_account_domain_prepare_search(req); + if (ret != EOK) { + goto immediately; + } + + /* FIXME - should gc_ctx always default to ignore_offline on creation + * time rather than setting the flag on first use? + */ + id_ctx->gc_ctx->ignore_mark_offline = true; + state->op = sdap_id_op_create(state, id_ctx->gc_ctx->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto immediately; + } + + ret = ad_get_account_domain_connect_retry(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Connection error"); + goto immediately; + } + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static errno_t ad_get_account_domain_prepare_search(struct tevent_req *req) +{ + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + const char *attr_name = NULL; + const char *objectclass = NULL; + + switch (state->entry_type) { + case BE_REQ_USER: + state->search_bases = state->sdom->user_search_bases; + attr_name = state->sdap_id_ctx->opts->user_map[SDAP_AT_USER_UID].name; + objectclass = state->sdap_id_ctx->opts->user_map[SDAP_OC_USER].name; + break; + case BE_REQ_GROUP: + state->search_bases = state->sdom->group_search_bases; + attr_name = state->sdap_id_ctx->opts->group_map[SDAP_AT_GROUP_GID].name; + objectclass = state->sdap_id_ctx->opts->group_map[SDAP_OC_GROUP].name; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Unsupported request type %X\n", + state->entry_type & BE_REQ_TYPE_MASK); + return EINVAL; + } + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to prepare search: missing search_bases\n"); + return EINVAL; + } + + switch (state->filter_type) { + case BE_FILTER_IDNUM: + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Unsupported filter type %X\n", state->filter_type); + return EINVAL; + } + + talloc_zfree(state->base_filter); + state->base_filter = talloc_asprintf(state, + "(&(%s=%s)(objectclass=%s))", + attr_name, + state->clean_filter, + objectclass); + if (state->base_filter == NULL) { + return ENOMEM; + } + + return EOK; +} + +static errno_t ad_get_account_domain_connect_retry(struct tevent_req *req) +{ + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + struct tevent_req *subreq; + errno_t ret; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ad_get_account_domain_connect_done, req); + return ret; +} + +static void ad_get_account_domain_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + int dp_error = DP_ERR_FATAL; + errno_t ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + ad_get_account_domain_search(req); +} + +static void ad_get_account_domain_search(struct tevent_req *req) +{ + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + struct tevent_req *subreq; + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters(state, state->base_filter, + state->search_bases[state->base_iter]->filter); + if (state->filter == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + subreq = sdap_get_generic_send(state, state->ev, state->sdap_id_ctx->opts, + sdap_id_op_handle(state->op), + "", + LDAP_SCOPE_SUBTREE, + state->filter, + state->attrs, NULL, 0, + dp_opt_get_int(state->sdap_id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + tevent_req_error(req, EIO); + return; + } + + tevent_req_set_callback(subreq, ad_get_account_domain_search_done, req); +} + +static void ad_get_account_domain_search_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + size_t count; + struct sysdb_attrs **objects; + errno_t ret; + + ret = sdap_get_generic_recv(subreq, state, + &count, &objects); + talloc_zfree(subreq); + if (ret) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Search returned %zu results.\n", count); + + if (count > 0) { + size_t copied; + + state->objects = + talloc_realloc(state, + state->objects, + struct sysdb_attrs *, + state->count + count + 1); + if (!state->objects) { + tevent_req_error(req, ENOMEM); + return; + } + + copied = sdap_steal_objects_in_dom(state->sdap_id_ctx->opts, + state->objects, + state->count, + NULL, + objects, count, + false); + + state->count += copied; + state->objects[state->count] = NULL; + } + + /* Even though we search with an empty search base (=across all domains) + * the reason we iterate over search bases is that the search bases can + * also contain a filter which might restrict the IDs we find + */ + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ad_get_account_domain_search(req); + return; + } + + /* No more searches, evaluate results */ + ad_get_account_domain_evaluate(req); +} + +static void ad_get_account_domain_evaluate(struct tevent_req *req) +{ + struct ad_get_account_domain_state *state = tevent_req_data(req, + struct ad_get_account_domain_state); + struct sss_domain_info *obj_dom; + errno_t ret; + + if (state->count == 0) { + if (state->twopass + && state->entry_type != BE_REQ_USER) { + DEBUG(SSSDBG_TRACE_FUNC, "Retrying search\n"); + + state->entry_type = BE_REQ_USER; + state->base_iter = 0; + ret = ad_get_account_domain_prepare_search(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot retry search\n"); + tevent_req_error(req, ret); + return; + } + + ad_get_account_domain_search(req); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Not found\n"); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ERR_NOT_FOUND, NULL); + tevent_req_done(req); + return; + } else if (state->count > 1) { + /* FIXME: If more than one entry was found, return error for now + * as the account requsts have no way of returning multiple + * messages back until we switch to the rdp_* requests + * from the responder side + */ + DEBUG(SSSDBG_OP_FAILURE, "Multiple entries found, error!\n"); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ERANGE, NULL); + tevent_req_done(req); + return; + } + + /* Exactly one entry was found */ + obj_dom = sdap_get_object_domain(state->sdap_id_ctx->opts, + state->objects[0], + state->sdom->dom); + if (obj_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not match entry with domain!\n"); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ERR_NOT_FOUND, NULL); + tevent_req_done(req); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Found object in domain %s\n", obj_dom->name); + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, EOK, obj_dom->name); + tevent_req_done(req); +} + +errno_t ad_get_account_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ad_get_account_domain_state *state = NULL; + + state = tevent_req_data(req, struct ad_get_account_domain_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} |