/* SSSD Authors: Stephen Gallagher 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 "util/util.h" #include "providers/ldap/sdap_async.h" #include "providers/ldap/ldap_common.h" #include "providers/ldap/sdap_async_private.h" #include "providers/ldap/sdap_idmap.h" #include "providers/ad/ad_common.h" #include "lib/idmap/sss_idmap.h" struct sdap_ad_match_rule_initgr_state { struct tevent_context *ev; struct sdap_options *opts; struct sysdb_ctx *sysdb; struct sss_domain_info *domain; struct sdap_handle *sh; const char *name; const char *orig_dn; const char **attrs; int timeout; const char *base_filter; char *filter; size_t count; struct sysdb_attrs **groups; size_t base_iter; struct sdap_search_base **search_bases; }; static errno_t sdap_get_ad_match_rule_initgroups_next_base(struct tevent_req *req); static void sdap_get_ad_match_rule_initgroups_step(struct tevent_req *subreq); struct tevent_req * sdap_get_ad_match_rule_initgroups_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_options *opts, struct sysdb_ctx *sysdb, struct sss_domain_info *domain, struct sdap_handle *sh, const char *name, const char *orig_dn, int timeout) { errno_t ret; struct tevent_req *req; struct sdap_ad_match_rule_initgr_state *state; const char **filter_members; char *sanitized_user_dn; char *oc_list; req = tevent_req_create(mem_ctx, &state, struct sdap_ad_match_rule_initgr_state); if (!req) return NULL; state->ev = ev; state->opts = opts; state->sysdb = sysdb; state->domain = domain; state->sh = sh; state->name = name; state->orig_dn = orig_dn; state->base_iter = 0; state->search_bases = opts->sdom->group_search_bases; /* Request all of the group attributes that we know * about, except for 'member' because that wastes a * lot of bandwidth here and we only really * care about a single member (the one we already * have). */ filter_members = talloc_array(state, const char *, 2); if (!filter_members) { ret = ENOMEM; goto immediate; } filter_members[0] = opts->group_map[SDAP_AT_GROUP_MEMBER].name; filter_members[1] = NULL; ret = build_attrs_from_map(state, opts->group_map, SDAP_OPTS_GROUP, filter_members, &state->attrs, NULL); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not build attribute map: [%s]\n", strerror(ret)); goto immediate; } /* Sanitize the user DN in case we have special characters in DN */ ret = sss_filter_sanitize(state, state->orig_dn, &sanitized_user_dn); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not sanitize user DN: %s\n", strerror(ret)); goto immediate; } /* Craft a special filter according to * http://msdn.microsoft.com/en-us/library/windows/desktop/aa746475%28v=vs.85%29.aspx */ oc_list = sdap_make_oc_list(state, state->opts->group_map); if (oc_list == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); ret = ENOMEM; goto immediate; } state->base_filter = talloc_asprintf(state, "(&(%s:%s:=%s)(%s))", state->opts->group_map[SDAP_AT_GROUP_MEMBER].name, SDAP_MATCHING_RULE_IN_CHAIN, sanitized_user_dn, oc_list); talloc_zfree(sanitized_user_dn); if (!state->base_filter) { ret = ENOMEM; goto immediate; } /* Start the loop through the search bases to get all of the * groups to which this user belongs. */ ret = sdap_get_ad_match_rule_initgroups_next_base(req); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sdap_get_ad_match_rule_members_next_base failed: [%s]\n", strerror(ret)); goto immediate; } return req; immediate: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static errno_t sdap_get_ad_match_rule_initgroups_next_base(struct tevent_req *req) { struct tevent_req *subreq; struct sdap_ad_match_rule_initgr_state *state; state = tevent_req_data(req, struct sdap_ad_match_rule_initgr_state); talloc_zfree(state->filter); state->filter = sdap_get_id_specific_filter(state, state->base_filter, state->search_bases[state->base_iter]->filter); if (!state->filter) { return ENOMEM; } DEBUG(SSSDBG_TRACE_FUNC, "Searching for groups with base [%s]\n", state->search_bases[state->base_iter]->basedn); subreq = sdap_get_generic_send( state, state->ev, state->opts, state->sh, state->search_bases[state->base_iter]->basedn, state->search_bases[state->base_iter]->scope, state->filter, state->attrs, state->opts->group_map, SDAP_OPTS_GROUP, state->timeout, true); if (!subreq) { return ENOMEM; } tevent_req_set_callback(subreq, sdap_get_ad_match_rule_initgroups_step, req); return EOK; } static void sdap_get_ad_match_rule_initgroups_step(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_ad_match_rule_initgr_state *state = tevent_req_data(req, struct sdap_ad_match_rule_initgr_state); size_t count, i; struct sysdb_attrs **groups; char **sysdb_grouplist; ret = sdap_get_generic_recv(subreq, state, &count, &groups); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "LDAP search failed: [%s]\n", sss_strerror(ret)); goto error; } DEBUG(SSSDBG_TRACE_LIBS, "Search for users returned %zu results\n", count); /* Add this batch of groups to the list */ if (count > 0) { state->groups = talloc_realloc(state, state->groups, struct sysdb_attrs *, state->count + count + 1); if (!state->groups) { tevent_req_error(req, ENOMEM); return; } /* Copy the new groups into the list */ for (i = 0; i < count; i++) { state->groups[state->count + i] = talloc_steal(state->groups, groups[i]); } state->count += count; state->groups[state->count] = NULL; } /* Continue checking other search bases */ state->base_iter++; if (state->search_bases[state->base_iter]) { /* There are more search bases to try */ ret = sdap_get_ad_match_rule_initgroups_next_base(req); if (ret != EOK) { goto error; } return; } /* No more search bases. Save the groups. */ if (state->count == 0) { DEBUG(SSSDBG_TRACE_LIBS, "User is not a member of any group in the search bases\n"); } /* Get the current sysdb group list for this user * so we can update it. */ ret = get_sysdb_grouplist(state, state->sysdb, state->domain, state->name, &sysdb_grouplist); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not get the list of groups for [%s] in the sysdb: " "[%s]\n", state->name, strerror(ret)); goto error; } /* The extensibleMatch search rule eliminates the need for * nested group searches, so we can just update the * memberships now. */ ret = sdap_initgr_common_store(state->sysdb, state->domain, state->opts, state->name, SYSDB_MEMBER_USER, sysdb_grouplist, state->groups, state->count); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not store groups for user [%s]: [%s]\n", state->name, strerror(ret)); goto error; } tevent_req_done(req); return; error: tevent_req_error(req, ret); } errno_t sdap_get_ad_match_rule_initgroups_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct sdap_get_ad_tokengroups_state { struct tevent_context *ev; struct sss_idmap_ctx *idmap_ctx; const char *username; char **sids; size_t num_sids; }; static void sdap_get_ad_tokengroups_done(struct tevent_req *subreq); static struct tevent_req * sdap_get_ad_tokengroups_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh, const char *name, const char *orig_dn, int timeout) { struct sdap_get_ad_tokengroups_state *state = NULL; struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; const char *attrs[] = {AD_TOKENGROUPS_ATTR, NULL}; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_get_ad_tokengroups_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->idmap_ctx = opts->idmap_ctx->map; state->ev = ev; state->username = talloc_strdup(state, name); if (state->username == NULL) { ret = ENOMEM; goto immediately; } subreq = sdap_get_generic_send(state, state->ev, opts, sh, orig_dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, 0, timeout, false); if (subreq == NULL) { ret = ENOMEM; goto immediately; } tevent_req_set_callback(subreq, sdap_get_ad_tokengroups_done, req); return req; immediately: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, ev); return req; } static void sdap_get_ad_tokengroups_done(struct tevent_req *subreq) { TALLOC_CTX *tmp_ctx = NULL; struct sdap_get_ad_tokengroups_state *state = NULL; struct tevent_req *req = NULL; struct sysdb_attrs **users = NULL; struct ldb_message_element *el = NULL; enum idmap_error_code err; char *sid_str = NULL; size_t num_users; size_t i; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_get_ad_tokengroups_state); ret = sdap_get_generic_recv(subreq, tmp_ctx, &num_users, &users); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "LDAP search failed: [%s]\n", sss_strerror(ret)); goto done; } if (num_users != 1) { DEBUG(SSSDBG_MINOR_FAILURE, "More than one result on a base search!\n"); ret = EINVAL; goto done; } /* get the list of sids from tokengroups */ ret = sysdb_attrs_get_el_ext(users[0], AD_TOKENGROUPS_ATTR, false, &el); if (ret == ENOENT) { DEBUG(SSSDBG_TRACE_LIBS, "No tokenGroups entries for [%s]\n", state->username); state->sids = NULL; state->num_sids = 0; ret = EOK; goto done; } else if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not read tokenGroups attribute: " "[%s]\n", strerror(ret)); goto done; } state->num_sids = 0; state->sids = talloc_zero_array(state, char*, el->num_values); if (state->sids == NULL) { ret = ENOMEM; goto done; } /* convert binary sid to string */ for (i = 0; i < el->num_values; i++) { err = sss_idmap_bin_sid_to_sid(state->idmap_ctx, el->values[i].data, el->values[i].length, &sid_str); if (err != IDMAP_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not convert binary SID to string: [%s]. Skipping\n", idmap_error_string(err)); continue; } state->sids[i] = talloc_move(state->sids, &sid_str); state->num_sids++; } /* shrink array to final number of elements */ state->sids = talloc_realloc(state, state->sids, char*, state->num_sids); if (state->sids == NULL) { ret = ENOMEM; goto done; } ret = EOK; done: talloc_free(tmp_ctx); if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static errno_t sdap_get_ad_tokengroups_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, size_t *_num_sids, char ***_sids) { struct sdap_get_ad_tokengroups_state *state = NULL; state = tevent_req_data(req, struct sdap_get_ad_tokengroups_state); TEVENT_REQ_RETURN_ON_ERROR(req); if (_num_sids != NULL) { *_num_sids = state->num_sids; } if (_sids != NULL) { *_sids = talloc_steal(mem_ctx, state->sids); } return EOK; } static errno_t sdap_ad_tokengroups_update_members(const char *username, struct sysdb_ctx *sysdb, struct sss_domain_info *domain, char **ldap_groups) { TALLOC_CTX *tmp_ctx = NULL; char **sysdb_groups = NULL; char **add_groups = NULL; char **del_groups = NULL; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); return ENOMEM; } /* Get the current sysdb group list for this user so we can update it. */ ret = get_sysdb_grouplist_dn(tmp_ctx, sysdb, domain, username, &sysdb_groups); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not get the list of groups for " "[%s] in the sysdb: [%s]\n", username, strerror(ret)); goto done; } /* Find the differences between the sysdb and LDAP lists. * Groups in the sysdb only must be removed. */ ret = diff_string_lists(tmp_ctx, ldap_groups, sysdb_groups, &add_groups, &del_groups, NULL); if (ret != EOK) { goto done; } DEBUG(SSSDBG_TRACE_LIBS, "Updating memberships for [%s]\n", username); ret = sysdb_update_members_dn(domain, username, SYSDB_MEMBER_USER, (const char *const *) add_groups, (const char *const *) del_groups); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", ret, strerror(ret)); goto done; } done: talloc_free(tmp_ctx); return ret; } struct sdap_ad_resolve_sids_state { struct tevent_context *ev; struct sdap_id_ctx *id_ctx; struct sdap_id_conn_ctx *conn; struct sdap_options *opts; struct sss_domain_info *domain; char **sids; const char *current_sid; int index; }; static errno_t sdap_ad_resolve_sids_step(struct tevent_req *req); static void sdap_ad_resolve_sids_done(struct tevent_req *subreq); static struct tevent_req * sdap_ad_resolve_sids_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_id_ctx *id_ctx, struct sdap_id_conn_ctx *conn, struct sdap_options *opts, struct sss_domain_info *domain, char **sids) { struct sdap_ad_resolve_sids_state *state = NULL; struct tevent_req *req = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_ad_resolve_sids_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->ev = ev; state->id_ctx = id_ctx; state->conn = conn; state->opts = opts; state->domain = get_domains_head(domain); state->sids = sids; state->index = 0; if (state->sids == NULL || state->sids[0] == NULL) { ret = EOK; goto immediately; } ret = sdap_ad_resolve_sids_step(req); if (ret != EAGAIN) { goto immediately; } return req; immediately: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, ev); return req; } static errno_t sdap_ad_resolve_sids_step(struct tevent_req *req) { struct sdap_ad_resolve_sids_state *state = NULL; struct tevent_req *subreq = NULL; struct sdap_domain *sdap_domain = NULL; struct sss_domain_info *domain = NULL; state = tevent_req_data(req, struct sdap_ad_resolve_sids_state); do { state->current_sid = state->sids[state->index]; if (state->current_sid == NULL) { return EOK; } state->index++; domain = sss_get_domain_by_sid_ldap_fallback(state->domain, state->current_sid); if (domain == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "SID %s does not belong to any known " "domain\n", state->current_sid); } } while (domain == NULL); sdap_domain = sdap_domain_get(state->opts, domain); if (sdap_domain == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "SDAP domain does not exist?\n"); return ERR_INTERNAL; } subreq = groups_get_send(state, state->ev, state->id_ctx, sdap_domain, state->conn, state->current_sid, BE_FILTER_SECID, BE_ATTR_CORE, false); if (subreq == NULL) { return ENOMEM; } tevent_req_set_callback(subreq, sdap_ad_resolve_sids_done, req); return EAGAIN; } static void sdap_ad_resolve_sids_done(struct tevent_req *subreq) { struct sdap_ad_resolve_sids_state *state = NULL; struct tevent_req *req = NULL; int dp_error; int sdap_error; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_ad_resolve_sids_state); ret = groups_get_recv(subreq, &dp_error, &sdap_error); talloc_zfree(subreq); if (ret == EOK && sdap_error == ENOENT && dp_error == DP_ERR_OK) { /* Group was not found, we will ignore the error and continue with * next group. This may happen for example if the group is built-in, * but a custom search base is provided. */ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve SID %s - will try next sid.\n", state->current_sid); } else if (ret != EOK || sdap_error != EOK || dp_error != DP_ERR_OK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve SID %s [dp_error: %d, " "sdap_error: %d, ret: %d]: %s\n", state->current_sid, dp_error, sdap_error, ret, strerror(ret)); goto done; } ret = sdap_ad_resolve_sids_step(req); if (ret == EAGAIN) { /* continue with next SID */ return; } done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static errno_t sdap_ad_resolve_sids_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct sdap_ad_tokengroups_initgr_mapping_state { struct tevent_context *ev; struct sdap_options *opts; struct sdap_handle *sh; struct sdap_idmap_ctx *idmap_ctx; struct sysdb_ctx *sysdb; struct sss_domain_info *domain; const char *orig_dn; int timeout; const char *username; struct sdap_id_op *op; }; static void sdap_ad_tokengroups_initgr_mapping_connect_done(struct tevent_req *subreq); static void sdap_ad_tokengroups_initgr_mapping_done(struct tevent_req *subreq); static errno_t handle_missing_pvt(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_options *opts, const char *orig_dn, int timeout, const char *username, struct sdap_handle *sh, struct tevent_req *req, tevent_req_fn callback); static struct tevent_req * sdap_ad_tokengroups_initgr_mapping_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_options *opts, struct sysdb_ctx *sysdb, struct sss_domain_info *domain, struct sdap_handle *sh, const char *name, const char *orig_dn, int timeout) { struct sdap_ad_tokengroups_initgr_mapping_state *state = NULL; struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_domain *sdom; struct ad_id_ctx *subdom_id_ctx; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_ad_tokengroups_initgr_mapping_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->ev = ev; state->opts = opts; state->sh = sh; state->idmap_ctx = opts->idmap_ctx; state->sysdb = sysdb; state->domain = domain; state->timeout = timeout; state->orig_dn = orig_dn; state->username = talloc_strdup(state, name); if (state->username == NULL) { ret = ENOMEM; goto immediately; } sdom = sdap_domain_get(opts, domain); if (sdom == NULL || sdom->pvt == NULL) { ret = handle_missing_pvt(mem_ctx, ev, opts, orig_dn, timeout, state->username, sh, req, sdap_ad_tokengroups_initgr_mapping_done); if (ret == EOK) { return req; } else { DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", domain->name); goto immediately; } } subdom_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); state->op = sdap_id_op_create(state, subdom_id_ctx->ldap_ctx->conn_cache); if (!state->op) { DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); ret = ENOMEM; goto immediately; } subreq = sdap_id_op_connect_send(state->op, state, &ret); if (subreq == NULL) { ret = ENOMEM; goto immediately; } tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_mapping_connect_done, req); return req; immediately: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, ev); return req; } static void sdap_ad_tokengroups_initgr_mapping_connect_done(struct tevent_req *subreq) { struct sdap_ad_tokengroups_initgr_mapping_state *state = NULL; struct tevent_req *req = NULL; int ret; int dp_error = DP_ERR_FATAL; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_mapping_state); ret = sdap_id_op_connect_recv(subreq, &dp_error); talloc_zfree(subreq); if (ret != EOK) { tevent_req_error(req, ret); return; } subreq = sdap_get_ad_tokengroups_send(state, state->ev, state->opts, sdap_id_op_handle(state->op), state->username, state->orig_dn, state->timeout); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_mapping_done, req); return; } static void sdap_ad_tokengroups_initgr_mapping_done(struct tevent_req *subreq) { TALLOC_CTX *tmp_ctx = NULL; struct sdap_ad_tokengroups_initgr_mapping_state *state = NULL; struct tevent_req *req = NULL; struct sss_domain_info *domain = NULL; struct ldb_message *msg = NULL; const char *attrs[] = {SYSDB_NAME, NULL}; const char *name = NULL; const char *sid = NULL; char **sids = NULL; size_t num_sids = 0; size_t i; time_t now; gid_t gid; char **groups = NULL; size_t num_groups; errno_t ret, sret; bool in_transaction = false; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); ret = ENOMEM; goto done; } req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_mapping_state); ret = sdap_get_ad_tokengroups_recv(state, subreq, &num_sids, &sids); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to acquire tokengroups [%d]: %s\n", ret, strerror(ret)); goto done; } num_groups = 0; groups = talloc_zero_array(tmp_ctx, char*, num_sids + 1); if (groups == NULL) { ret = ENOMEM; goto done; } now = time(NULL); ret = sysdb_transaction_start(state->sysdb); if (ret != EOK) { goto done; } in_transaction = true; for (i = 0; i < num_sids; i++) { sid = sids[i]; DEBUG(SSSDBG_TRACE_LIBS, "Processing membership SID [%s]\n", sid); ret = sdap_idmap_sid_to_unix(state->idmap_ctx, sid, &gid); if (ret == ENOTSUP) { DEBUG(SSSDBG_TRACE_FUNC, "Skipping built-in object.\n"); continue; } else if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not convert SID to GID: [%s]. " "Skipping\n", strerror(ret)); continue; } domain = sss_get_domain_by_sid_ldap_fallback(state->domain, sid); if (domain == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "Domain not found for SID %s\n", sid); continue; } DEBUG(SSSDBG_TRACE_LIBS, "SID [%s] maps to GID [%"SPRIgid"]\n", sid, gid); /* Check whether this GID already exists in the sysdb */ ret = sysdb_search_group_by_gid(tmp_ctx, domain, gid, attrs, &msg); if (ret == EOK) { name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); if (name == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not retrieve group name from sysdb\n"); ret = EINVAL; goto done; } } else if (ret == ENOENT) { /* This is a new group. For now, we will store it under the name * of its SID. When a direct lookup of the group or its GID occurs, * it will replace this temporary entry. */ name = sid; ret = sysdb_add_incomplete_group(domain, name, gid, NULL, sid, false, now); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not create incomplete " "group: [%s]\n", strerror(ret)); goto done; } } else { /* Unexpected error */ DEBUG(SSSDBG_MINOR_FAILURE, "Could not look up group in sysdb: " "[%s]\n", strerror(ret)); goto done; } groups[num_groups] = sysdb_group_strdn(tmp_ctx, domain->name, name); if (groups[num_groups] == NULL) { ret = ENOMEM; goto done; } num_groups++; } groups[num_groups] = NULL; ret = sdap_ad_tokengroups_update_members(state->username, state->sysdb, state->domain, groups); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", ret, strerror(ret)); goto done; } ret = sysdb_transaction_commit(state->sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not commit transaction! [%s]\n", strerror(ret)); goto done; } in_transaction = false; done: talloc_free(tmp_ctx); if (in_transaction) { sret = sysdb_transaction_cancel(state->sysdb); DEBUG(SSSDBG_FATAL_FAILURE, "Could not cancel transaction! [%s]\n", strerror(sret)); } if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static int sdap_ad_tokengroups_initgr_mapping_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct sdap_ad_tokengroups_initgr_posix_state { struct tevent_context *ev; struct sdap_id_ctx *id_ctx; struct sdap_id_conn_ctx *conn; struct sdap_options *opts; struct sdap_handle *sh; struct sysdb_ctx *sysdb; struct sss_domain_info *domain; const char *orig_dn; int timeout; const char *username; struct sdap_id_op *op; char **missing_sids; size_t num_missing_sids; char **cached_groups; size_t num_cached_groups; }; static void sdap_ad_tokengroups_initgr_posix_tg_done(struct tevent_req *subreq); static void sdap_ad_tokengroups_initgr_posix_sids_connect_done(struct tevent_req *subreq); static void sdap_ad_tokengroups_initgr_posix_sids_done(struct tevent_req *subreq); static struct tevent_req * sdap_ad_tokengroups_initgr_posix_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_id_ctx *id_ctx, struct sdap_id_conn_ctx *conn, struct sdap_options *opts, struct sysdb_ctx *sysdb, struct sss_domain_info *domain, struct sdap_handle *sh, const char *name, const char *orig_dn, int timeout) { struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_domain *sdom; struct ad_id_ctx *subdom_id_ctx; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_ad_tokengroups_initgr_posix_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->ev = ev; state->id_ctx = id_ctx; state->conn = conn; state->opts = opts; state->sh = sh; state->sysdb = sysdb; state->domain = domain; state->orig_dn = orig_dn; state->timeout = timeout; state->username = talloc_strdup(state, name); if (state->username == NULL) { ret = ENOMEM; goto immediately; } sdom = sdap_domain_get(opts, domain); if (sdom == NULL || sdom->pvt == NULL) { ret = handle_missing_pvt(mem_ctx, ev, opts, orig_dn, timeout, state->username, sh, req, sdap_ad_tokengroups_initgr_posix_tg_done); if (ret == EOK) { return req; } else { DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", domain->name); goto immediately; } } subdom_id_ctx = talloc_get_type(sdom->pvt, struct ad_id_ctx); state->op = sdap_id_op_create(state, subdom_id_ctx->ldap_ctx->conn_cache); if (!state->op) { DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); ret = ENOMEM; goto immediately; } subreq = sdap_id_op_connect_send(state->op, state, &ret); if (subreq == NULL) { ret = ENOMEM; goto immediately; } tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_posix_sids_connect_done, req); return req; immediately: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, ev); return req; } static void sdap_ad_tokengroups_initgr_posix_sids_connect_done(struct tevent_req *subreq) { struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; struct tevent_req *req = NULL; int ret; int dp_error = DP_ERR_FATAL; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_posix_state); ret = sdap_id_op_connect_recv(subreq, &dp_error); talloc_zfree(subreq); if (ret != EOK) { tevent_req_error(req, ret); return; } subreq = sdap_get_ad_tokengroups_send(state, state->ev, state->opts, sdap_id_op_handle(state->op), state->username, state->orig_dn, state->timeout); if (subreq == NULL) { tevent_req_error(req, ENOMEM); return; } tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_posix_tg_done, req); return; } static errno_t sdap_ad_tokengroups_get_posix_members(TALLOC_CTX *mem_ctx, struct sdap_ad_tokengroups_initgr_posix_state *state, size_t num_sids, char **sids, size_t *_num_missing, char ***_missing, size_t *_num_valid, char ***_valid_groups) { TALLOC_CTX *tmp_ctx = NULL; struct sss_domain_info *domain = NULL; struct ldb_message *msg = NULL; const char *attrs[] = {SYSDB_NAME, NULL}; const char *name = NULL; char *sid = NULL; char **valid_groups = NULL; size_t num_valid_groups; char **missing_sids = NULL; size_t num_missing_sids; size_t i; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); ret = ENOMEM; goto done; } num_valid_groups = 0; valid_groups = talloc_zero_array(tmp_ctx, char*, num_sids + 1); if (valid_groups == NULL) { ret = ENOMEM; goto done; } num_missing_sids = 0; missing_sids = talloc_zero_array(tmp_ctx, char*, num_sids + 1); if (missing_sids == NULL) { ret = ENOMEM; goto done; } /* For each SID check if it is already present in the cache. If yes, we * will get name of the group and update the membership. Otherwise we need * to remember the SID and download missing groups one by one. */ for (i = 0; i < num_sids; i++) { sid = sids[i]; DEBUG(SSSDBG_TRACE_LIBS, "Processing membership SID [%s]\n", sid); domain = sss_get_domain_by_sid_ldap_fallback(state->domain, sid); if (domain == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "Domain not found for SID %s\n", sid); continue; } ret = sysdb_search_group_by_sid_str(tmp_ctx, domain, sid, attrs, &msg); if (ret == EOK) { /* we will update membership of this group */ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); if (name == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "Could not retrieve group name from sysdb\n"); ret = EINVAL; goto done; } valid_groups[num_valid_groups] = sysdb_group_strdn(valid_groups, domain->name, name); if (valid_groups[num_valid_groups] == NULL) { ret = ENOMEM; goto done; } num_valid_groups++; } else if (ret == ENOENT) { if (_missing != NULL) { /* we need to download this group */ missing_sids[num_missing_sids] = talloc_steal(missing_sids, sid); num_missing_sids++; DEBUG(SSSDBG_TRACE_FUNC, "Missing SID %s will be downloaded\n", sid); } /* else: We have downloaded missing groups but some of them may * remained missing because they are outside of search base. We * will just ignore them and continue with the next group. */ } else { DEBUG(SSSDBG_MINOR_FAILURE, "Could not look up SID %s in sysdb: " "[%s]\n", sid, strerror(ret)); goto done; } } valid_groups[num_valid_groups] = NULL; missing_sids[num_missing_sids] = NULL; /* return list of missing groups */ if (_missing != NULL) { *_missing = talloc_steal(mem_ctx, missing_sids); *_num_missing = num_missing_sids; } /* return list of missing groups */ if (_valid_groups != NULL) { *_valid_groups = talloc_steal(mem_ctx, valid_groups); *_num_valid = num_valid_groups; } ret = EOK; done: talloc_free(tmp_ctx); return ret; } static void sdap_ad_tokengroups_initgr_posix_tg_done(struct tevent_req *subreq) { struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; struct tevent_req *req = NULL; char **sids = NULL; size_t num_sids = 0; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_posix_state); ret = sdap_get_ad_tokengroups_recv(state, subreq, &num_sids, &sids); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to acquire tokengroups [%d]: %s\n", ret, strerror(ret)); goto done; } ret = sdap_ad_tokengroups_get_posix_members(state, state, num_sids, sids, &state->num_missing_sids, &state->missing_sids, &state->num_cached_groups, &state->cached_groups); if (ret != EOK) { goto done; } /* download missing SIDs */ subreq = sdap_ad_resolve_sids_send(state, state->ev, state->id_ctx, state->conn, state->opts, state->domain, state->missing_sids); if (subreq == NULL) { ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgr_posix_sids_done, req); return; done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static char **concatenate_string_array(TALLOC_CTX *mem_ctx, char **arr1, size_t len1, char **arr2, size_t len2); static void sdap_ad_tokengroups_initgr_posix_sids_done(struct tevent_req *subreq) { struct sdap_ad_tokengroups_initgr_posix_state *state = NULL; struct tevent_req *req = NULL; errno_t ret; char **cached_groups; size_t num_cached_groups; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_ad_tokengroups_initgr_posix_state); ret = sdap_ad_resolve_sids_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve missing SIDs " "[%d]: %s\n", ret, strerror(ret)); goto done; } ret = sdap_ad_tokengroups_get_posix_members(state, state, state->num_missing_sids, state->missing_sids, NULL, NULL, &num_cached_groups, &cached_groups); if (ret != EOK){ DEBUG(SSSDBG_MINOR_FAILURE, "sdap_ad_tokengroups_get_posix_members failed [%d]: %s\n", ret, strerror(ret)); goto done; } state->cached_groups = concatenate_string_array(state, state->cached_groups, state->num_cached_groups, cached_groups, num_cached_groups); if (state->cached_groups == NULL) { ret = ENOMEM; goto done; } /* update membership of existing groups */ ret = sdap_ad_tokengroups_update_members(state->username, state->sysdb, state->domain, state->cached_groups); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", ret, strerror(ret)); goto done; } done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static char **concatenate_string_array(TALLOC_CTX *mem_ctx, char **arr1, size_t len1, char **arr2, size_t len2) { size_t i, j; size_t new_size = len1 + len2; char ** string_array = talloc_realloc(mem_ctx, arr1, char *, new_size + 1); if (string_array == NULL) { return NULL; } for (i=len1, j=0; i < new_size; ++i,++j) { string_array[i] = talloc_steal(string_array, arr2[j]); } string_array[i] = NULL; return string_array; } static errno_t sdap_ad_tokengroups_initgr_posix_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct sdap_ad_tokengroups_initgroups_state { bool use_id_mapping; struct sss_domain_info *domain; }; static void sdap_ad_tokengroups_initgroups_done(struct tevent_req *subreq); struct tevent_req * sdap_ad_tokengroups_initgroups_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_id_ctx *id_ctx, struct sdap_id_conn_ctx *conn, struct sdap_options *opts, struct sysdb_ctx *sysdb, struct sss_domain_info *domain, struct sdap_handle *sh, const char *name, const char *orig_dn, int timeout, bool use_id_mapping) { struct sdap_ad_tokengroups_initgroups_state *state = NULL; struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_ad_tokengroups_initgroups_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->use_id_mapping = use_id_mapping; state->domain = domain; if (state->use_id_mapping && !IS_SUBDOMAIN(state->domain)) { subreq = sdap_ad_tokengroups_initgr_mapping_send(state, ev, opts, sysdb, domain, sh, name, orig_dn, timeout); } else { subreq = sdap_ad_tokengroups_initgr_posix_send(state, ev, id_ctx, conn, opts, sysdb, domain, sh, name, orig_dn, timeout); } if (subreq == NULL) { ret = ENOMEM; goto immediately; } tevent_req_set_callback(subreq, sdap_ad_tokengroups_initgroups_done, req); return req; immediately: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, ev); return req; } static void sdap_ad_tokengroups_initgroups_done(struct tevent_req *subreq) { struct sdap_ad_tokengroups_initgroups_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 sdap_ad_tokengroups_initgroups_state); if (state->use_id_mapping && !IS_SUBDOMAIN(state->domain)) { ret = sdap_ad_tokengroups_initgr_mapping_recv(subreq); } else { ret = sdap_ad_tokengroups_initgr_posix_recv(subreq); } talloc_zfree(subreq); if (ret != EOK) { goto done; } done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } errno_t sdap_ad_tokengroups_initgroups_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } static errno_t handle_missing_pvt(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_options *opts, const char *orig_dn, int timeout, const char *username, struct sdap_handle *sh, struct tevent_req *req, tevent_req_fn callback) { struct tevent_req *subreq = NULL; errno_t ret; if (sh != NULL) { /* plain LDAP provider already has a sdap_handle */ subreq = sdap_get_ad_tokengroups_send(mem_ctx, ev, opts, sh, username, orig_dn, timeout); if (subreq == NULL) { ret = ENOMEM; tevent_req_error(req, ret); goto done; } tevent_req_set_callback(subreq, callback, req); ret = EOK; goto done; } else { ret = EINVAL; goto done; } done: return ret; }