/* SSSD IPA Identity Backend Module for sub-domains Authors: Sumit Bose Copyright (C) 2012 Red Hat This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "util/util.h" #include "util/sss_nss.h" #include "util/strtonum.h" #include "db/sysdb.h" #include "providers/ldap/ldap_common.h" #include "providers/ldap/sdap_async.h" #include "providers/ldap/sdap_async_ad.h" #include "providers/ipa/ipa_id.h" #include "providers/ad/ad_id.h" #include "providers/ad/ad_pac.h" #include "providers/ipa/ipa_subdomains.h" static struct tevent_req * ipa_srv_ad_acct_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct ipa_id_ctx *ipa_ctx, struct sysdb_attrs *override_attrs, struct be_acct_req *ar); static errno_t ipa_srv_ad_acct_recv(struct tevent_req *req, int *dp_error_out); struct ipa_subdomain_account_state { struct tevent_context *ev; struct ipa_id_ctx *ipa_ctx; struct sdap_id_ctx *ctx; struct sdap_id_op *op; struct sysdb_ctx *sysdb; struct sss_domain_info *domain; struct be_acct_req *ar; bool ipa_server_mode; bool server_retry; int entry_type; const char *filter; int filter_type; struct sysdb_attrs *override_attrs; int dp_error; }; static void ipa_subdomain_account_connected(struct tevent_req *subreq); static void ipa_subdomain_account_got_override(struct tevent_req *subreq); static void ipa_subdomain_account_done(struct tevent_req *subreq); static errno_t ipa_subdomain_account_get_original_step(struct tevent_req *req, struct be_acct_req *ar); struct tevent_req *ipa_subdomain_account_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct ipa_id_ctx *ipa_ctx, struct be_acct_req *ar) { struct tevent_req *req; struct ipa_subdomain_account_state *state; struct tevent_req *subreq; int ret; req = tevent_req_create(memctx, &state, struct ipa_subdomain_account_state); if (req == NULL) { DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); return NULL; } state->ev = ev; state->ipa_ctx = ipa_ctx; state->ctx = ipa_ctx->sdap_id_ctx; state->dp_error = DP_ERR_FATAL; state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); if (!state->op) { DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); ret = ENOMEM; goto fail; } state->domain = find_domain_by_name(state->ctx->be->domain, ar->domain, true); if (state->domain == NULL) { DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); ret = ENOMEM; goto fail; } state->sysdb = state->domain->sysdb; state->ar = ar; state->ipa_server_mode = dp_opt_get_bool(state->ipa_ctx->ipa_options->basic, IPA_SERVER_MODE); state->override_attrs = NULL; /* With views we cannot got directly to the look up the AD objects but * have to check first if the request matches an override in the given * view. But there are cases where this can be skipped and the AD object * can be searched directly: * - if no view is defined, i.e. the server does not supprt views yet * - searches by SID: because we do not override the SID * - if the responder does not send the EXTRA_INPUT_MAYBE_WITH_VIEW flags, * because in this case the entry was found in the cache and the * original value is used for the search (e.g. during cache updates) */ if (state->ipa_ctx->view_name == NULL || state->ar->filter_type == BE_FILTER_SECID || (!state->ipa_server_mode && state->ar->extra_value != NULL && strcmp(state->ar->extra_value, EXTRA_INPUT_MAYBE_WITH_VIEW) != 0 )) { ret = ipa_subdomain_account_get_original_step(req, state->ar); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ipa_subdomain_account_get_original_step failed.\n"); goto fail; } return req; } subreq = sdap_id_op_connect_send(state->op, state, &ret); if (!subreq) { goto fail; } tevent_req_set_callback(subreq, ipa_subdomain_account_connected, req); return req; fail: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void ipa_subdomain_account_connected(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_subdomain_account_state *state = tevent_req_data(req, struct ipa_subdomain_account_state); int dp_error = DP_ERR_FATAL; int ret; ret = sdap_id_op_connect_recv(subreq, &dp_error); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect request failed.\n"); goto fail; } subreq = ipa_get_ad_override_send(state, state->ev, state->ctx, state->ipa_ctx->ipa_options, dp_opt_get_string(state->ipa_ctx->ipa_options->basic, IPA_KRB5_REALM), state->ipa_ctx->view_name, state->ar); if (subreq == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); ret = ENOMEM; goto fail; } tevent_req_set_callback(subreq, ipa_subdomain_account_got_override, req); return; fail: state->dp_error = dp_error; tevent_req_error(req, ret); return; } #define OVERRIDE_ANCHOR_SID_PREFIX ":SID:" #define OVERRIDE_ANCHOR_SID_PREFIX_LEN (sizeof(OVERRIDE_ANCHOR_SID_PREFIX) -1 ) static void ipa_subdomain_account_got_override(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_subdomain_account_state *state = tevent_req_data(req, struct ipa_subdomain_account_state); int dp_error = DP_ERR_FATAL; int ret; const char *anchor = NULL; struct be_acct_req *ar; ret = ipa_get_ad_override_recv(subreq, &dp_error, state, &state->override_attrs); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); goto fail; } if (state->override_attrs != NULL) { DEBUG(SSSDBG_TRACE_ALL, "Processing override.\n"); ret = sysdb_attrs_get_string(state->override_attrs, SYSDB_OVERRIDE_ANCHOR_UUID, &anchor); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); goto fail; } if (anchor != NULL && strncmp(OVERRIDE_ANCHOR_SID_PREFIX, anchor, OVERRIDE_ANCHOR_SID_PREFIX_LEN) == 0) { ret = get_be_acct_req_for_sid(state, anchor + OVERRIDE_ANCHOR_SID_PREFIX_LEN, state->ar->domain, &ar); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "get_be_acct_req_for_sid failed.\n"); goto fail; } if (state->ipa_server_mode && (state->ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_INITGROUPS) { DEBUG(SSSDBG_TRACE_ALL, "Switching back to BE_REQ_INITGROUPS.\n"); ar->entry_type = BE_REQ_INITGROUPS; ar->filter_type = BE_FILTER_SECID; ar->attr_type = BE_ATTR_CORE; } } else { DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported override anchor type [%s].\n", anchor); ret = EINVAL; goto fail; } } else { ar = state->ar; } ret = ipa_subdomain_account_get_original_step(req, ar); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ipa_subdomain_account_get_original_step failed.\n"); goto fail; } return; fail: state->dp_error = dp_error; tevent_req_error(req, ret); return; } static errno_t ipa_subdomain_account_get_original_step(struct tevent_req *req, struct be_acct_req *ar) { struct ipa_subdomain_account_state *state = tevent_req_data(req, struct ipa_subdomain_account_state); struct tevent_req *subreq; if (state->ipa_server_mode) { subreq = ipa_srv_ad_acct_send(state, state->ev, state->ipa_ctx, state->override_attrs, ar); } else { subreq = ipa_get_subdom_acct_send(state, state->ev, state->ipa_ctx, state->override_attrs, ar); } if (subreq == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_*_acct_send failed.\n"); return ENOMEM; } tevent_req_set_callback(subreq, ipa_subdomain_account_done, req); return EOK; } static void ipa_subdomain_account_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_subdomain_account_state *state = tevent_req_data(req, struct ipa_subdomain_account_state); int dp_error = DP_ERR_FATAL; int ret; if (state->ipa_server_mode) { ret = ipa_srv_ad_acct_recv(subreq, &dp_error); } else { ret = ipa_get_subdom_acct_recv(subreq, &dp_error); } talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_*_acct request failed: [%d]: %s.\n", ret, sss_strerror(ret)); state->dp_error = dp_error; tevent_req_error(req, ret); return; } state->dp_error = DP_ERR_OK; tevent_req_done(req); return; } errno_t ipa_subdomain_account_recv(struct tevent_req *req, int *dp_error_out) { struct ipa_subdomain_account_state *state = tevent_req_data(req, struct ipa_subdomain_account_state); if (dp_error_out) { *dp_error_out = state->dp_error; } TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct ipa_get_subdom_acct { struct tevent_context *ev; struct ipa_id_ctx *ipa_ctx; struct sdap_id_ctx *ctx; struct sdap_id_op *op; struct sysdb_ctx *sysdb; struct sss_domain_info *domain; struct sysdb_attrs *override_attrs; int entry_type; const char *filter; int filter_type; bool use_pac; struct ldb_message *user_msg; int dp_error; }; static void ipa_get_subdom_acct_connected(struct tevent_req *subreq); static void ipa_get_subdom_acct_done(struct tevent_req *subreq); struct tevent_req *ipa_get_subdom_acct_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct ipa_id_ctx *ipa_ctx, struct sysdb_attrs *override_attrs, struct be_acct_req *ar) { struct tevent_req *req; struct ipa_get_subdom_acct *state; struct tevent_req *subreq; int ret; req = tevent_req_create(memctx, &state, struct ipa_get_subdom_acct); if (!req) return NULL; state->ev = ev; state->ipa_ctx = ipa_ctx; state->ctx = ipa_ctx->sdap_id_ctx; state->dp_error = DP_ERR_FATAL; state->override_attrs = override_attrs; state->use_pac = false; state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); if (!state->op) { DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); ret = ENOMEM; goto fail; } state->domain = find_domain_by_name(state->ctx->be->domain, ar->domain, true); if (state->domain == NULL) { DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); ret = ENOMEM; goto fail; } state->sysdb = state->domain->sysdb; state->entry_type = (ar->entry_type & BE_REQ_TYPE_MASK); state->filter = ar->filter_value; state->filter_type = ar->filter_type; switch (state->entry_type) { case BE_REQ_USER: case BE_REQ_GROUP: case BE_REQ_BY_SECID: case BE_REQ_USER_AND_GROUP: ret = EOK; break; case BE_REQ_INITGROUPS: ret = check_if_pac_is_available(state, state->domain, ar, &state->user_msg); if (ret == EOK) { state->use_pac = true; } ret = EOK; break; default: ret = EINVAL; DEBUG(SSSDBG_OP_FAILURE, "Invalid sub-domain request type.\n"); } if (ret != EOK) goto fail; subreq = sdap_id_op_connect_send(state->op, state, &ret); if (!subreq) { goto fail; } tevent_req_set_callback(subreq, ipa_get_subdom_acct_connected, req); return req; fail: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void ipa_get_subdom_acct_connected(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_get_subdom_acct *state = tevent_req_data(req, struct ipa_get_subdom_acct); int dp_error = DP_ERR_FATAL; int ret; char *endptr; struct req_input *req_input; char *shortname; 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; } if (state->entry_type == BE_REQ_INITGROUPS) { /* With V1 of the extdom plugin a user lookup will resolve the full * group membership of the user. */ if (sdap_is_extension_supported(sdap_id_op_handle(state->op), EXOP_SID2NAME_V1_OID)) { state->entry_type = BE_REQ_USER; } else { if (state->use_pac && state->user_msg != NULL) { /* This means the user entry is already in the cache and has * the pac attached, we only have look up the missing groups * and add the user to all groups. */ subreq = ipa_get_subdom_acct_process_pac_send(state, state->ev, sdap_id_op_handle(state->op), state->ipa_ctx, state->domain, state->user_msg); if (subreq == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_subdom_acct_process_pac failed.\n"); tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, ipa_get_subdom_acct_done, req); return; } /* Fall through if there is no PAC */ DEBUG(SSSDBG_TRACE_FUNC, "Initgroups requests are not handled " \ "by the IPA provider but are resolved " \ "by the responder directly from the " \ "cache.\n"); tevent_req_error(req, ENOTSUP); return; } } req_input = talloc(state, struct req_input); if (req_input == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); tevent_req_error(req, ENOMEM); return; } switch (state->filter_type) { case BE_FILTER_NAME: req_input->type = REQ_INP_NAME; /* The extdom plugin expects the shortname and domain separately */ ret = sss_parse_internal_fqname(req_input, state->filter, &shortname, NULL); req_input->inp.name = talloc_steal(req_input, shortname); if (req_input->inp.name == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); tevent_req_error(req, ENOMEM); return; } break; case BE_FILTER_IDNUM: req_input->type = REQ_INP_ID; req_input->inp.id = strtouint32(state->filter, &endptr, 10); if (errno || *endptr || (state->filter == endptr)) { tevent_req_error(req, errno ? errno : EINVAL); return; } break; case BE_FILTER_SECID: req_input->type = REQ_INP_SECID; req_input->inp.secid = talloc_strdup(req_input, state->filter); if (req_input->inp.secid == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); tevent_req_error(req, ENOMEM); return; } break; case BE_FILTER_CERT: if (sdap_is_extension_supported(sdap_id_op_handle(state->op), EXOP_SID2NAME_V1_OID)) { req_input->type = REQ_INP_CERT; req_input->inp.cert = talloc_strdup(req_input, state->filter); if (req_input->inp.cert == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); tevent_req_error(req, ENOMEM); return; } } else { DEBUG(SSSDBG_OP_FAILURE, "Lookup by certificate not supported by the server.\n"); state->dp_error = DP_ERR_OK; tevent_req_error(req, EINVAL); return; } break; default: DEBUG(SSSDBG_OP_FAILURE, "Invalid sub-domain filter type.\n"); state->dp_error = dp_error; tevent_req_error(req, EINVAL); return; } subreq = ipa_s2n_get_acct_info_send(state, state->ev, state->ipa_ctx, state->ctx->opts, state->domain, state->override_attrs, sdap_id_op_handle(state->op), state->entry_type, req_input); if (!subreq) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, ipa_get_subdom_acct_done, req); return; } static void ipa_get_subdom_acct_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_get_subdom_acct *state = tevent_req_data(req, struct ipa_get_subdom_acct); int dp_error = DP_ERR_FATAL; int ret; ret = ipa_s2n_get_acct_info_recv(subreq); talloc_zfree(subreq); ret = sdap_id_op_done(state->op, ret, &dp_error); if (dp_error == DP_ERR_OK && ret != EOK) { /* retry */ subreq = sdap_id_op_connect_send(state->op, state, &ret); if (!subreq) { tevent_req_error(req, ret); return; } tevent_req_set_callback(subreq, ipa_get_subdom_acct_connected, req); return; } if (ret && ret != ENOENT) { state->dp_error = dp_error; tevent_req_error(req, ret); return; } /* FIXME: do we need some special handling of ENOENT */ state->dp_error = DP_ERR_OK; tevent_req_done(req); } int ipa_get_subdom_acct_recv(struct tevent_req *req, int *dp_error_out) { struct ipa_get_subdom_acct *state = tevent_req_data(req, struct ipa_get_subdom_acct); if (dp_error_out) { *dp_error_out = state->dp_error; } TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } /* IPA lookup for server mode. Directly to AD. */ struct ipa_get_ad_acct_state { int dp_error; struct tevent_context *ev; struct ipa_id_ctx *ipa_ctx; struct be_acct_req *ar; struct sss_domain_info *obj_dom; char *object_sid; struct sysdb_attrs *override_attrs; struct ldb_message *obj_msg; struct ldb_message_element *ghosts; }; static void ipa_get_ad_acct_ad_part_done(struct tevent_req *subreq); static void ipa_get_ad_override_done(struct tevent_req *subreq); static errno_t ipa_get_ad_apply_override_step(struct tevent_req *req); static errno_t ipa_get_ad_ipa_membership_step(struct tevent_req *req); static void ipa_id_get_groups_overrides_done(struct tevent_req *subreq); static void ipa_get_ad_acct_done(struct tevent_req *subreq); static struct ad_id_ctx *ipa_get_ad_id_ctx(struct ipa_id_ctx *ipa_ctx, struct sss_domain_info *dom); static struct tevent_req * ipa_get_ad_acct_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct ipa_id_ctx *ipa_ctx, struct sysdb_attrs *override_attrs, struct be_acct_req *ar) { errno_t ret; struct tevent_req *req; struct tevent_req *subreq; struct ipa_get_ad_acct_state *state; struct sdap_domain *sdom; struct sdap_id_conn_ctx **clist; struct sdap_id_ctx *sdap_id_ctx;; struct ad_id_ctx *ad_id_ctx; req = tevent_req_create(mem_ctx, &state, struct ipa_get_ad_acct_state); if (req == NULL) return NULL; state->dp_error = -1; state->ev = ev; state->ipa_ctx = ipa_ctx; state->ar = ar; state->obj_msg = NULL; state->override_attrs = override_attrs; /* This can only be a subdomain request, verify subdomain */ state->obj_dom = find_domain_by_name(ipa_ctx->sdap_id_ctx->be->domain, ar->domain, true); if (state->obj_dom == NULL) { ret = EINVAL; goto fail; } /* Let's see if this subdomain has a ad_id_ctx */ ad_id_ctx = ipa_get_ad_id_ctx(ipa_ctx, state->obj_dom); if (ad_id_ctx == NULL) { ret = EINVAL; goto fail; } sdap_id_ctx = ad_id_ctx->sdap_id_ctx; /* We read users and groups from GC. From groups, we may switch to * using LDAP connection in the group request itself, but in order * to resolve Universal group memberships, we also need the GC * connection */ switch (state->ar->entry_type & BE_REQ_TYPE_MASK) { case BE_REQ_INITGROUPS: case BE_REQ_BY_SECID: case BE_REQ_GROUP: clist = ad_gc_conn_list(req, ad_id_ctx, state->obj_dom); break; default: clist = ad_ldap_conn_list(req, ad_id_ctx, state->obj_dom); break; } if (clist == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Cannot generate AD connection list!\n"); ret = ENOMEM; goto fail; } /* Now we already need ad_id_ctx in particular sdap_id_conn_ctx */ sdom = sdap_domain_get(sdap_id_ctx->opts, state->obj_dom); if (sdom == NULL) { ret = EIO; goto fail; } subreq = ad_handle_acct_info_send(req, ar, sdap_id_ctx, ad_id_ctx->ad_options, sdom, clist); if (subreq == NULL) { ret = ENOMEM; goto fail; } tevent_req_set_callback(subreq, ipa_get_ad_acct_ad_part_done, req); return req; fail: state->dp_error = DP_ERR_FATAL; tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static struct ad_id_ctx * ipa_get_ad_id_ctx(struct ipa_id_ctx *ipa_ctx, struct sss_domain_info *dom) { struct ipa_ad_server_ctx *iter; DLIST_FOR_EACH(iter, ipa_ctx->server_mode->trusts) { if (iter->dom == dom) break; } return (iter) ? iter->ad_id_ctx : NULL; } static errno_t get_subdomain_homedir_of_user(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, const char *fqname, uint32_t uid, const char *original, const char **_homedir) { errno_t ret; const char *homedir; TALLOC_CTX *tmp_ctx; struct sss_nss_homedir_ctx homedir_ctx; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { ret = ENOMEM; goto done; } if (strstr(dom->subdomain_homedir, "%o") != NULL && original == NULL) { DEBUG(SSSDBG_TRACE_ALL, "Original home directory for user: %s is empty.\n", fqname); ret = ERR_HOMEDIR_IS_NULL; goto done; } ZERO_STRUCT(homedir_ctx); homedir_ctx.uid = uid; homedir_ctx.username = fqname; homedir_ctx.domain = dom->name; homedir_ctx.flatname = dom->flat_name; homedir_ctx.config_homedir_substr = dom->homedir_substr; homedir_ctx.original = original; /* To be compatible with the old winbind based user lookups and IPA * clients the user name in the home directory path will be lower-case. */ homedir = expand_homedir_template(tmp_ctx, dom->subdomain_homedir, false, &homedir_ctx); if (homedir == NULL) { DEBUG(SSSDBG_OP_FAILURE, "expand_homedir_template failed\n"); ret = ENOMEM; goto done; } if (_homedir == NULL) { ret = EINVAL; goto done; } *_homedir = talloc_steal(mem_ctx, homedir); ret = EOK; done: talloc_free(tmp_ctx); return ret; } static errno_t store_homedir_of_user(struct sss_domain_info *domain, const char *fqname, const char *homedir) { errno_t ret; errno_t sret; TALLOC_CTX *tmp_ctx; bool in_transaction = false; struct sysdb_attrs *attrs; struct sysdb_ctx *sysdb = domain->sysdb; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ret = ENOMEM; goto done; } attrs = sysdb_new_attrs(tmp_ctx); if (attrs == NULL) { ret = ENOMEM; goto done; } ret = sysdb_attrs_add_string(attrs, SYSDB_HOMEDIR, homedir); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Error setting homedir: [%s]\n", strerror(ret)); goto done; } ret = sysdb_transaction_start(sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); goto done; } in_transaction = true; ret = sysdb_set_user_attr(domain, fqname, attrs, SYSDB_MOD_REP); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to update homedir information!\n"); goto done; } ret = sysdb_transaction_commit(sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot commit sysdb transaction [%d]: %s.\n", ret, strerror(ret)); goto done; } in_transaction = false; done: if (in_transaction) { sret = sysdb_transaction_cancel(sysdb); if (sret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction.\n"); } } talloc_free(tmp_ctx); return ret; } static errno_t apply_subdomain_homedir(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, struct ldb_message *msg) { errno_t ret; uint32_t uid; const char *fqname; const char *original; const char *homedir = NULL; struct ldb_message_element *msg_el = NULL; size_t c; msg_el = ldb_msg_find_element(msg, SYSDB_OBJECTCLASS); if (msg_el == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_find_element failed.\n"); ret = ENOENT; goto done; } for (c = 0; c < msg_el->num_values; c++) { if (strncmp(SYSDB_USER_CLASS, (const char *)msg_el->values[c].data, msg_el->values[c].length) == 0) { break; } } if (c == msg_el->num_values) { DEBUG(SSSDBG_TRACE_ALL, "User objectclass not found, object is not a user.\n"); ret = ENOENT; goto done; } fqname = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); if (fqname == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); ret = EINVAL; goto done; } uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); if (uid == 0) { DEBUG(SSSDBG_OP_FAILURE, "UID for user [%s] is not known.\n", fqname); ret = ENOENT; goto done; } original = ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL); if (original == NULL) { DEBUG(SSSDBG_TRACE_ALL, "Missing homedir of %s.\n", fqname); } ret = get_subdomain_homedir_of_user(mem_ctx, dom, fqname, uid, original, &homedir); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "get_subdomain_homedir_of_user failed: [%d]: [%s]\n", ret, sss_strerror(ret)); if (ret == ERR_HOMEDIR_IS_NULL) { /* This is not fatal, fallback_homedir will be used. */ ret = EOK; } goto done; } ret = store_homedir_of_user(dom, fqname, homedir); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "store_homedir_of_user failed: [%d]: [%s]\n", ret, sss_strerror(ret)); goto done; } done: return ret; } errno_t get_object_from_cache(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, struct be_acct_req *ar, struct ldb_message **_msg) { errno_t ret; uint32_t id; struct ldb_message *msg = NULL; struct ldb_result *res = NULL; const char *attrs[] = { SYSDB_NAME, SYSDB_UIDNUM, SYSDB_SID_STR, SYSDB_OBJECTCLASS, SYSDB_UUID, SYSDB_GHOST, SYSDB_HOMEDIR, NULL }; if (ar->filter_type == BE_FILTER_SECID) { ret = sysdb_search_object_by_sid(mem_ctx, dom, ar->filter_value, attrs, &res); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to make request to our cache: [%d]: [%s]\n", ret, sss_strerror(ret)); goto done; } *_msg = res->msgs[0]; ret = EOK; goto done; } else if (ar->filter_type == BE_FILTER_UUID) { ret = sysdb_search_object_by_uuid(mem_ctx, dom, ar->filter_value, attrs, &res); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to make request to our cache: [%d]: [%s]\n", ret, sss_strerror(ret)); goto done; } *_msg = res->msgs[0]; ret = EOK; goto done; } else if (ar->filter_type == BE_FILTER_CERT) { ret = sysdb_search_object_by_cert(mem_ctx, dom, ar->filter_value, attrs, &res); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to make request to our cache: [%d]: [%s]\n", ret, sss_strerror(ret)); goto done; } *_msg = res->msgs[0]; ret = EOK; goto done; } else if (ar->filter_type == BE_FILTER_IDNUM) { errno = 0; id = strtouint32(ar->filter_value, NULL, 10); if (errno != 0) { ret = errno; DEBUG(SSSDBG_OP_FAILURE, "strtouint32 failed.\n"); goto done; } switch (ar->entry_type & BE_REQ_TYPE_MASK) { case BE_REQ_GROUP: ret = sysdb_search_group_by_gid(mem_ctx, dom, id, attrs, &msg); break; case BE_REQ_INITGROUPS: case BE_REQ_USER: case BE_REQ_USER_AND_GROUP: ret = sysdb_search_user_by_uid(mem_ctx, dom, id, attrs, &msg); if (ret == ENOENT && (ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_USER_AND_GROUP) { ret = sysdb_search_group_by_gid(mem_ctx, dom, id, attrs, &msg); } break; default: DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected entry type [%d].\n", (ar->entry_type & BE_REQ_TYPE_MASK)); ret = EINVAL; goto done; } } else if (ar->filter_type == BE_FILTER_NAME) { switch (ar->entry_type & BE_REQ_TYPE_MASK) { case BE_REQ_GROUP: ret = sysdb_search_group_by_name(mem_ctx, dom, ar->filter_value, attrs, &msg); break; case BE_REQ_INITGROUPS: case BE_REQ_USER: case BE_REQ_USER_AND_GROUP: if (ar->extra_value && strcmp(ar->extra_value, EXTRA_NAME_IS_UPN) == 0) { ret = sysdb_search_user_by_upn(mem_ctx, dom, ar->filter_value, attrs, &msg); } else { ret = sysdb_search_user_by_name(mem_ctx, dom, ar->filter_value, attrs, &msg); if (ret == ENOENT && (ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_USER_AND_GROUP) { ret = sysdb_search_group_by_name(mem_ctx, dom, ar->filter_value, attrs, &msg); } } break; default: DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected entry type [%d].\n", (ar->entry_type & BE_REQ_TYPE_MASK)); ret = EINVAL; goto done; } } else { DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected filter type.\n"); ret = EINVAL; goto done; } if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_OP_FAILURE, "Failed to make request to our cache: [%d]: [%s]\n", ret, sss_strerror(ret)); goto done; } *_msg = msg; done: return ret; } static void ipa_get_ad_acct_ad_part_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_get_ad_acct_state *state = tevent_req_data(req, struct ipa_get_ad_acct_state); errno_t ret; const char *sid; struct be_acct_req *ar; ret = ad_handle_acct_info_recv(subreq, &state->dp_error, NULL); talloc_zfree(subreq); if (ret == ERR_SUBDOM_INACTIVE) { tevent_req_error(req, ret); return; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "AD lookup failed: %d\n", ret); tevent_req_error(req, ret); return; } ret = get_object_from_cache(state, state->obj_dom, state->ar, &state->obj_msg); if (ret == ENOENT) { DEBUG(SSSDBG_MINOR_FAILURE, "Object not found, ending request\n"); tevent_req_done(req); return; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); goto fail; } ret = apply_subdomain_homedir(state, state->obj_dom, state->obj_msg); if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_OP_FAILURE, "apply_subdomain_homedir failed: [%d]: [%s].\n", ret, sss_strerror(ret)); goto fail; } if (state->override_attrs == NULL) { sid = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_SID_STR, NULL); if (sid == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find a SID.\n"); ret = EINVAL; goto fail; } state->object_sid = talloc_strdup(state, sid); if (state->object_sid == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); ret = ENOMEM; goto fail; } ret = get_be_acct_req_for_sid(state, state->object_sid, state->obj_dom->name, &ar); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "get_be_acct_req_for_sid failed.\n"); goto fail; } subreq = ipa_get_ad_override_send(state, state->ev, state->ipa_ctx->sdap_id_ctx, state->ipa_ctx->ipa_options, state->ipa_ctx->server_mode->realm, state->ipa_ctx->view_name, ar); if (subreq == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); ret = ENOMEM; goto fail; } tevent_req_set_callback(subreq, ipa_get_ad_override_done, req); } else { ret = ipa_get_ad_apply_override_step(req); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_apply_override_step failed.\n"); goto fail; } } return; fail: state->dp_error = DP_ERR_FATAL; tevent_req_error(req, ret); return; } static void ipa_get_ad_override_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_get_ad_acct_state *state = tevent_req_data(req, struct ipa_get_ad_acct_state); errno_t ret; ret = ipa_get_ad_override_recv(subreq, &state->dp_error, state, &state->override_attrs); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); tevent_req_error(req, ret); return; } ret = ipa_get_ad_apply_override_step(req); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_apply_override_step failed.\n"); goto fail; } return; fail: state->dp_error = DP_ERR_FATAL; tevent_req_error(req, ret); return; } static void ipa_check_ghost_members_done(struct tevent_req *subreq); static errno_t ipa_check_ghost_members(struct tevent_req *req) { struct ipa_get_ad_acct_state *state = tevent_req_data(req, struct ipa_get_ad_acct_state); errno_t ret; struct tevent_req *subreq; if (state->obj_msg == NULL) { ret = get_object_from_cache(state, state->obj_dom, state->ar, &state->obj_msg); if (ret == ENOENT) { DEBUG(SSSDBG_MINOR_FAILURE, "Object not found, ending request\n"); return EOK; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); return ret; } } state->ghosts = ldb_msg_find_element(state->obj_msg, SYSDB_GHOST); if (state->ghosts != NULL) { /* Resolve ghost members */ subreq = ipa_resolve_user_list_send(state, state->ev, state->ipa_ctx, state->obj_dom->name, state->ghosts); if (subreq == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); return ENOMEM; } tevent_req_set_callback(subreq, ipa_check_ghost_members_done, req); return EAGAIN; } return EOK; } static void ipa_check_ghost_members_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_get_ad_acct_state *state = tevent_req_data(req, struct ipa_get_ad_acct_state); int ret; ret = ipa_resolve_user_list_recv(subreq, NULL); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list request failed [%d]\n", ret); tevent_req_error(req, ret); return; } tevent_req_done(req); return; } static errno_t ipa_get_ad_apply_override_step(struct tevent_req *req) { struct ipa_get_ad_acct_state *state = tevent_req_data(req, struct ipa_get_ad_acct_state); errno_t ret; struct tevent_req *subreq; const char *obj_name; int entry_type; size_t groups_count = 0; struct ldb_message **groups = NULL; const char *attrs[] = SYSDB_INITGR_ATTRS; const char *overide_name; if (state->override_attrs != NULL) { /* We are in ipa-server-mode, so the view is the default view by * definition. */ ret = sysdb_apply_default_override(state->obj_dom, state->override_attrs, state->obj_msg->dn); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_apply_default_override failed.\n"); return ret; } } entry_type = (state->ar->entry_type & BE_REQ_TYPE_MASK); if (entry_type != BE_REQ_INITGROUPS && entry_type != BE_REQ_USER && entry_type != BE_REQ_BY_SECID && entry_type != BE_REQ_GROUP) { tevent_req_done(req); return EOK; } /* expand ghost members, if any, to get group members with overrides * right. */ if (entry_type == BE_REQ_GROUP) { ret = ipa_check_ghost_members(req); if (ret == EOK) { tevent_req_done(req); return EOK; } else if (ret == EAGAIN) { return EOK; } else { DEBUG(SSSDBG_OP_FAILURE, "ipa_check_ghost_members failed.\n"); return ret; } } /* check if there is a override name which should replace the orignal * name in the memberUid sttribute. */ ret = sysdb_attrs_get_string(state->override_attrs, SYSDB_NAME, &overide_name); if (ret == EOK) { ret = sysdb_refresh_group_memberships(state->obj_dom, state->obj_msg->dn); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_replace_override_name_in_memberuid failed, " "ignored, member names might not show overrides.\n"); } } /* Replace ID with name in search filter */ if ((entry_type == BE_REQ_USER && state->ar->filter_type == BE_FILTER_IDNUM) || (entry_type == BE_REQ_INITGROUPS && state->ar->filter_type == BE_FILTER_SECID) || entry_type == BE_REQ_BY_SECID) { if (state->obj_msg == NULL) { ret = get_object_from_cache(state, state->obj_dom, state->ar, &state->obj_msg); if (ret == ENOENT) { DEBUG(SSSDBG_MINOR_FAILURE, "Object not found, ending request\n"); tevent_req_done(req); return EOK; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); return ret; } } obj_name = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_NAME, NULL); if (obj_name == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Cached object has no name.\n"); return EINVAL; } state->ar->filter_value = talloc_strdup(state->ar, obj_name); if (state->ar->filter_value == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); return ENOMEM; } state->ar->filter_type = BE_FILTER_NAME; state->ar->entry_type = BE_REQ_USER; } /* Lookup all groups the user is a member of which do not have ORIGINALAD * attributes set, i.e. where overrides might not have been applied. */ ret = sysdb_asq_search(state, state->obj_dom, state->obj_msg->dn, "(&("SYSDB_GC")("SYSDB_GIDNUM"=*)" \ "("SYSDB_POSIX"=TRUE)" \ "(!("ORIGINALAD_PREFIX SYSDB_GIDNUM"=*))" \ "(!("ORIGINALAD_PREFIX SYSDB_NAME"=*)))", SYSDB_INITGR_ATTR, attrs, &groups_count, &groups); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_groups_without_orig failed.\n"); return ret; } if (groups != NULL) { subreq = ipa_initgr_get_overrides_send(state, state->ev, state->ipa_ctx, state->obj_dom, groups_count, groups, SYSDB_SID_STR); if (subreq == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ipa_initgr_get_overrides_send failed.\n"); return ENOMEM; } tevent_req_set_callback(subreq, ipa_id_get_groups_overrides_done, req); return EOK; } ret = ipa_get_ad_ipa_membership_step(req); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_ipa_membership_step failed.\n"); return ret; } return EOK; } static void ipa_id_get_groups_overrides_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); errno_t ret; ret = ipa_initgr_get_overrides_recv(subreq, NULL); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "IPA resolve user groups overrides failed [%d].\n", ret); tevent_req_error(req, ret); return; } ret = ipa_get_ad_ipa_membership_step(req); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_ipa_membership_step failed.\n"); tevent_req_error(req, ret); return; } return; } static errno_t ipa_get_ad_ipa_membership_step(struct tevent_req *req) { struct ipa_get_ad_acct_state *state = tevent_req_data(req, struct ipa_get_ad_acct_state); struct tevent_req *subreq; /* For initgroups request we have to check IPA group memberships of AD * users. This has to be done for other user-request as well to make sure * IPA related attributes are not overwritten. */ subreq = ipa_get_ad_memberships_send(state, state->ev, state->ar, state->ipa_ctx->server_mode, state->obj_dom, state->ipa_ctx->sdap_id_ctx, state->ipa_ctx->server_mode->realm); if (subreq == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_memberships_send failed.\n"); return ENOMEM; } tevent_req_set_callback(subreq, ipa_get_ad_acct_done, req); return EOK; } static void ipa_get_ad_acct_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_get_ad_acct_state *state = tevent_req_data(req, struct ipa_get_ad_acct_state); errno_t ret; ret = ipa_get_ad_memberships_recv(subreq, &state->dp_error); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "IPA external groups lookup failed: %d\n", ret); tevent_req_error(req, ret); return; } tevent_req_done(req); } static errno_t ipa_get_ad_acct_recv(struct tevent_req *req, int *dp_error_out) { struct ipa_get_ad_acct_state *state = tevent_req_data(req, struct ipa_get_ad_acct_state); if (dp_error_out) { *dp_error_out = state->dp_error; } TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct ipa_srv_ad_acct_state { struct tevent_context *ev; struct ipa_id_ctx *ipa_ctx; struct sysdb_attrs *override_attrs; struct be_acct_req *ar; struct sss_domain_info *obj_dom; struct be_ctx *be_ctx; bool retry; int dp_error; }; static int ipa_srv_ad_acct_lookup_step(struct tevent_req *req); static void ipa_srv_ad_acct_lookup_done(struct tevent_req *subreq); static void ipa_srv_ad_acct_retried(struct tevent_req *subreq); static struct tevent_req * ipa_srv_ad_acct_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct ipa_id_ctx *ipa_ctx, struct sysdb_attrs *override_attrs, struct be_acct_req *ar) { errno_t ret; struct tevent_req *req; struct ipa_srv_ad_acct_state *state; req = tevent_req_create(mem_ctx, &state, struct ipa_srv_ad_acct_state); if (req == NULL) { return NULL; } state->ev = ev; state->ipa_ctx = ipa_ctx; state->override_attrs = override_attrs; state->ar = ar; state->retry = true; state->dp_error = DP_ERR_FATAL; state->be_ctx = ipa_ctx->sdap_id_ctx->be; state->obj_dom = find_domain_by_name( state->ipa_ctx->sdap_id_ctx->be->domain, state->ar->domain, true); if (state->obj_dom == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Domain not found\n"); ret = ERR_DOMAIN_NOT_FOUND; goto fail; } ret = ipa_srv_ad_acct_lookup_step(req); if (ret != EOK) { goto fail; } return req; fail: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static int ipa_srv_ad_acct_lookup_step(struct tevent_req *req) { struct tevent_req *subreq; struct ipa_srv_ad_acct_state *state = tevent_req_data(req, struct ipa_srv_ad_acct_state); DEBUG(SSSDBG_TRACE_FUNC, "Looking up AD account\n"); subreq = ipa_get_ad_acct_send(state, state->ev, state->ipa_ctx, state->override_attrs, state->ar); if (subreq == NULL) { return ENOMEM; } tevent_req_set_callback(subreq, ipa_srv_ad_acct_lookup_done, req); return EOK; } static void ipa_srv_ad_acct_lookup_done(struct tevent_req *subreq) { errno_t ret; int dp_error = DP_ERR_FATAL; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_srv_ad_acct_state *state = tevent_req_data(req, struct ipa_srv_ad_acct_state); ret = ipa_get_ad_acct_recv(subreq, &dp_error); talloc_free(subreq); if (ret == ERR_SUBDOM_INACTIVE && state->retry == true) { state->retry = false; DEBUG(SSSDBG_MINOR_FAILURE, "Sudomain lookup failed, will try to reset sudomain..\n"); subreq = ipa_server_trusted_dom_setup_send(state, state->ev, state->be_ctx, state->ipa_ctx, state->obj_dom); if (subreq == NULL) { goto fail; } tevent_req_set_callback(subreq, ipa_srv_ad_acct_retried, req); return; } else if (ret != EOK) { be_mark_dom_offline(state->obj_dom, state->be_ctx); DEBUG(SSSDBG_OP_FAILURE, "ipa_get_*_acct request failed: [%d]: %s.\n", ret, sss_strerror(ret)); goto fail; } state->dp_error = DP_ERR_OK; tevent_req_done(req); return; fail: state->dp_error = dp_error; tevent_req_error(req, ret); } static void ipa_srv_ad_acct_retried(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct ipa_srv_ad_acct_state *state = tevent_req_data(req, struct ipa_srv_ad_acct_state); ret = ipa_server_trusted_dom_setup_recv(subreq); talloc_free(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to re-set subdomain [%d]: %s\n", ret, sss_strerror(ret)); state->dp_error = DP_ERR_FATAL; tevent_req_error(req, ret); } DEBUG(SSSDBG_TRACE_FUNC, "Sudomain re-set, will retry lookup\n"); be_fo_reset_svc(state->be_ctx, state->obj_dom->name); ret = ipa_srv_ad_acct_lookup_step(req); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to look up AD acct [%d]: %s\n", ret, sss_strerror(ret)); state->dp_error = DP_ERR_FATAL; tevent_req_error(req, ret); } } static errno_t ipa_srv_ad_acct_recv(struct tevent_req *req, int *dp_error_out) { struct ipa_srv_ad_acct_state *state = tevent_req_data(req, struct ipa_srv_ad_acct_state); if (dp_error_out) { *dp_error_out = state->dp_error; } TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; }