/* SSSD Authors: Pavel Březina Copyright (C) 2013 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 #include #include #include #include #include #include #include "util/util.h" #include "util/probes.h" #include "db/sysdb.h" #include "providers/ldap/ldap_common.h" #include "providers/ldap/sdap_async.h" #include "providers/ldap/sdap_async_private.h" #include "providers/ldap/sdap_idmap.h" #include "providers/ipa/ipa_dn.h" #define sdap_nested_group_sysdb_search_users(domain, dn) \ sdap_nested_group_sysdb_search((domain), (dn), true) #define sdap_nested_group_sysdb_search_groups(domain, dn) \ sdap_nested_group_sysdb_search((domain), (dn), false) enum sdap_nested_group_dn_type { SDAP_NESTED_GROUP_DN_USER, SDAP_NESTED_GROUP_DN_GROUP, SDAP_NESTED_GROUP_DN_UNKNOWN }; struct sdap_nested_group_member { enum sdap_nested_group_dn_type type; const char *dn; const char *user_filter; const char *group_filter; }; #ifndef EXTERNAL_MEMBERS_CHUNK #define EXTERNAL_MEMBERS_CHUNK 16 #endif /* EXTERNAL_MEMBERS_CHUNK */ struct sdap_external_missing_member { const char **parent_group_dns; size_t parent_dn_idx; }; struct sdap_nested_group_ctx { struct sss_domain_info *domain; struct sdap_options *opts; struct sdap_search_base **user_search_bases; struct sdap_search_base **group_search_bases; struct sdap_search_base **ignore_user_search_bases; struct sdap_handle *sh; hash_table_t *users; hash_table_t *groups; hash_table_t *missing_external; bool try_deref; int deref_threshold; int max_nesting_level; }; static struct tevent_req * sdap_nested_group_process_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, int nesting_level, struct sysdb_attrs *group); static errno_t sdap_nested_group_process_recv(struct tevent_req *req); static struct tevent_req * sdap_nested_group_single_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct sdap_nested_group_member *members, int num_members, int num_groups_max, int nesting_level); static errno_t sdap_nested_group_single_recv(struct tevent_req *req); static struct tevent_req * sdap_nested_group_lookup_user_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct sdap_nested_group_member *member); static errno_t sdap_nested_group_lookup_user_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct sysdb_attrs **_user); static struct tevent_req * sdap_nested_group_lookup_group_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct sdap_nested_group_member *member); static errno_t sdap_nested_group_lookup_group_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct sysdb_attrs **_group); static struct tevent_req * sdap_nested_group_lookup_unknown_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct sdap_nested_group_member *member); static errno_t sdap_nested_group_lookup_unknown_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct sysdb_attrs **_entry, enum sdap_nested_group_dn_type *_type); static struct tevent_req * sdap_nested_group_deref_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct ldb_message_element *members, const char *group_dn, int nesting_level); static errno_t sdap_nested_group_deref_recv(struct tevent_req *req); static errno_t sdap_nested_group_extract_hash_table(TALLOC_CTX *mem_ctx, hash_table_t *table, unsigned long *_num_entries, struct sysdb_attrs ***_entries) { struct sysdb_attrs **entries = NULL; struct sysdb_attrs *entry = NULL; hash_value_t *values; unsigned long num_entries; unsigned int i; bool hret; errno_t ret; hret = hash_values(table, &num_entries, &values); if (hret != HASH_SUCCESS) { ret = EIO; goto done; } if (num_entries > 0) { entries = talloc_array(mem_ctx, struct sysdb_attrs *, num_entries); if (entries == NULL) { ret = ENOMEM; goto done; } for (i = 0; i < num_entries; i++) { entry = talloc_get_type(values[i].ptr, struct sysdb_attrs); entries[i] = talloc_steal(entries, entry); } } if (_num_entries != NULL) { *_num_entries = num_entries; } if (_entries != NULL) { *_entries = entries; } ret = EOK; done: talloc_free(values); if (ret != EOK) { talloc_free(entries); } return ret; } static errno_t sdap_nested_group_hash_insert(hash_table_t *table, const char *entry_key, void *entry_value, bool overwrite, const char *table_name) { hash_key_t key; hash_value_t value; int hret; DEBUG(SSSDBG_TRACE_ALL, "Inserting [%s] into hash table [%s]\n", entry_key, table_name); key.type = HASH_KEY_STRING; key.c_str = discard_const(entry_key); /* hash_enter() will make a copy */ if (overwrite == false && hash_has_key(table, &key)) { return EEXIST; } value.type = HASH_VALUE_PTR; value.ptr = entry_value; hret = hash_enter(table, &key, &value); if (hret != HASH_SUCCESS) { return EIO; } talloc_steal(table, value.ptr); return EOK; } static errno_t sdap_nested_group_hash_entry(hash_table_t *table, struct sysdb_attrs *entry, const char *table_name) { const char *name = NULL; errno_t ret; ret = sysdb_attrs_get_string(entry, SYSDB_DN_FOR_MEMBER_HASH_TABLE, &name); if (ret != EOK) { ret = sysdb_attrs_get_string(entry, SYSDB_ORIG_DN, &name); if (ret != EOK) { return ret; } } return sdap_nested_group_hash_insert(table, name, entry, false, table_name); } static errno_t sdap_nested_group_hash_user(struct sdap_nested_group_ctx *group_ctx, struct sysdb_attrs *user) { return sdap_nested_group_hash_entry(group_ctx->users, user, "users"); } static errno_t sdap_nested_group_hash_group(struct sdap_nested_group_ctx *group_ctx, struct sysdb_attrs *group) { struct sdap_attr_map *map = group_ctx->opts->group_map; gid_t gid = 0; errno_t ret; bool posix_group = true; bool use_id_mapping; bool can_find_gid; bool need_filter; ret = sdap_check_ad_group_type(group_ctx->domain, group_ctx->opts, group, "", &need_filter); if (ret != EOK) { return ret; } if (need_filter) { posix_group = false; gid = 0; } use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( group_ctx->opts->idmap_ctx, group_ctx->domain->name, group_ctx->domain->domain_id); can_find_gid = posix_group && !use_id_mapping; if (can_find_gid) { ret = sysdb_attrs_get_uint32_t(group, map[SDAP_AT_GROUP_GID].sys_name, &gid); } if (!can_find_gid || ret == ENOENT || (ret == EOK && gid == 0)) { DEBUG(SSSDBG_TRACE_ALL, "The group's gid was %s\n", ret == ENOENT ? "missing" : "zero"); DEBUG(SSSDBG_TRACE_INTERNAL, "Marking group as non-POSIX and setting GID=0!\n"); if (ret == ENOENT || !posix_group) { ret = sysdb_attrs_add_uint32(group, map[SDAP_AT_GROUP_GID].sys_name, 0); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add a GID to non-POSIX group!\n"); return ret; } } ret = sysdb_attrs_add_bool(group, SYSDB_POSIX, false); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "Error: Failed to mark group as non-POSIX!\n"); return ret; } } else if (ret != EOK) { return ret; } return sdap_nested_group_hash_entry(group_ctx->groups, group, "groups"); } static errno_t sdap_nested_group_external_add(hash_table_t *table, const char *ext_member, const char *parent_group_dn) { hash_key_t key; hash_value_t value; int hret; int ret; struct sdap_external_missing_member *ext_mem; key.type = HASH_KEY_STRING; key.str = discard_const(ext_member); DEBUG(SSSDBG_TRACE_ALL, "Inserting external member [%s] into external members hash table\n", ext_member); hret = hash_lookup(table, &key, &value); switch (hret) { case HASH_ERROR_KEY_NOT_FOUND: ext_mem = talloc_zero(table, struct sdap_external_missing_member); if (ext_mem == NULL) { return ENOMEM; } ext_mem->parent_group_dns = talloc_zero_array(ext_mem, const char *, EXTERNAL_MEMBERS_CHUNK); if (ext_mem->parent_group_dns == NULL) { talloc_free(ext_mem); return ENOMEM; } ret = sdap_nested_group_hash_insert(table, ext_member, ext_mem, true, "missing external users"); if (ret != EOK) { return ret; } break; case HASH_SUCCESS: ext_mem = talloc_get_type(value.ptr, struct sdap_external_missing_member); if (ext_mem->parent_dn_idx == \ talloc_array_length(ext_mem->parent_group_dns)) { ext_mem->parent_group_dns = talloc_realloc(ext_mem, ext_mem->parent_group_dns, const char *, ext_mem->parent_dn_idx + \ EXTERNAL_MEMBERS_CHUNK); if (ext_mem->parent_group_dns == NULL) { talloc_free(ext_mem); return ENOMEM; } } break; default: return EIO; } ext_mem->parent_group_dns[ext_mem->parent_dn_idx] = \ talloc_strdup(ext_mem->parent_group_dns, parent_group_dn); if (ext_mem->parent_group_dns[ext_mem->parent_dn_idx] == NULL) { return ENOMEM; } ext_mem->parent_dn_idx++; return EOK; } static errno_t sdap_nested_group_sysdb_search(struct sss_domain_info *domain, const char *dn, bool user) { static const char *attrs[] = {SYSDB_CACHE_EXPIRE, SYSDB_UIDNUM, NULL}; struct ldb_message **msgs = NULL; size_t count; time_t now = time(NULL); uint64_t expire; uid_t uid; errno_t ret; if (user) { ret = sysdb_search_users_by_orig_dn(NULL, domain, dn, attrs, &count, &msgs); } else { ret = sysdb_search_groups_by_orig_dn(NULL, domain, dn, attrs, &count, &msgs); } if (ret != EOK) { goto done; } if (count != 1) { DEBUG(SSSDBG_OP_FAILURE, "More than one entry found?\n"); ret = EFAULT; goto done; } /* we found an object with this origDN in the sysdb, * check if it is valid */ if (user) { uid = ldb_msg_find_attr_as_uint64(msgs[0], SYSDB_UIDNUM, 0); if (uid == 0) { DEBUG(SSSDBG_OP_FAILURE, "User with no UID?\n"); ret = EINVAL; goto done; } } expire = ldb_msg_find_attr_as_uint64(msgs[0], SYSDB_CACHE_EXPIRE, 0); if (expire != 0 && expire <= now) { /* needs refresh */ ret = EAGAIN; goto done; } /* valid object */ ret = EOK; done: talloc_zfree(msgs); return ret; } static errno_t sdap_nested_group_check_cache(struct sdap_options *opts, struct sss_domain_info *domain, const char *member_dn, enum sdap_nested_group_dn_type *_type) { struct sdap_domain *sdap_domain = NULL; struct sss_domain_info *member_domain = NULL; errno_t ret; /* determine correct domain of this member */ sdap_domain = sdap_domain_get_by_dn(opts, member_dn); member_domain = sdap_domain == NULL ? domain : sdap_domain->dom; /* search in users */ PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_USERS_PRE); ret = sdap_nested_group_sysdb_search_users(member_domain, member_dn); PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_USERS_POST); if (ret == EOK || ret == EAGAIN) { /* user found */ *_type = SDAP_NESTED_GROUP_DN_USER; goto done; } else if (ret != ENOENT) { /* error */ goto done; } /* search in groups */ PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_GROUPS_PRE); ret = sdap_nested_group_sysdb_search_groups(member_domain, member_dn); PROBE(SDAP_NESTED_GROUP_SYSDB_SEARCH_GROUPS_POST); if (ret == EOK || ret == EAGAIN) { /* group found */ *_type = SDAP_NESTED_GROUP_DN_GROUP; goto done; } else if (ret != ENOENT) { /* error */ goto done; } /* not found in the sysdb */ ret = ENOENT; done: return ret; } static bool sdap_nested_member_is_ent(struct sdap_nested_group_ctx *group_ctx, const char *dn, char **filter, bool is_user) { struct sdap_domain *sditer = NULL; bool ret = false; struct sdap_search_base **search_bases; DLIST_FOR_EACH(sditer, group_ctx->opts->sdom) { search_bases = is_user ? sditer->user_search_bases : \ sditer->group_search_bases; ret = sss_ldap_dn_in_search_bases(group_ctx, dn, search_bases, filter); if (ret == true) { break; } } return ret; } static inline bool sdap_nested_member_is_user(struct sdap_nested_group_ctx *group_ctx, const char *dn, char **filter) { return sdap_nested_member_is_ent(group_ctx, dn, filter, true); } static inline bool sdap_nested_member_is_group(struct sdap_nested_group_ctx *group_ctx, const char *dn, char **filter) { return sdap_nested_member_is_ent(group_ctx, dn, filter, false); } static errno_t sdap_nested_group_split_members(TALLOC_CTX *mem_ctx, struct sdap_nested_group_ctx *group_ctx, int threshold, int nesting_level, struct ldb_message_element *members, struct sdap_nested_group_member **_missing, int *_num_missing, int *_num_groups) { TALLOC_CTX *tmp_ctx = NULL; struct sdap_nested_group_member *missing = NULL; enum sdap_nested_group_dn_type type; char *dn = NULL; char *user_filter = NULL; char *group_filter = NULL; int num_missing = 0; int num_groups = 0; hash_key_t key; bool bret; bool is_user; bool is_group; errno_t ret; int i; if (members == NULL) { *_missing = NULL; *_num_missing = 0; *_num_groups = 0; return EOK; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); return ENOMEM; } missing = talloc_zero_array(tmp_ctx, struct sdap_nested_group_member, members->num_values); if (missing == NULL) { ret = ENOMEM; goto done; } /* create list of missing members * skip dn if: * - is present in user or group hash table * - is present in sysdb and not expired * - it is a group and we have reached the maximal nesting level * - it is not under user nor group search bases * * if dn is in sysdb but expired * - we know what object type it is * * if dn is not in hash table or sysdb * - try to determine type of object by search base that match dn */ for (i = 0; i < members->num_values; i++) { dn = (char*)members->values[i].data; type = SDAP_NESTED_GROUP_DN_UNKNOWN; /* check hash tables */ key.type = HASH_KEY_STRING; key.str = dn; bret = hash_has_key(group_ctx->users, &key); if (bret) { continue; } bret = hash_has_key(group_ctx->groups, &key); if (bret) { continue; } /* check sysdb */ PROBE(SDAP_NESTED_GROUP_CHECK_CACHE_PRE); ret = sdap_nested_group_check_cache(group_ctx->opts, group_ctx->domain, dn, &type); PROBE(SDAP_NESTED_GROUP_CHECK_CACHE_POST); if (ret == EOK) { /* found and valid */ DEBUG(SSSDBG_TRACE_ALL, "[%s] found in cache, skipping\n", dn); continue; } else if (ret != EAGAIN && ret != ENOENT) { /* error */ goto done; } /* try to determine type by dn */ if (type == SDAP_NESTED_GROUP_DN_UNKNOWN) { /* user */ is_user = sdap_nested_member_is_user(group_ctx, dn, &user_filter); is_group = sdap_nested_member_is_group(group_ctx, dn, &group_filter); if (is_user && is_group) { /* search bases overlap */ DEBUG(SSSDBG_TRACE_ALL, "[%s] is unknown object\n", dn); type = SDAP_NESTED_GROUP_DN_UNKNOWN; } else if (is_user) { DEBUG(SSSDBG_TRACE_ALL, "[%s] is a user\n", dn); type = SDAP_NESTED_GROUP_DN_USER; } else if (is_group) { DEBUG(SSSDBG_TRACE_ALL, "[%s] is a group\n", dn); type = SDAP_NESTED_GROUP_DN_GROUP; } else { /* dn is outside search bases */ DEBUG(SSSDBG_TRACE_ALL, "[%s] is out of scope of configured " "search bases, skipping\n", dn); continue; } } /* check nesting level */ if (type == SDAP_NESTED_GROUP_DN_GROUP) { if (nesting_level >= group_ctx->max_nesting_level) { DEBUG(SSSDBG_TRACE_ALL, "[%s] is outside nesting limit " "(level %d), skipping\n", dn, nesting_level); talloc_zfree(user_filter); talloc_zfree(group_filter); continue; } } missing[num_missing].dn = talloc_strdup(missing, dn); if (missing[num_missing].dn == NULL) { ret = ENOMEM; goto done; } missing[num_missing].type = type; missing[num_missing].user_filter = talloc_steal(missing, user_filter); missing[num_missing].group_filter = talloc_steal(missing, group_filter); num_missing++; if (threshold > 0 && num_missing > threshold) { if (_num_missing) { *_num_missing = num_missing; } ret = ERR_DEREF_THRESHOLD; goto done; } if (type != SDAP_NESTED_GROUP_DN_USER) { num_groups++; } } missing = talloc_realloc(mem_ctx, missing, struct sdap_nested_group_member, num_missing); /* talloc_realloc behaves as talloc_free if 3rd parameter (count) is 0, * so it's OK to return NULL then */ if (missing == NULL && num_missing > 0) { ret = ENOMEM; goto done; } if (_missing) { *_missing = talloc_steal(mem_ctx, missing); } if (_num_missing) { *_num_missing = num_missing; } if (_num_groups) { *_num_groups = num_groups; } ret = EOK; done: talloc_free(tmp_ctx); return ret; } static errno_t sdap_nested_group_add_ext_members(struct sdap_nested_group_ctx *group_ctx, struct sysdb_attrs *group, struct ldb_message_element *ext_members) { errno_t ret; const char *ext_member_attr; const char *orig_dn; if (ext_members == NULL) { return EOK; } ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN, &orig_dn); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "A group with no originalDN!?!\n"); return ret; } for (size_t i = 0; i < ext_members->num_values; i++) { ext_member_attr = (const char *) ext_members->values[i].data; ret = sdap_nested_group_external_add(group_ctx->missing_external, ext_member_attr, orig_dn); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add %s into external members [%d]: %s\n", ext_member_attr, ret, sss_strerror(ret)); return ret; } } return EOK; } static struct ldb_message_element * sdap_nested_group_ext_members(struct sdap_options *opts, struct sysdb_attrs *group) { errno_t ret; struct ldb_message_element *ext_members = NULL; if (opts->ext_ctx == NULL) { return NULL; } ret = sysdb_attrs_get_el_ext(group, opts->group_map[SDAP_AT_GROUP_EXT_MEMBER].sys_name, false, &ext_members); if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve external member list " "[%d]: %s\n", ret, sss_strerror(ret)); } return ext_members; } struct sdap_nested_group_state { struct sdap_nested_group_ctx *group_ctx; }; static void sdap_nested_group_done(struct tevent_req *subreq); struct tevent_req * sdap_nested_group_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_domain *sdom, struct sdap_options *opts, struct sdap_handle *sh, struct sysdb_attrs *group) { struct sdap_nested_group_state *state = NULL; struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; errno_t ret; int i; PROBE(SDAP_NESTED_GROUP_SEND); req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } /* create main nested group context */ state->group_ctx = talloc_zero(state, struct sdap_nested_group_ctx); if (state->group_ctx == NULL) { ret = ENOMEM; goto immediately; } ret = sss_hash_create(state->group_ctx, 0, &state->group_ctx->users); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", ret, strerror(ret)); goto immediately; } ret = sss_hash_create(state->group_ctx, 0, &state->group_ctx->groups); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", ret, strerror(ret)); goto immediately; } ret = sss_hash_create(state->group_ctx, 0, &state->group_ctx->missing_external); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", ret, strerror(ret)); goto immediately; } state->group_ctx->try_deref = true; state->group_ctx->deref_threshold = dp_opt_get_int(opts->basic, SDAP_DEREF_THRESHOLD); state->group_ctx->max_nesting_level = dp_opt_get_int(opts->basic, SDAP_NESTING_LEVEL); state->group_ctx->domain = sdom->dom; state->group_ctx->opts = opts; state->group_ctx->user_search_bases = sdom->user_search_bases; state->group_ctx->group_search_bases = sdom->group_search_bases; state->group_ctx->ignore_user_search_bases = sdom->ignore_user_search_bases; state->group_ctx->sh = sh; state->group_ctx->try_deref = sdap_has_deref_support(sh, opts); /* disable deref if threshold <= 0 */ if (state->group_ctx->deref_threshold <= 0) { state->group_ctx->try_deref = false; } /* if any search base contains filter, disable dereference. */ if (state->group_ctx->try_deref) { for (i = 0; opts->sdom->user_search_bases[i] != NULL; i++) { if (opts->sdom->user_search_bases[i]->filter != NULL) { DEBUG(SSSDBG_TRACE_FUNC, "User search base contains filter, " "dereference will be disabled\n"); state->group_ctx->try_deref = false; break; } } } if (state->group_ctx->try_deref) { for (i = 0; opts->sdom->group_search_bases[i] != NULL; i++) { if (opts->sdom->group_search_bases[i]->filter != NULL) { DEBUG(SSSDBG_TRACE_FUNC, "Group search base contains filter, " "dereference will be disabled\n"); state->group_ctx->try_deref = false; break; } } } /* insert initial group into hash table */ ret = sdap_nested_group_hash_group(state->group_ctx, group); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to insert group into hash table " "[%d]: %s\n", ret, strerror(ret)); goto immediately; } /* resolve group */ subreq = sdap_nested_group_process_send(state, ev, state->group_ctx, 0, group); if (subreq == NULL) { ret = ENOMEM; goto immediately; } tevent_req_set_callback(subreq, sdap_nested_group_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_nested_group_done(struct tevent_req *subreq) { struct tevent_req *req = NULL; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); ret = sdap_nested_group_process_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } errno_t sdap_nested_group_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, unsigned long *_num_users, struct sysdb_attrs ***_users, unsigned long *_num_groups, struct sysdb_attrs ***_groups, hash_table_t **_missing_external) { struct sdap_nested_group_state *state = NULL; struct sysdb_attrs **users = NULL; struct sysdb_attrs **groups = NULL; unsigned long num_users; unsigned long num_groups; errno_t ret; state = tevent_req_data(req, struct sdap_nested_group_state); PROBE(SDAP_NESTED_GROUP_RECV); TEVENT_REQ_RETURN_ON_ERROR(req); ret = sdap_nested_group_extract_hash_table(state, state->group_ctx->users, &num_users, &users); if (ret != EOK) { return ret; } DEBUG(SSSDBG_TRACE_FUNC, "%lu users found in the hash table\n", num_users); ret = sdap_nested_group_extract_hash_table(state, state->group_ctx->groups, &num_groups, &groups); if (ret != EOK) { return ret; } DEBUG(SSSDBG_TRACE_FUNC, "%lu groups found in the hash table\n", num_groups); if (_num_users != NULL) { *_num_users = num_users; } if (_users != NULL) { *_users = talloc_steal(mem_ctx, users); } if (_num_groups!= NULL) { *_num_groups = num_groups; } if (_groups != NULL) { *_groups = talloc_steal(mem_ctx, groups); } if (_missing_external) { *_missing_external = talloc_steal(mem_ctx, state->group_ctx->missing_external); } return EOK; } struct sdap_nested_group_process_state { struct tevent_context *ev; struct sdap_nested_group_ctx *group_ctx; struct sdap_nested_group_member *missing; int num_missing_total; int num_missing_groups; struct ldb_message_element *ext_members; struct ldb_message_element *members; int nesting_level; char *group_dn; bool deref; bool deref_shortcut; }; static void sdap_nested_group_process_done(struct tevent_req *subreq); static struct tevent_req * sdap_nested_group_process_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, int nesting_level, struct sysdb_attrs *group) { struct sdap_nested_group_process_state *state = NULL; struct sdap_attr_map *group_map = NULL; struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; const char *orig_dn = NULL; errno_t ret; int split_threshold; req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_process_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->ev = ev; state->group_ctx = group_ctx; state->nesting_level = nesting_level; group_map = state->group_ctx->opts->group_map; /* get original dn */ ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN, &orig_dn); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve original dn " "[%d]: %s\n", ret, strerror(ret)); goto immediately; } state->group_dn = talloc_strdup(state, orig_dn); if (state->group_dn == NULL) { ret = ENOMEM; goto immediately; } DEBUG(SSSDBG_TRACE_INTERNAL, "About to process group [%s]\n", orig_dn); PROBE(SDAP_NESTED_GROUP_PROCESS_SEND, state->group_dn); /* get member list, both direct and external */ state->ext_members = sdap_nested_group_ext_members(state->group_ctx->opts, group); ret = sysdb_attrs_get_el_ext(group, group_map[SDAP_AT_GROUP_MEMBER].sys_name, false, &state->members); if (ret == ENOENT && state->ext_members == NULL) { ret = EOK; /* no members, direct or external */ goto immediately; } else if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve member list " "[%d]: %s\n", ret, strerror(ret)); goto immediately; } split_threshold = state->group_ctx->try_deref ? \ state->group_ctx->deref_threshold : \ -1; /* get members that need to be refreshed */ PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_PRE); ret = sdap_nested_group_split_members(state, state->group_ctx, split_threshold, state->nesting_level, state->members, &state->missing, &state->num_missing_total, &state->num_missing_groups); PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_POST); if (ret == ERR_DEREF_THRESHOLD) { DEBUG(SSSDBG_TRACE_FUNC, "More members were missing than the deref threshold\n"); state->deref_shortcut = true; } else if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split member list " "[%d]: %s\n", ret, sss_strerror(ret)); goto immediately; } ret = sdap_nested_group_add_ext_members(state->group_ctx, group, state->ext_members); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split external member list " "[%d]: %s\n", ret, sss_strerror(ret)); goto immediately; } if (state->num_missing_total == 0 && hash_count(state->group_ctx->missing_external) == 0) { ret = EOK; /* we're done */ goto immediately; } /* If there are only indirect members of the group, it's still safe to * proceed and let the direct lookup code just fall through. */ DEBUG(SSSDBG_TRACE_INTERNAL, "Looking up %d/%d members of group [%s]\n", state->num_missing_total, state->members ? state->members->num_values : 0, orig_dn); /* process members */ if (group_ctx->try_deref && state->num_missing_total > group_ctx->deref_threshold) { DEBUG(SSSDBG_TRACE_INTERNAL, "Dereferencing members of group [%s]\n", orig_dn); state->deref = true; subreq = sdap_nested_group_deref_send(state, ev, group_ctx, state->members, orig_dn, state->nesting_level); } else { DEBUG(SSSDBG_TRACE_INTERNAL, "Members of group [%s] will be " "processed individually\n", orig_dn); state->deref = false; subreq = sdap_nested_group_single_send(state, ev, group_ctx, state->missing, state->num_missing_total, state->num_missing_groups, state->nesting_level); } if (subreq == NULL) { ret = ENOMEM; goto immediately; } tevent_req_set_callback(subreq, sdap_nested_group_process_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_nested_group_process_done(struct tevent_req *subreq) { struct sdap_nested_group_process_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_nested_group_process_state); if (state->deref) { ret = sdap_nested_group_deref_recv(subreq); talloc_zfree(subreq); if (ret == ENOTSUP) { /* dereference is not supported, try again without dereference */ state->group_ctx->try_deref = false; state->deref = false; DEBUG(SSSDBG_TRACE_INTERNAL, "Members of group [%s] will be " "processed individually\n", state->group_dn); if (state->deref_shortcut == true) { /* If we previously short-cut dereference, we need to split the * members again to get full list of missing member types */ PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_PRE); ret = sdap_nested_group_split_members(state, state->group_ctx, -1, state->nesting_level, state->members, &state->missing, &state->num_missing_total, &state->num_missing_groups); PROBE(SDAP_NESTED_GROUP_PROCESS_SPLIT_POST); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split member list " "[%d]: %s\n", ret, sss_strerror(ret)); goto done; } } subreq = sdap_nested_group_single_send(state, state->ev, state->group_ctx, state->missing, state->num_missing_total, state->num_missing_groups, state->nesting_level); if (subreq == NULL) { ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sdap_nested_group_process_done, req); ret = EAGAIN; } } else { ret = sdap_nested_group_single_recv(subreq); talloc_zfree(subreq); } done: if (ret == EOK) { tevent_req_done(req); } else if (ret != EAGAIN) { tevent_req_error(req, ret); } } static errno_t sdap_nested_group_process_recv(struct tevent_req *req) { #ifdef HAVE_SYSTEMTAP struct sdap_nested_group_process_state *state = NULL; state = tevent_req_data(req, struct sdap_nested_group_process_state); PROBE(SDAP_NESTED_GROUP_PROCESS_RECV, state->group_dn); #endif TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct sdap_nested_group_recurse_state { struct tevent_context *ev; struct sdap_nested_group_ctx *group_ctx; struct sysdb_attrs **groups; int num_groups; int index; int nesting_level; }; static errno_t sdap_nested_group_recurse_step(struct tevent_req *req); static void sdap_nested_group_recurse_done(struct tevent_req *subreq); static struct tevent_req * sdap_nested_group_recurse_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct sysdb_attrs **nested_groups, int num_groups, int nesting_level) { struct sdap_nested_group_recurse_state *state = NULL; struct tevent_req *req = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_recurse_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->ev = ev; state->group_ctx = group_ctx; state->groups = nested_groups; state->num_groups = num_groups; state->index = 0; state->nesting_level = nesting_level; /* process each group individually */ ret = sdap_nested_group_recurse_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_nested_group_recurse_step(struct tevent_req *req) { struct sdap_nested_group_recurse_state *state = NULL; struct tevent_req *subreq = NULL; state = tevent_req_data(req, struct sdap_nested_group_recurse_state); if (state->index >= state->num_groups) { /* we're done */ return EOK; } subreq = sdap_nested_group_process_send(state, state->ev, state->group_ctx, state->nesting_level, state->groups[state->index]); if (subreq == NULL) { return ENOMEM; } tevent_req_set_callback(subreq, sdap_nested_group_recurse_done, req); state->index++; return EAGAIN; } static void sdap_nested_group_recurse_done(struct tevent_req *subreq) { struct tevent_req *req = NULL; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); ret = sdap_nested_group_process_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { goto done; } ret = sdap_nested_group_recurse_step(req); done: if (ret == EOK) { tevent_req_done(req); } else if (ret != EAGAIN) { tevent_req_error(req, ret); } return; } static errno_t sdap_nested_group_recurse_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct sdap_nested_group_single_state { struct tevent_context *ev; struct sdap_nested_group_ctx *group_ctx; struct sdap_nested_group_member *members; int nesting_level; struct sdap_nested_group_member *current_member; int num_members; int member_index; struct sysdb_attrs **nested_groups; int num_groups; bool ignore_unreadable_references; }; static errno_t sdap_nested_group_single_step(struct tevent_req *req); static void sdap_nested_group_single_step_done(struct tevent_req *subreq); static void sdap_nested_group_single_done(struct tevent_req *subreq); static struct tevent_req * sdap_nested_group_single_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct sdap_nested_group_member *members, int num_members, int num_groups_max, int nesting_level) { struct sdap_nested_group_single_state *state = NULL; struct tevent_req *req = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_single_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->ev = ev; state->group_ctx = group_ctx; state->members = members; state->nesting_level = nesting_level; state->current_member = NULL; state->num_members = num_members; state->member_index = 0; state->nested_groups = talloc_zero_array(state, struct sysdb_attrs *, num_groups_max); if (state->nested_groups == NULL) { ret = ENOMEM; goto immediately; } state->num_groups = 0; /* we will count exact number of the groups */ state->ignore_unreadable_references = dp_opt_get_bool( group_ctx->opts->basic, SDAP_IGNORE_UNREADABLE_REFERENCES); /* process each member individually */ ret = sdap_nested_group_single_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 must_ignore(struct sdap_search_base **ignore_user_search_bases, struct ldb_context *ldb_ctx, const char *dn_str, bool *_ignore) { bool ignore; struct ldb_dn *ldn; struct sdap_search_base **base; if (ldb_ctx == NULL || dn_str == NULL) { return EINVAL; } if (ignore_user_search_bases == NULL) { *_ignore = false; return EOK; } ldn = ldb_dn_new(NULL, ldb_ctx, dn_str); if (ldn == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to allocate memory for the DN\n"); return ENOMEM; } ignore = false; for (base = ignore_user_search_bases; *base != NULL; base++) { if ((*base)->ldb_basedn != NULL) { if (ldb_dn_compare_base((*base)->ldb_basedn, ldn) == 0) { ignore = true; DEBUG(SSSDBG_TRACE_INTERNAL, "Ignoring entry [%s]\n", dn_str); break; } } else { DEBUG(SSSDBG_TRACE_INTERNAL, "Not checking ignore user search base %s \n", (*base)->basedn); } } *_ignore = ignore; talloc_free(ldn); return EOK; } static errno_t sdap_nested_group_single_step(struct tevent_req *req) { struct sdap_nested_group_single_state *state = NULL; struct tevent_req *subreq = NULL; errno_t ret; bool ignore; state = tevent_req_data(req, struct sdap_nested_group_single_state); do { if (state->member_index >= state->num_members) { /* we're done */ return EOK; } state->current_member = &state->members[state->member_index]; state->member_index++; ret = must_ignore(state->group_ctx->ignore_user_search_bases, sysdb_ctx_get_ldb(state->group_ctx->domain->sysdb), state->current_member->dn, &ignore); if (ret != EOK) { return ret; } } while (ignore); switch (state->current_member->type) { case SDAP_NESTED_GROUP_DN_USER: subreq = sdap_nested_group_lookup_user_send(state, state->ev, state->group_ctx, state->current_member); break; case SDAP_NESTED_GROUP_DN_GROUP: subreq = sdap_nested_group_lookup_group_send(state, state->ev, state->group_ctx, state->current_member); break; case SDAP_NESTED_GROUP_DN_UNKNOWN: subreq = sdap_nested_group_lookup_unknown_send(state, state->ev, state->group_ctx, state->current_member); break; } if (subreq == NULL) { return ENOMEM; } tevent_req_set_callback(subreq, sdap_nested_group_single_step_done, req); return EAGAIN; } static errno_t sdap_nested_group_single_step_process(struct tevent_req *subreq) { struct sdap_nested_group_single_state *state = NULL; struct tevent_req *req = NULL; struct sysdb_attrs *entry = NULL; enum sdap_nested_group_dn_type type = SDAP_NESTED_GROUP_DN_UNKNOWN; const char *orig_dn = NULL; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_nested_group_single_state); /* set correct type if possible */ if (state->current_member->type == SDAP_NESTED_GROUP_DN_UNKNOWN) { ret = sdap_nested_group_lookup_unknown_recv(state, subreq, &entry, &type); if (ret != EOK) { goto done; } if (entry != NULL) { state->current_member->type = type; } } switch (state->current_member->type) { case SDAP_NESTED_GROUP_DN_USER: if (entry == NULL) { /* type was not unknown, receive data */ ret = sdap_nested_group_lookup_user_recv(state, subreq, &entry); if (ret != EOK) { goto done; } if (entry == NULL) { /* user not found, continue */ break; } } /* The original DN of the user object itself might differ from the one * used in the member attribute, e.g. different case. To make sure if * can be found in a hash table when iterating over group members the * DN from the member attribute used for the search as saved as well. */ ret = sysdb_attrs_add_string(entry, SYSDB_DN_FOR_MEMBER_HASH_TABLE, state->current_member->dn); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); goto done; } /* save user in hash table */ ret = sdap_nested_group_hash_user(state->group_ctx, entry); if (ret == EEXIST) { /* the user is already present, skip it */ talloc_zfree(entry); ret = EOK; goto done; } else if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save user in hash table " "[%d]: %s\n", ret, strerror(ret)); goto done; } break; case SDAP_NESTED_GROUP_DN_GROUP: if (entry == NULL) { /* type was not unknown, receive data */ ret = sdap_nested_group_lookup_group_recv(state, subreq, &entry); if (ret != EOK) { goto done; } if (entry == NULL) { /* group not found, continue */ break; } } else { /* the type was unknown so we had to pull the group, * but we don't want to process it if we have reached * the nesting level */ if (state->nesting_level >= state->group_ctx->max_nesting_level) { ret = sysdb_attrs_get_string(entry, SYSDB_ORIG_DN, &orig_dn); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "The entry has no originalDN\n"); orig_dn = "invalid"; } DEBUG(SSSDBG_TRACE_ALL, "[%s] is outside nesting limit " "(level %d), skipping\n", orig_dn, state->nesting_level); break; } } /* save group in hash table */ ret = sdap_nested_group_hash_group(state->group_ctx, entry); if (ret == EEXIST) { /* the group is already present, skip it */ talloc_zfree(entry); ret = EOK; goto done; } else if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save group in hash table " "[%d]: %s\n", ret, strerror(ret)); goto done; } /* remember the group for later processing */ state->nested_groups[state->num_groups] = entry; state->num_groups++; break; case SDAP_NESTED_GROUP_DN_UNKNOWN: if (state->ignore_unreadable_references) { DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference [%s]\n", state->current_member->dn); } else { DEBUG(SSSDBG_OP_FAILURE, "Unknown entry type [%s]!\n", state->current_member->dn); ret = EINVAL; goto done; } break; } ret = EOK; done: return ret; } static void sdap_nested_group_single_step_done(struct tevent_req *subreq) { struct sdap_nested_group_single_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_nested_group_single_state); /* process direct members */ ret = sdap_nested_group_single_step_process(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Error processing direct membership " "[%d]: %s\n", ret, strerror(ret)); goto done; } ret = sdap_nested_group_single_step(req); if (ret == EOK) { /* we have processed all direct members, * now recurse and process nested groups */ subreq = sdap_nested_group_recurse_send(state, state->ev, state->group_ctx, state->nested_groups, state->num_groups, state->nesting_level + 1); if (subreq == NULL) { ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sdap_nested_group_single_done, req); } else if (ret != EAGAIN) { /* error */ goto done; } /* we're not done yet */ ret = EAGAIN; done: if (ret == EOK) { /* tevent_req_error() cannot cope with EOK */ DEBUG(SSSDBG_CRIT_FAILURE, "We should not get here with EOK\n"); tevent_req_error(req, EINVAL); } else if (ret != EAGAIN) { tevent_req_error(req, ret); } return; } static void sdap_nested_group_single_done(struct tevent_req *subreq) { struct tevent_req *req = NULL; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); /* all nested groups are completed */ ret = sdap_nested_group_recurse_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Error processing nested groups " "[%d]: %s.\n", ret, strerror(ret)); tevent_req_error(req, ret); return; } tevent_req_done(req); return; } static errno_t sdap_nested_group_single_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } static errno_t sdap_nested_group_get_ipa_user(TALLOC_CTX *mem_ctx, const char *user_dn, struct sysdb_ctx *sysdb, struct sysdb_attrs **_user) { TALLOC_CTX *tmp_ctx; struct sysdb_attrs *user; char *name; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } ret = ipa_get_rdn(tmp_ctx, sysdb, user_dn, &name, "uid", "cn", "users", "cn", "accounts"); if (ret != EOK) { goto done; } user = sysdb_new_attrs(tmp_ctx); if (user == NULL) { ret = ENOMEM; goto done; } ret = sysdb_attrs_add_string(user, SYSDB_NAME, name); if (ret != EOK) { goto done; } ret = sysdb_attrs_add_string(user, SYSDB_ORIG_DN, user_dn); if (ret != EOK) { goto done; } ret = sysdb_attrs_add_string(user, SYSDB_OBJECTCATEGORY, SYSDB_USER_CLASS); if (ret != EOK) { goto done; } *_user = talloc_steal(mem_ctx, user); done: talloc_free(tmp_ctx); return ret; } struct sdap_nested_group_lookup_user_state { struct sysdb_attrs *user; }; static void sdap_nested_group_lookup_user_done(struct tevent_req *subreq); static struct tevent_req * sdap_nested_group_lookup_user_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct sdap_nested_group_member *member) { struct sdap_nested_group_lookup_user_state *state = NULL; struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; const char **attrs = NULL; const char *base_filter = NULL; const char *filter = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_lookup_user_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } PROBE(SDAP_NESTED_GROUP_LOOKUP_USER_SEND); if (group_ctx->opts->schema_type == SDAP_SCHEMA_IPA_V1) { /* if the schema is IPA, then just shortcut and guess the name */ ret = sdap_nested_group_get_ipa_user(state, member->dn, group_ctx->domain->sysdb, &state->user); if (ret == EOK) { goto immediately; } DEBUG(SSSDBG_MINOR_FAILURE, "Couldn't parse out user information " "based on DN %s, falling back to an LDAP lookup\n", member->dn); } /* only pull down username and originalDN */ attrs = talloc_array(state, const char *, 3); if (attrs == NULL) { ret = ENOMEM; goto immediately; } attrs[0] = "objectClass"; attrs[1] = group_ctx->opts->user_map[SDAP_AT_USER_NAME].name; attrs[2] = NULL; /* create filter */ base_filter = talloc_asprintf(state, "(objectclass=%s)", group_ctx->opts->user_map[SDAP_OC_USER].name); if (base_filter == NULL) { ret = ENOMEM; goto immediately; } /* use search base filter if needed */ filter = sdap_combine_filters(state, base_filter, member->user_filter); if (filter == NULL) { ret = ENOMEM; goto immediately; } /* search */ subreq = sdap_get_generic_send(state, ev, group_ctx->opts, group_ctx->sh, member->dn, LDAP_SCOPE_BASE, filter, attrs, group_ctx->opts->user_map, group_ctx->opts->user_map_cnt, dp_opt_get_int(group_ctx->opts->basic, SDAP_SEARCH_TIMEOUT), false); if (subreq == NULL) { ret = ENOMEM; goto immediately; } tevent_req_set_callback(subreq, sdap_nested_group_lookup_user_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_nested_group_lookup_user_done(struct tevent_req *subreq) { struct sdap_nested_group_lookup_user_state *state = NULL; struct tevent_req *req = NULL; struct sysdb_attrs **user = NULL; size_t count = 0; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_nested_group_lookup_user_state); ret = sdap_get_generic_recv(subreq, state, &count, &user); talloc_zfree(subreq); if (ret == ENOENT) { count = 0; } else if (ret != EOK) { goto done; } if (count == 1) { state->user = user[0]; } else if (count == 0) { /* group not found */ state->user = NULL; } else { DEBUG(SSSDBG_OP_FAILURE, "BASE search returned more than one records\n"); ret = EIO; goto done; } ret = EOK; done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static errno_t sdap_nested_group_lookup_user_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct sysdb_attrs **_user) { struct sdap_nested_group_lookup_user_state *state = NULL; state = tevent_req_data(req, struct sdap_nested_group_lookup_user_state); PROBE(SDAP_NESTED_GROUP_LOOKUP_USER_RECV); TEVENT_REQ_RETURN_ON_ERROR(req); if (_user != NULL) { *_user = talloc_steal(mem_ctx, state->user); } return EOK; } struct sdap_nested_group_lookup_group_state { struct sysdb_attrs *group; }; static void sdap_nested_group_lookup_group_done(struct tevent_req *subreq); static struct tevent_req * sdap_nested_group_lookup_group_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct sdap_nested_group_member *member) { struct sdap_nested_group_lookup_group_state *state = NULL; struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_attr_map *map = group_ctx->opts->group_map; const char **attrs = NULL; const char *base_filter = NULL; const char *filter = NULL; char *oc_list; errno_t ret; PROBE(SDAP_NESTED_GROUP_LOOKUP_GROUP_SEND); req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_lookup_group_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } ret = build_attrs_from_map(state, group_ctx->opts->group_map, SDAP_OPTS_GROUP, NULL, &attrs, NULL); if (ret != EOK) { goto immediately; } /* create filter */ oc_list = sdap_make_oc_list(state, map); if (oc_list == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create objectClass list.\n"); ret = ENOMEM; goto immediately; } base_filter = talloc_asprintf(attrs, "(&(%s)(%s=*))", oc_list, map[SDAP_AT_GROUP_NAME].name); if (base_filter == NULL) { ret = ENOMEM; goto immediately; } /* use search base filter if needed */ filter = sdap_combine_filters(state, base_filter, member->group_filter); if (filter == NULL) { ret = ENOMEM; goto immediately; } /* search */ subreq = sdap_get_generic_send(state, ev, group_ctx->opts, group_ctx->sh, member->dn, LDAP_SCOPE_BASE, filter, attrs, map, SDAP_OPTS_GROUP, dp_opt_get_int(group_ctx->opts->basic, SDAP_SEARCH_TIMEOUT), false); if (subreq == NULL) { ret = ENOMEM; goto immediately; } tevent_req_set_callback(subreq, sdap_nested_group_lookup_group_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_nested_group_lookup_group_done(struct tevent_req *subreq) { struct sdap_nested_group_lookup_group_state *state = NULL; struct tevent_req *req = NULL; struct sysdb_attrs **group = NULL; size_t count = 0; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_nested_group_lookup_group_state); ret = sdap_get_generic_recv(subreq, state, &count, &group); talloc_zfree(subreq); if (ret == ENOENT) { count = 0; } else if (ret != EOK) { goto done; } if (count == 1) { state->group = group[0]; } else if (count == 0) { /* group not found */ state->group = NULL; } else { DEBUG(SSSDBG_OP_FAILURE, "BASE search returned more than one records\n"); ret = EIO; goto done; } ret = EOK; done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static errno_t sdap_nested_group_lookup_group_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct sysdb_attrs **_group) { struct sdap_nested_group_lookup_group_state *state = NULL; state = tevent_req_data(req, struct sdap_nested_group_lookup_group_state); PROBE(SDAP_NESTED_GROUP_LOOKUP_GROUP_RECV); TEVENT_REQ_RETURN_ON_ERROR(req); if (_group != NULL) { *_group = talloc_steal(mem_ctx, state->group); } return EOK; } struct sdap_nested_group_lookup_unknown_state { struct tevent_context *ev; struct sdap_nested_group_ctx *group_ctx; struct sdap_nested_group_member *member; enum sdap_nested_group_dn_type type; struct sysdb_attrs *entry; }; static void sdap_nested_group_lookup_unknown_user_done(struct tevent_req *subreq); static void sdap_nested_group_lookup_unknown_group_done(struct tevent_req *subreq); static struct tevent_req * sdap_nested_group_lookup_unknown_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct sdap_nested_group_member *member) { struct sdap_nested_group_lookup_unknown_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_nested_group_lookup_unknown_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } PROBE(SDAP_NESTED_GROUP_LOOKUP_UNKNOWN_SEND); state->ev = ev; state->group_ctx = group_ctx; state->member = member; /* try users first */ subreq = sdap_nested_group_lookup_user_send(state, state->ev, state->group_ctx, state->member); if (subreq == NULL) { ret = ENOMEM; tevent_req_error(req, ret); tevent_req_post(req, ev); } else { tevent_req_set_callback(subreq, sdap_nested_group_lookup_unknown_user_done, req); } return req; } static void sdap_nested_group_lookup_unknown_user_done(struct tevent_req *subreq) { struct sdap_nested_group_lookup_unknown_state *state = NULL; struct tevent_req *req = NULL; struct sysdb_attrs *entry = NULL; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_nested_group_lookup_unknown_state); ret = sdap_nested_group_lookup_user_recv(state, subreq, &entry); talloc_zfree(subreq); if (ret != EOK) { goto done; } if (entry != NULL) { /* found in users */ state->entry = entry; state->type = SDAP_NESTED_GROUP_DN_USER; ret = EOK; goto done; } /* not found in users, try group */ subreq = sdap_nested_group_lookup_group_send(state, state->ev, state->group_ctx, state->member); if (subreq == NULL) { ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sdap_nested_group_lookup_unknown_group_done, req); ret = EAGAIN; done: if (ret == EOK) { tevent_req_done(req); } else if (ret != EAGAIN) { tevent_req_error(req, ret); } return; } static void sdap_nested_group_lookup_unknown_group_done(struct tevent_req *subreq) { struct sdap_nested_group_lookup_unknown_state *state = NULL; struct tevent_req *req = NULL; struct sysdb_attrs *entry = NULL; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_nested_group_lookup_unknown_state); ret = sdap_nested_group_lookup_group_recv(state, subreq, &entry); talloc_zfree(subreq); if (ret != EOK) { goto done; } if (entry == NULL) { /* not found, end request */ state->entry = NULL; state->type = SDAP_NESTED_GROUP_DN_UNKNOWN; } else { /* found in groups */ state->entry = entry; state->type = SDAP_NESTED_GROUP_DN_GROUP; } ret = EOK; done: if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static errno_t sdap_nested_group_lookup_unknown_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct sysdb_attrs **_entry, enum sdap_nested_group_dn_type *_type) { struct sdap_nested_group_lookup_unknown_state *state = NULL; state = tevent_req_data(req, struct sdap_nested_group_lookup_unknown_state); PROBE(SDAP_NESTED_GROUP_LOOKUP_UNKNOWN_RECV); TEVENT_REQ_RETURN_ON_ERROR(req); if (_entry != NULL) { *_entry = talloc_steal(mem_ctx, state->entry); } if (_type != NULL) { *_type = state->type; } return EOK; } struct sdap_nested_group_deref_state { struct tevent_context *ev; struct sdap_nested_group_ctx *group_ctx; struct ldb_message_element *members; int nesting_level; struct sysdb_attrs **nested_groups; int num_groups; }; static void sdap_nested_group_deref_direct_done(struct tevent_req *subreq); static void sdap_nested_group_deref_done(struct tevent_req *subreq); static struct tevent_req * sdap_nested_group_deref_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_nested_group_ctx *group_ctx, struct ldb_message_element *members, const char *group_dn, int nesting_level) { struct sdap_nested_group_deref_state *state = NULL; struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_attr_map_info *maps = NULL; static const int num_maps = 2; struct sdap_options *opts = group_ctx->opts; const char **attrs = NULL; size_t num_attrs = 0; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_deref_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } PROBE(SDAP_NESTED_GROUP_DEREF_SEND); state->ev = ev; state->group_ctx = group_ctx; state->members = members; state->nesting_level = nesting_level; state->num_groups = 0; /* we will count exact number of the groups */ maps = talloc_array(state, struct sdap_attr_map_info, num_maps); if (maps == NULL) { ret = ENOMEM; goto immediately; } maps[0].map = opts->user_map; maps[0].num_attrs = opts->user_map_cnt; maps[1].map = opts->group_map; maps[1].num_attrs = SDAP_OPTS_GROUP; /* pull down the whole group map, * but only pull down username and originalDN for users */ ret = build_attrs_from_map(state, opts->group_map, SDAP_OPTS_GROUP, NULL, &attrs, &num_attrs); if (ret != EOK) { goto immediately; } attrs = talloc_realloc(state, attrs, const char *, num_attrs + 2); if (attrs == NULL) { ret = ENOMEM; goto immediately; } attrs[num_attrs] = group_ctx->opts->user_map[SDAP_AT_USER_NAME].name; attrs[num_attrs + 1] = NULL; /* send request */ subreq = sdap_deref_search_send(state, ev, opts, group_ctx->sh, group_dn, opts->group_map[SDAP_AT_GROUP_MEMBER].name, attrs, num_maps, maps, dp_opt_get_int(opts->basic, SDAP_SEARCH_TIMEOUT)); if (subreq == NULL) { ret = ENOMEM; goto immediately; } tevent_req_set_callback(subreq, sdap_nested_group_deref_direct_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 hash_table_t * convert_ldb_element_to_set(const struct ldb_message_element *members) { errno_t ret; hash_table_t *set = NULL; hash_key_t key; hash_value_t value; size_t j; ret = sss_hash_create(NULL, members->num_values, &set); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", ret, strerror(ret)); return NULL; } key.type = HASH_KEY_CONST_STRING; value.type = HASH_VALUE_UNDEF; for (j = 0; j < members->num_values; ++j) { key.c_str = (const char*)members->values[j].data; /* since hash table is used as a set, we don't care about value */ ret = hash_enter(set, &key, &value); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add '%s'\n", key.c_str); hash_destroy(set); return NULL; } } return set; } static bool set_has_key(hash_table_t *set, const char *key) { hash_key_t hkey; hkey.type = HASH_KEY_CONST_STRING; hkey.c_str = key; return hash_has_key(set, &hkey); } static errno_t sdap_nested_group_deref_direct_process(struct tevent_req *subreq) { struct sdap_nested_group_deref_state *state = NULL; struct tevent_req *req = NULL; struct sdap_options *opts = NULL; struct sdap_deref_attrs **entries = NULL; struct ldb_message_element *members = NULL; hash_table_t *members_set = NULL; /* will be used as a `set` */ const char *orig_dn = NULL; size_t num_entries = 0; size_t i; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_nested_group_deref_state); opts = state->group_ctx->opts; members = state->members; members_set = convert_ldb_element_to_set(state->members); if (members_set == NULL) { ret = ENOMEM; goto done; } ret = sdap_deref_search_recv(subreq, state, &num_entries, &entries); if (ret != EOK) { goto done; } DEBUG(SSSDBG_TRACE_INTERNAL, "Received %zu dereference results, " "about to process them\n", num_entries); /* * We don't have any knowledge about possible number of groups when * dereferencing. We expect that every member is a group and we will * allocate enough space to hold it. We will shrink the memory later. */ state->nested_groups = talloc_zero_array(state, struct sysdb_attrs *, num_entries); if (state->nested_groups == NULL) { ret = ENOMEM; goto done; } PROBE(SDAP_NESTED_GROUP_DEREF_PROCESS_PRE); for (i = 0; i < num_entries; i++) { ret = sysdb_attrs_get_string(entries[i]->attrs, SYSDB_ORIG_DN, &orig_dn); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "The entry has no originalDN\n"); goto done; } /* Ensure that all members returned from the deref request are included * in the member processing. Sometimes we will get more results back * from deref/asq than we got from the initial lookup, as is the case * with Active Directory and its range retrieval mechanism. */ if (!set_has_key(members_set, orig_dn)) { /* Append newly found member to member list. * Changes in state->members will propagate into sysdb_attrs of * the group. */ state->members->values = talloc_realloc(members, members->values, struct ldb_val, members->num_values + 1); if (members->values == NULL) { ret = ENOMEM; goto done; } members->values[members->num_values].data = (uint8_t *)talloc_strdup(members->values, orig_dn); if (members->values[members->num_values].data == NULL) { ret = ENOMEM; goto done; } members->values[members->num_values].length = strlen(orig_dn); members->num_values++; } if (entries[i]->map == opts->user_map) { /* we found a user */ /* skip the user if it is not amongst configured search bases */ if (!sdap_nested_member_is_user(state->group_ctx, orig_dn, NULL)) { continue; } /* save user in hash table */ ret = sdap_nested_group_hash_user(state->group_ctx, entries[i]->attrs); if (ret != EOK && ret != EEXIST) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save user in hash table " "[%d]: %s\n", ret, strerror(ret)); goto done; } } else if (entries[i]->map == opts->group_map) { /* we found a group */ /* skip the group if we have reached the nesting limit */ if (state->nesting_level >= state->group_ctx->max_nesting_level) { DEBUG(SSSDBG_TRACE_ALL, "[%s] is outside nesting limit " "(level %d), skipping\n", orig_dn, state->nesting_level); continue; } /* skip the group if it is not amongst configured search bases */ if (!sdap_nested_member_is_group(state->group_ctx, orig_dn, NULL)) { continue; } /* save group in hash table */ ret = sdap_nested_group_hash_group(state->group_ctx, entries[i]->attrs); if (ret == EEXIST) { continue; } else if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save group in hash table " "[%d]: %s\n", ret, strerror(ret)); goto done; } /* remember the group for later processing */ state->nested_groups[state->num_groups] = entries[i]->attrs; state->num_groups++; } else { /* this should never happen, but if it does, do not loop forever */ DEBUG(SSSDBG_MINOR_FAILURE, "Entry does not match any known map, skipping\n"); continue; } } PROBE(SDAP_NESTED_GROUP_DEREF_PROCESS_POST); /* adjust size of nested groups array */ if (state->num_groups > 0) { state->nested_groups = talloc_realloc(state, state->nested_groups, struct sysdb_attrs *, state->num_groups); if (state->nested_groups == NULL) { ret = ENOMEM; goto done; } } else { talloc_zfree(state->nested_groups); } ret = EOK; done: if (members_set != NULL) { hash_destroy(members_set); } return ret; } static void sdap_nested_group_deref_direct_done(struct tevent_req *subreq) { struct sdap_nested_group_deref_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_nested_group_deref_state); /* process direct members */ ret = sdap_nested_group_deref_direct_process(subreq); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Error processing direct membership " "[%d]: %s\n", ret, strerror(ret)); goto done; } /* we have processed all direct members, * now recurse and process nested groups */ subreq = sdap_nested_group_recurse_send(state, state->ev, state->group_ctx, state->nested_groups, state->num_groups, state->nesting_level + 1); if (subreq == NULL) { ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sdap_nested_group_deref_done, req); ret = EAGAIN; done: if (ret == EOK) { /* tevent_req_error() cannot cope with EOK */ DEBUG(SSSDBG_CRIT_FAILURE, "We should not get here with EOK\n"); tevent_req_error(req, EINVAL); } else if (ret != EAGAIN) { tevent_req_error(req, ret); } return; } static void sdap_nested_group_deref_done(struct tevent_req *subreq) { struct tevent_req *req = NULL; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); /* process nested groups */ ret = sdap_nested_group_recurse_recv(subreq); talloc_zfree(subreq); if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } return; } static errno_t sdap_nested_group_deref_recv(struct tevent_req *req) { PROBE(SDAP_NESTED_GROUP_DEREF_RECV); TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct sdap_ext_member { struct sdap_external_missing_member *missing_mem; const char *ext_member_attr; enum sysdb_member_type member_type; struct sss_domain_info *dom; struct sysdb_attrs *attrs; }; struct sdap_nested_group_lookup_external_state { struct tevent_context *ev; struct sdap_ext_member_ctx *ext_ctx; struct sss_domain_info *group_dom; hash_table_t *missing_external; hash_entry_t *entries; unsigned long n_entries; unsigned long eniter; struct sdap_ext_member *ext_members; ext_member_send_fn_t ext_member_resolve_send; ext_member_recv_fn_t ext_member_resolve_recv; }; static errno_t sdap_nested_group_lookup_external_step(struct tevent_req *req); static void sdap_nested_group_lookup_external_done(struct tevent_req *subreq); static errno_t sdap_nested_group_lookup_external_link(struct tevent_req *req); static errno_t sdap_nested_group_lookup_external_link_member( struct sdap_nested_group_lookup_external_state *state, struct sdap_ext_member *member); static errno_t sdap_nested_group_memberof_dn_by_original_dn( TALLOC_CTX *mem_ctx, struct sss_domain_info *group_dom, const char *original_dn, const char ***_parents); struct tevent_req * sdap_nested_group_lookup_external_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sss_domain_info *group_dom, struct sdap_ext_member_ctx *ext_ctx, hash_table_t *missing_external) { struct sdap_nested_group_lookup_external_state *state = NULL; struct tevent_req *req = NULL; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_lookup_external_state); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); return NULL; } state->ev = ev; state->group_dom = group_dom; state->ext_ctx = ext_ctx; state->missing_external = missing_external; if (state->ext_ctx->ext_member_resolve_send == NULL || state->ext_ctx->ext_member_resolve_recv == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Wrong private context\n"); ret = EINVAL; goto immediately; } ret = hash_entries(state->missing_external, &state->n_entries, &state->entries); if (ret != HASH_SUCCESS) { DEBUG(SSSDBG_CRIT_FAILURE, "hash_entries returned %d\n", ret); ret = EIO; goto immediately; } state->eniter = 0; state->ext_members = talloc_zero_array(state, struct sdap_ext_member, state->n_entries); if (state->ext_members == NULL) { ret = ENOMEM; goto immediately; } ret = sdap_nested_group_lookup_external_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_nested_group_lookup_external_step(struct tevent_req *req) { struct tevent_req *subreq = NULL; struct sdap_nested_group_lookup_external_state *state = NULL; state = tevent_req_data(req, struct sdap_nested_group_lookup_external_state); subreq = state->ext_ctx->ext_member_resolve_send(state, state->ev, state->entries[state->eniter].key.str, state->ext_ctx->pvt); if (subreq == NULL) { return ENOMEM; } DEBUG(SSSDBG_TRACE_FUNC, "Refreshing member %lu/%lu\n", state->eniter, state->n_entries); tevent_req_set_callback(subreq, sdap_nested_group_lookup_external_done, req); return EAGAIN; } static void sdap_nested_group_lookup_external_done(struct tevent_req *subreq) { errno_t ret; struct tevent_req *req = NULL; struct sdap_nested_group_lookup_external_state *state = NULL; enum sysdb_member_type member_type; struct sysdb_attrs *member; struct sss_domain_info *member_dom; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_nested_group_lookup_external_state); ret = state->ext_ctx->ext_member_resolve_recv(state, subreq, &member_type, &member_dom, &member); talloc_free(subreq); if (ret == EOK) { DEBUG(SSSDBG_TRACE_FUNC, "Refreshed member %lu\n", state->eniter); state->ext_members[state->eniter].missing_mem = \ state->entries[state->eniter].value.ptr; state->ext_members[state->eniter].dom = member_dom; state->ext_members[state->eniter].ext_member_attr = \ talloc_steal(state->ext_members, state->entries[state->eniter].key.str); state->ext_members[state->eniter].member_type = member_type; state->ext_members[state->eniter].attrs = \ talloc_steal(state->ext_members, member); } state->eniter++; if (state->eniter >= state->n_entries) { DEBUG(SSSDBG_TRACE_FUNC, "All external members processed\n"); ret = sdap_nested_group_lookup_external_link(req); if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); return; } ret = sdap_nested_group_lookup_external_step(req); if (ret != EOK && ret != EAGAIN) { tevent_req_error(req, ret); return; } return; } static errno_t sdap_nested_group_lookup_external_link(struct tevent_req *req) { errno_t ret, tret; bool in_transaction = false; struct sdap_nested_group_lookup_external_state *state = NULL; state = tevent_req_data(req, struct sdap_nested_group_lookup_external_state); ret = sysdb_transaction_start(state->group_dom->sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); goto fail; } in_transaction = true; for (size_t i = 0; i < state->eniter; i++) { if (state->ext_members[i].attrs == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "The member %s could not be resolved\n", state->ext_members[i].ext_member_attr); continue; } ret = sdap_nested_group_lookup_external_link_member(state, &state->ext_members[i]); if (ret != EOK) { goto fail; } } ret = sysdb_transaction_commit(state->group_dom->sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); goto fail; } in_transaction = false; return EOK; fail: if (in_transaction) { tret = sysdb_transaction_cancel(state->group_dom->sysdb); if (tret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); } } return EFAULT; } static errno_t sdap_nested_group_lookup_external_link_member( struct sdap_nested_group_lookup_external_state *state, struct sdap_ext_member *member) { const char *name; int ret; const char **parents = NULL; size_t i; TALLOC_CTX *tmp_ctx; const char *orig_dn; tmp_ctx = talloc_new(state); if (tmp_ctx == NULL) { return ENOMEM; } ret = sysdb_attrs_get_string(member->attrs, SYSDB_NAME, &name); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "No name for a user\n"); goto done; } /* This only works because the groups were saved in a previous * transaction */ for (i=0; i < member->missing_mem->parent_dn_idx; i++) { orig_dn = member->missing_mem->parent_group_dns[i]; DEBUG(SSSDBG_TRACE_INTERNAL, "Linking external members %s from domain %s to parents of %s\n", name, member->dom->name, orig_dn); ret = sdap_nested_group_memberof_dn_by_original_dn(tmp_ctx, state->group_dom, orig_dn, &parents); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find parents of %s\n", orig_dn); continue; } /* We don't have to remove the members here, since all members attributes * are always written anew */ ret = sysdb_update_members_dn(member->dom, name, member->member_type, parents, NULL); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Cannot link %s@%s to its parents\n", name, member->dom->name); goto done; } } ret = EOK; done: talloc_free(tmp_ctx); return ret; } static errno_t sdap_nested_group_memberof_dn_by_original_dn( TALLOC_CTX *mem_ctx, struct sss_domain_info *group_dom, const char *original_dn, const char ***_parents) { errno_t ret; const char *attrs[] = { SYSDB_NAME, SYSDB_MEMBEROF, NULL }; struct ldb_message **msgs = NULL; size_t count; TALLOC_CTX *tmp_ctx; struct ldb_message_element *memberof; const char **parents; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ENOMEM; } ret = sysdb_search_groups_by_orig_dn(tmp_ctx, group_dom, original_dn, attrs, &count, &msgs); if (ret != EOK) { goto done; } if (count != 1) { DEBUG(SSSDBG_OP_FAILURE, "More than one entry found by originalDN?\n"); goto done; } memberof = ldb_msg_find_element(msgs[0], SYSDB_MEMBEROF); if (memberof == NULL || memberof->num_values == 0) { DEBUG(SSSDBG_MINOR_FAILURE, "The external group is not a member of any groups\n"); ret = ENOENT; goto done; } parents = talloc_zero_array(tmp_ctx, const char *, memberof->num_values + 1); if (parents == NULL) { ret = ENOMEM; goto done; } for (size_t i = 0; i < memberof->num_values; i++) { parents[i] = talloc_strdup(parents, (const char *) memberof->values[i].data); if (parents[i] == NULL) { ret = ENOMEM; goto done; } } *_parents = talloc_steal(mem_ctx, parents); ret = EOK; done: talloc_free(tmp_ctx); return ret; } errno_t sdap_nested_group_lookup_external_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; }