From 97ae45d61d921f07e812620e0156aee02b7b83a7 Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Sun, 10 Jun 2012 13:07:15 -0400 Subject: LDAP: Add support for AD chain matching extension in group lookups --- src/providers/ldap/sdap_async.h | 14 ++ src/providers/ldap/sdap_async_groups.c | 162 +++++++++++++++++-- src/providers/ldap/sdap_async_groups_ad.c | 250 ++++++++++++++++++++++++++++++ 3 files changed, 417 insertions(+), 9 deletions(-) create mode 100644 src/providers/ldap/sdap_async_groups_ad.c (limited to 'src') diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h index 7b5dba7bc..9a65b25e0 100644 --- a/src/providers/ldap/sdap_async.h +++ b/src/providers/ldap/sdap_async.h @@ -248,4 +248,18 @@ enum_services_recv(struct tevent_req *req); */ #define SDAP_MATCHING_RULE_IN_CHAIN "1.2.840.113556.1.4.1941" +struct tevent_req * +sdap_get_ad_match_rule_members_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sysdb_attrs *group, + int timeout); + +errno_t +sdap_get_ad_match_rule_members_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *num_users, + struct sysdb_attrs ***users); + #endif /* _SDAP_ASYNC_H_ */ diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c index 70e656bd7..8be5ff1d9 100644 --- a/src/providers/ldap/sdap_async_groups.c +++ b/src/providers/ldap/sdap_async_groups.c @@ -1352,6 +1352,8 @@ static struct tevent_req *sdap_nested_group_process_send( bool enable_deref, uint32_t nesting); static void sdap_nested_done(struct tevent_req *req); static errno_t sdap_nested_group_process_recv(struct tevent_req *req); +static void sdap_ad_match_rule_members_process(struct tevent_req *subreq); + static void sdap_get_groups_process(struct tevent_req *subreq) { struct tevent_req *req = @@ -1433,10 +1435,15 @@ static void sdap_get_groups_process(struct tevent_req *subreq) * for RFC2307bis/FreeIPA/ActiveDirectory * We don't need to do this for enumeration, * because all groups will be picked up anyway. + * + * We can also skip this if we're using the + * LDAP_MATCHING_RULE_IN_CHAIN available in + * AD 2008 and later */ if (!state->enumeration) { - if ((state->opts->schema_type != SDAP_SCHEMA_RFC2307) && - (dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0)) { + if ((state->opts->schema_type != SDAP_SCHEMA_RFC2307) + && (dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0) + && !dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE_GROUPS)) { /* Prepare hashes for nested user processing */ ret = sss_hash_create(state, 32, &state->user_hash); @@ -1501,6 +1508,25 @@ static void sdap_get_groups_process(struct tevent_req *subreq) /* We have all of the groups. Save them to the sysdb */ state->check_count = state->count; + /* If we're using LDAP_MATCHING_RULE_IN_CHAIN, start a subreq to + * retrieve the members so we can save them in a single step. + */ + if (!state->enumeration + && (state->opts->schema_type != SDAP_SCHEMA_RFC2307) + && dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE_GROUPS)) { + subreq = sdap_get_ad_match_rule_members_send( + state, state->ev, state->opts, state->sh, + state->groups[0], state->timeout); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, + sdap_ad_match_rule_members_process, + req); + return; + } + ret = sysdb_transaction_start(state->sysdb); if (ret != EOK) { DEBUG(0, ("Failed to start transaction\n")); @@ -1584,6 +1610,131 @@ static void sdap_get_groups_done(struct tevent_req *subreq) } } +static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + struct sysdb_attrs **users, + int num_users, + hash_table_t **_ghosts); + +static void sdap_ad_match_rule_members_process(struct tevent_req *subreq) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_get_groups_state *state = tevent_req_data(req, + struct sdap_get_groups_state); + struct sysdb_attrs **users; + struct sysdb_attrs *group = state->groups[0]; + struct ldb_message_element *member_el; + struct ldb_message_element *orig_dn_el; + size_t count; + size_t i; + hash_table_t *ghosts; + + ret = sdap_get_ad_match_rule_members_recv(subreq, state, + &count, &users); + talloc_zfree(subreq); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not retrieve members using AD match rule. [%s]\n", + strerror(ret))); + + goto done; + } + + /* Save the group and users to the cache */ + + /* Truncate the member attribute of the group. + * It will be repopulated below, and it may currently + * be incomplete anyway, thanks to the range extension. + */ + + ret = sysdb_attrs_get_el(group, SYSDB_MEMBER, &member_el); + if (ret != EOK) { + goto done; + } + + member_el->num_values = 0; + talloc_zfree(member_el->values); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + /* Figure out which users are already cached in the sysdb and + * which ones need to be added as ghost users. + */ + ret = sdap_nested_group_populate_users(tmp_ctx, state->sysdb, + state->opts, users, count, + &ghosts); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not determine which users are ghosts: [%s]\n", + strerror(ret))); + goto done; + } + + /* Add any entries that aren't in the ghost hash table to the + * member element of the group. This will get converted to a + * native sysdb representation later in sdap_save_groups(). + */ + + /* Add all of the users as members + */ + member_el->values = talloc_zero_array(tmp_ctx, struct ldb_val, count); + if (!member_el->values) { + ret = ENOMEM; + goto done; + } + + /* Copy the origDN values of the users into the member element */ + for (i = 0; i < count; i++) { + ret = sysdb_attrs_get_el(users[i], SYSDB_ORIG_DN, + &orig_dn_el); + if (ret != EOK) { + /* This should never happen. Every entry should have + * an originalDN. + */ + DEBUG(SSSDBG_MINOR_FAILURE, + ("BUG: Missing originalDN for user?\n")); + goto done; + } + + /* These values will have the same lifespan, so instead + * of copying them, just point at the data. + */ + member_el->values[i].data = orig_dn_el->values[0].data; + member_el->values[i].length = orig_dn_el->values[0].length; + } + member_el->num_values = count; + + /* Now save the group, users and ghosts to the cache */ + ret = sdap_save_groups(tmp_ctx, state->sysdb, state->dom, + state->opts, state->groups, 1, + false, ghosts, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not save group to the cache: [%s]\n", + strerror(ret))); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + int sdap_get_groups_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, char **usn_value) { @@ -1599,13 +1750,6 @@ int sdap_get_groups_recv(struct tevent_req *req, return EOK; } -static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx, - struct sysdb_ctx *sysdb, - struct sdap_options *opts, - struct sysdb_attrs **users, - int num_users, - hash_table_t **_ghosts); - static void sdap_nested_done(struct tevent_req *subreq) { errno_t ret, tret; diff --git a/src/providers/ldap/sdap_async_groups_ad.c b/src/providers/ldap/sdap_async_groups_ad.c new file mode 100644 index 000000000..1082957f9 --- /dev/null +++ b/src/providers/ldap/sdap_async_groups_ad.c @@ -0,0 +1,250 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "util/util.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/ldap_common.h" + +struct sdap_ad_match_rule_state { + struct tevent_context *ev; + struct sdap_handle *sh; + const char **attrs; + + struct sdap_options *opts; + const char *base_filter; + char *filter; + int timeout; + + size_t base_iter; + struct sdap_search_base **search_bases; + + size_t count; + struct sysdb_attrs **users; +}; + +static errno_t +sdap_get_ad_match_rule_members_next_base(struct tevent_req *req); +static void +sdap_get_ad_match_rule_members_step(struct tevent_req *subreq); + +struct tevent_req * +sdap_get_ad_match_rule_members_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_options *opts, + struct sdap_handle *sh, + struct sysdb_attrs *group, + int timeout) +{ + errno_t ret; + struct tevent_req *req; + struct sdap_ad_match_rule_state *state; + const char *group_dn; + char *sanitized_group_dn; + + req = tevent_req_create(mem_ctx, &state, struct sdap_ad_match_rule_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->sh = sh; + state->timeout = timeout; + state->count = 0; + state->base_iter = 0; + state->search_bases = opts->user_search_bases; + + /* Request all of the user attributes that we know about. */ + ret = build_attrs_from_map(state, opts->user_map, SDAP_OPTS_USER, + NULL, &state->attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not build attribute map: [%s]\n", + strerror(ret))); + goto immediate; + } + + /* Get the DN of the group */ + ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN, &group_dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not retrieve originalDN for group: %s\n", + strerror(ret))); + goto immediate; + } + + /* Sanitize it in case we have special characters in DN */ + ret = sss_filter_sanitize(state, group_dn, &sanitized_group_dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not sanitize group DN: %s\n", + strerror(ret))); + goto immediate; + } + + /* Craft a special filter according to + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa746475%28v=vs.85%29.aspx + */ + state->base_filter = + talloc_asprintf(state, + "(&(%s:%s:=%s)(objectClass=%s))", + state->opts->user_map[SDAP_AT_USER_MEMBEROF].name, + SDAP_MATCHING_RULE_IN_CHAIN, + sanitized_group_dn, + state->opts->user_map[SDAP_OC_USER].name); + talloc_zfree(sanitized_group_dn); + if (!state->base_filter) { + ret = ENOMEM; + goto immediate; + } + + /* Start the loop through the search bases to get all of the users */ + ret = sdap_get_ad_match_rule_members_next_base(req); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("sdap_get_ad_match_rule_members_next_base failed: [%s]\n", + strerror(ret))); + goto immediate; + } + + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +sdap_get_ad_match_rule_members_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sdap_ad_match_rule_state *state; + + state = tevent_req_data(req, struct sdap_ad_match_rule_state); + + talloc_zfree(state->filter); + state->filter = sdap_get_id_specific_filter(state, + state->base_filter, + state->search_bases[state->base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + ("Searching for users with base [%s]\n", + state->search_bases[state->base_iter]->basedn)); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + state->search_bases[state->base_iter]->basedn, + state->search_bases[state->base_iter]->scope, + state->filter, state->attrs, + state->opts->user_map, SDAP_OPTS_USER, + state->timeout, true); + if (!subreq) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_get_ad_match_rule_members_step, req); + + return EOK; +} + +static void +sdap_get_ad_match_rule_members_step(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sdap_ad_match_rule_state *state = + tevent_req_data(req, struct sdap_ad_match_rule_state); + size_t count, i; + struct sysdb_attrs **users; + + ret = sdap_get_generic_recv(subreq, state, &count, &users); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("LDAP search failed: [%s]\n", strerror(ret))); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, + ("Search for users returned %d results\n", count)); + + /* Add this batch of users to the list */ + if (count > 0) { + state->users = talloc_realloc(state, state->users, + struct sysdb_attrs *, + state->count + count + 1); + if (!state->users) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Copy the new users into the list */ + for (i = 0; i < count; i++) { + state->users[state->count + i] = + talloc_steal(state->users, users[i]); + } + + state->count += count; + state->users[state->count] = NULL; + } + + /* Continue checking other search bases */ + state->base_iter++; + if (state->search_bases[state->base_iter]) { + /* There are more search bases to try */ + ret = sdap_get_ad_match_rule_members_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } + + /* No more search bases. We're done here. */ + if (state->count == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + ("No users matched in any search base\n")); + tevent_req_error(req, ENOENT); + return; + } + + tevent_req_done(req); +} + +errno_t +sdap_get_ad_match_rule_members_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *num_users, + struct sysdb_attrs ***users) +{ + struct sdap_ad_match_rule_state *state = + tevent_req_data(req, struct sdap_ad_match_rule_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *num_users = state->count; + *users = talloc_steal(mem_ctx, state->users); + return EOK; +} -- cgit