summaryrefslogtreecommitdiffstats
path: root/src/providers/ad/ad_id.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ad/ad_id.c')
-rw-r--r--src/providers/ad/ad_id.c1547
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;
+}