diff options
-rw-r--r-- | src/providers/ldap/sdap_async_nested_groups.c | 2229 |
1 files changed, 2229 insertions, 0 deletions
diff --git a/src/providers/ldap/sdap_async_nested_groups.c b/src/providers/ldap/sdap_async_nested_groups.c new file mode 100644 index 00000000..6b9a440c --- /dev/null +++ b/src/providers/ldap/sdap_async_nested_groups.c @@ -0,0 +1,2229 @@ +/* + SSSD + + Authors: + Pavel B??ezina <pbrezina@redhat.com> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <errno.h> +#include <string.h> +#include <tevent.h> +#include <talloc.h> +#include <ldb.h> +#include <dhash.h> +#include <stdint.h> +#include <time.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" + +#define sdap_nested_group_sysdb_search_users(domain, filter) \ + sdap_nested_group_sysdb_search((domain), (filter), true) + +#define sdap_nested_group_sysdb_search_groups(domain, filter) \ + sdap_nested_group_sysdb_search((domain), (filter), 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; +}; + +struct sdap_nested_group_ctx { + struct sss_domain_info *domain; + struct sdap_options *opts; + struct sdap_handle *sh; + hash_table_t *users; + hash_table_t *groups; + bool try_deref; + int deref_treshold; + 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 num_groups_max, + 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_entry(hash_table_t *table, + struct sysdb_attrs *entry, + const char *table_name) +{ + hash_key_t key; + hash_value_t value; + const char *name = NULL; + errno_t ret; + int hret; + + ret = sysdb_attrs_get_string(entry, SYSDB_ORIG_DN, &name); + if (ret != EOK) { + return ret; + } + + DEBUG(SSSDBG_TRACE_ALL, ("Inserting [%s] into hash table [%s]\n", + name, table_name)); + + key.type = HASH_KEY_STRING; + key.str = talloc_strdup(NULL, name); + if (key.str == NULL) { + return ENOMEM; + } + + if (hash_has_key(table, &key)) { + talloc_free(key.str); + return EEXIST; + } + + value.type = HASH_VALUE_PTR; + value.ptr = entry; + + hret = hash_enter(table, &key, &value); + if (hret != HASH_SUCCESS) { + talloc_free(key.str); + return EIO; + } + + talloc_steal(table, key.str); + talloc_steal(table, value.ptr); + + return EOK; +} + +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; + errno_t ret; + + ret = sysdb_attrs_get_uint32_t(group, map[SDAP_AT_GROUP_GID].sys_name, + &gid); + if (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) { + 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_sysdb_search(struct sss_domain_info *domain, + const char *filter, + 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(NULL, domain->sysdb, domain, filter, attrs, + &count, &msgs); + } else { + ret = sysdb_search_groups(NULL, domain->sysdb, domain, filter, 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 sss_domain_info *domain, + const char *member_dn, + enum sdap_nested_group_dn_type *_type) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *sanitized_dn = NULL; + char *filter = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_new() failed\n")); + return ENOMEM; + } + + ret = sss_filter_sanitize(tmp_ctx, member_dn, &sanitized_dn); + if (ret != EOK) { + goto done; + } + + filter = talloc_asprintf(tmp_ctx, "(%s=%s)", SYSDB_ORIG_DN, sanitized_dn); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + /* search in users */ + ret = sdap_nested_group_sysdb_search_users(domain, filter); + 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 */ + ret = sdap_nested_group_sysdb_search_groups(domain, filter); + 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: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sdap_nested_group_split_members(TALLOC_CTX *mem_ctx, + struct sdap_nested_group_ctx *group_ctx, + 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; + + 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 */ + ret = sdap_nested_group_check_cache(group_ctx->domain, dn, &type); + 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 = sss_ldap_dn_in_search_bases(tmp_ctx, dn, + group_ctx->opts->user_search_bases, &user_filter); + + is_group = sss_ldap_dn_in_search_bases(tmp_ctx, dn, + group_ctx->opts->group_search_bases, &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 (type != SDAP_NESTED_GROUP_DN_USER) { + num_groups++; + } + } + + missing = talloc_realloc(mem_ctx, missing, + struct sdap_nested_group_member, num_missing); + if (missing == NULL) { + 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; +} + + +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 sss_domain_info *domain, + 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; + + 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, 32, &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, 32, &state->group_ctx->groups); + 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_treshold = 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 = domain; + state->group_ctx->opts = opts; + 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_treshold <= 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->user_search_bases[i] != NULL; i++) { + if (opts->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->group_search_bases[i] != NULL; i++) { + if (opts->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) +{ + 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); + + 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); + } + + 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; + int nesting_level; + char *group_dn; + bool deref; +}; + +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; + struct ldb_message_element *members = NULL; + const char *orig_dn = NULL; + errno_t ret; + + 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)); + + /* get member list */ + ret = sysdb_attrs_get_el(group, group_map[SDAP_AT_GROUP_MEMBER].sys_name, + &members); + if (ret == ENOENT) { + ret = EOK; /* no members */ + goto immediately; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to retrieve member list " + "[%d]: %s\n", ret, strerror(ret))); + goto immediately; + } + + /* get members that need to be refreshed */ + ret = sdap_nested_group_split_members(state, state->group_ctx, + state->nesting_level, members, + &state->missing, + &state->num_missing_total, + &state->num_missing_groups); + + DEBUG(SSSDBG_TRACE_INTERNAL, ("Looking up %d/%d members of group [%s]\n", + state->num_missing_total, members->num_values, orig_dn)); + + if (state->num_missing_total == 0) { + ret = EOK; /* we're done */ + goto immediately; + } + + /* process members */ + if (group_ctx->try_deref + && state->num_missing_total > group_ctx->deref_treshold) { + 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, members, + orig_dn, + state->num_missing_groups, + 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)); + + 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) +{ + 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; +}; + +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 */ + + /* 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 sdap_nested_group_single_step(struct tevent_req *req) +{ + struct sdap_nested_group_single_state *state = NULL; + struct tevent_req *subreq = NULL; + + state = tevent_req_data(req, struct sdap_nested_group_single_state); + + if (state->member_index >= state->num_members) { + /* we're done */ + return EOK; + } + + state->current_member = &state->members[state->member_index]; + state->member_index++; + + 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; + 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; + } + } + + /* save user in hash table */ + ret = sdap_nested_group_hash_user(state->group_ctx, entry); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, ("User was looked up twice, " + "this shouldn't have happened.\n")); + 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) { + DEBUG(SSSDBG_TRACE_FUNC, ("Group was looked up twice, " + "this shouldn't have happened.\n")); + 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: + /* not found in users nor nested_groups, continue */ + 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", ret, strerror(ret))); + tevent_req_error(req, ret); + } + + 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; +} + +/* This should be a function pointer set from the IPA provider */ +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) +{ + errno_t ret; + struct sysdb_attrs *user = NULL; + char *name; + struct ldb_dn *dn = NULL; + const char *rdn_name; + const char *users_comp_name; + const char *acct_comp_name; + const struct ldb_val *rdn_val; + const struct ldb_val *users_comp_val; + const struct ldb_val *acct_comp_val; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + /* return username if dn is in form: + * uid=username,cn=users,cn=accounts,dc=example,dc=com */ + + dn = ldb_dn_new(tmp_ctx, sysdb_ctx_get_ldb(sysdb), user_dn); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + /* rdn, users, accounts and least one domain component */ + if (ldb_dn_get_comp_num(dn) < 4) { + ret = ENOENT; + goto done; + } + + rdn_name = ldb_dn_get_rdn_name(dn); + if (rdn_name == NULL) { + ret = EINVAL; + goto done; + } + + /* rdn must be 'uid' */ + if (strcasecmp("uid", rdn_name) != 0) { + ret = ENOENT; + goto done; + } + + /* second component must be 'cn=users' */ + users_comp_name = ldb_dn_get_component_name(dn, 1); + if (strcasecmp("cn", users_comp_name) != 0) { + ret = ENOENT; + goto done; + } + + users_comp_val = ldb_dn_get_component_val(dn, 1); + if (strncasecmp("users", (const char *) users_comp_val->data, + users_comp_val->length) != 0) { + ret = ENOENT; + goto done; + } + + /* third component must be 'cn=accounts' */ + acct_comp_name = ldb_dn_get_component_name(dn, 2); + if (strcasecmp("cn", acct_comp_name) != 0) { + ret = ENOENT; + goto done; + } + + acct_comp_val = ldb_dn_get_component_val(dn, 2); + if (strncasecmp("accounts", (const char *) acct_comp_val->data, + acct_comp_val->length) != 0) { + ret = ENOENT; + goto done; + } + + /* value of rdn is username */ + user = sysdb_new_attrs(tmp_ctx); + if (user == NULL) { + ret = ENOMEM; + goto done; + } + + rdn_val = ldb_dn_get_rdn_val(dn); + name = talloc_strndup(user, (const char *)rdn_val->data, rdn_val->length); + if (name == 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_OBJECTCLASS, 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; + } + + 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_get_id_specific_filter(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, SDAP_OPTS_USER, + 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); + + 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; + errno_t ret; + + 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 */ + base_filter = talloc_asprintf(attrs, "(&(objectclass=%s)(%s=*))", + map[SDAP_OC_GROUP].name, + map[SDAP_AT_GROUP_NAME].name); + if (base_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* use search base filter if needed */ + filter = sdap_get_id_specific_filter(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); + + 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; + } + + 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; + goto immediately; + } + + tevent_req_set_callback(subreq, sdap_nested_group_lookup_unknown_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_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); + + 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 num_groups_max, + 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; + } + + state->ev = ev; + state->group_ctx = group_ctx; + state->members = members; + state->nesting_level = nesting_level; + 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 */ + + 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 = SDAP_OPTS_USER; + 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 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; + const char *orig_dn = NULL; + const char *member_dn = NULL; + size_t num_entries = 0; + size_t i, j; + bool member_found; + bool bret; + 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; + + ret = sdap_deref_search_recv(subreq, state, &num_entries, &entries); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, ("Received %d dereference results, " + "about to process them\n", num_entries)); + + 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. + */ + member_found = false; + for (j = 0; j < members->num_values; j++) { + /* FIXME: This is inefficient for very large sets of groups */ + member_dn = (const char *)members->values[j].data; + if (strcasecmp(orig_dn, member_dn) == 0) { + member_found = true; + break; + } + } + + if (!member_found) { + /* 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 */ + bret = sss_ldap_dn_in_search_bases(state, orig_dn, + opts->user_search_bases, NULL); + if (!bret) { + 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 */ + bret = sss_ldap_dn_in_search_bases(state, orig_dn, + opts->group_search_bases, NULL); + if (!bret) { + 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; + } + } + + ret = EOK; + +done: + 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) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} |