From 25699846bd1c9f8bb513b6271eb4366ab682fbd2 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Oct 31 2016 11:38:58 +0000 Subject: LDAP/AD: resolve domain local groups for remote users If a user from a trusted domain in the same forest is a direct or indirect member of domain local groups from the local domain those memberships must be resolved as well. Since those domain local groups are not valid in the trusted domain a DC from the trusted domain which is used to lookup the user data is not aware of them. As a consequence those memberships must be resolved against a local DC in a second step. Resolves https://fedorahosted.org/sssd/ticket/3206 Reviewed-by: Jakub Hrozek --- diff --git a/src/db/sysdb.h b/src/db/sysdb.h index f5d3ddb..9012683 100644 --- a/src/db/sysdb.h +++ b/src/db/sysdb.h @@ -225,6 +225,7 @@ SYSDB_OVERRIDE_OBJECT_DN, \ SYSDB_DEFAULT_OVERRIDE_NAME, \ SYSDB_UUID, \ + SYSDB_ORIG_DN, \ NULL} #define SYSDB_GRSRC_ATTRS {SYSDB_NAME, SYSDB_GIDNUM, \ diff --git a/src/providers/ldap/sdap_async_initgroups.c b/src/providers/ldap/sdap_async_initgroups.c index 0f56b87..45fc007 100644 --- a/src/providers/ldap/sdap_async_initgroups.c +++ b/src/providers/ldap/sdap_async_initgroups.c @@ -2317,6 +2317,7 @@ static errno_t rfc2307bis_nested_groups_step(struct tevent_req *req) struct sdap_rfc2307bis_nested_ctx *state = tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx); char *oc_list; + const char *class; tmp_ctx = talloc_new(state); if (!tmp_ctx) { @@ -2324,9 +2325,21 @@ static errno_t rfc2307bis_nested_groups_step(struct tevent_req *req) goto done; } - ret = sdap_get_group_primary_name(state, state->opts, - state->groups[state->group_iter], - state->dom, &state->primary_name); + ret = sysdb_attrs_get_string(state->groups[state->group_iter], + SYSDB_OBJECTCLASS, &class); + if (ret == EOK) { + /* If there is a objectClass attribute the object is coming from the + * cache and the name attribute of the object already has the primary + * name. + * If the objectClass attribute is missing the object is coming from + * LDAP and we have to find the primary name first. */ + ret = sysdb_attrs_get_string(state->groups[state->group_iter], + SYSDB_NAME, &state->primary_name); + } else { + ret = sdap_get_group_primary_name(state, state->opts, + state->groups[state->group_iter], + state->dom, &state->primary_name); + } if (ret != EOK) { goto done; } @@ -3069,6 +3082,103 @@ fail: tevent_req_error(req, ret); } +static void sdap_ad_check_domain_local_groups_done(struct tevent_req *subreq); + +errno_t sdap_ad_check_domain_local_groups(struct tevent_req *req) +{ + struct sdap_get_initgr_state *state = tevent_req_data(req, + struct sdap_get_initgr_state); + int ret; + struct sdap_domain *local_sdom; + const char *orig_name; + const char *sysdb_name; + struct ldb_result *res; + struct tevent_req *subreq; + struct sysdb_attrs **groups; + + /* We only need to check for domain local groups in the AD case and if the + * user is not from our domain, i.e. if the user comes from a sub-domain. + */ + if (state->opts->schema_type != SDAP_SCHEMA_AD + || !IS_SUBDOMAIN(state->dom) + || !dp_target_enabled(state->id_ctx->be->provider, "ad", DPT_ID)) { + return EOK; + } + + local_sdom = sdap_domain_get(state->id_ctx->opts, state->dom->parent); + if (local_sdom == NULL || local_sdom->pvt == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n", + state->dom->parent->name); + return EINVAL; + } + + ret = sysdb_attrs_get_string(state->orig_user, SYSDB_NAME, &orig_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing name in user object.\n"); + return ret; + } + + sysdb_name = sss_create_internal_fqname(state, orig_name, state->dom->name); + if (sysdb_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n"); + return ENOMEM; + } + + ret = sysdb_initgroups(state, state->dom, sysdb_name, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_initgroups failed for user [%s].\n", + sysdb_name); + return ret; + } + + if (res->count == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_initgroups returned no results for user [%s].\n", + sysdb_name); + return EINVAL; + } + + /* The user object, the first entry in the res->msgs, is included as well + * to cover the case where the remote user is directly added to + * a domain local group. */ + ret = sysdb_msg2attrs(state, res->count, res->msgs, &groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_msg2attrs failed.\n"); + return ret; + } + + subreq = sdap_ad_get_domain_local_groups_send(state, state->ev, local_sdom, + state->opts, state->sysdb, state->dom->parent, + groups, res->count); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_get_domain_local_groups_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, sdap_ad_check_domain_local_groups_done, + req); + + return EAGAIN; +} + +static void sdap_ad_check_domain_local_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int ret; + + ret = sdap_ad_get_domain_local_groups_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + + return; +} + static void sdap_get_initgr_pgid(struct tevent_req *req); static void sdap_get_initgr_done(struct tevent_req *subreq) { @@ -3201,8 +3311,6 @@ static void sdap_get_initgr_done(struct tevent_req *subreq) if (ret == EOK) { DEBUG(SSSDBG_TRACE_FUNC, "Primary group already cached, nothing to do.\n"); - ret = EOK; - goto done; } else { gid = talloc_asprintf(state, "%lu", (unsigned long)primary_gid); if (gid == NULL) { @@ -3219,10 +3327,28 @@ static void sdap_get_initgr_done(struct tevent_req *subreq) goto done; } tevent_req_set_callback(subreq, sdap_get_initgr_pgid, req); + + talloc_free(tmp_ctx); + return; } - talloc_free(tmp_ctx); - return; + ret = sdap_ad_check_domain_local_groups(req); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking for domain local group memberships.\n"); + talloc_free(tmp_ctx); + return; + } else if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "No need to check for domain local group memberships.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_check_domain_local_groups failed, " + "meberships to domain local groups might be missing.\n"); + /* do not let the request fail completely because we already have at + * least "some" groups */ + ret = EOK; + } done: talloc_free(tmp_ctx); @@ -3247,7 +3373,25 @@ static void sdap_get_initgr_pgid(struct tevent_req *subreq) return; } + ret = sdap_ad_check_domain_local_groups(req); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_ALL, + "Checking for domain local group memberships.\n"); + return; + } else if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "No need to check for domain local group memberships.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_check_domain_local_groups failed.\n"); + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_check_domain_local_groups failed, " + "meberships to domain local groups might be missing.\n"); + /* do not let the request fail completely because we already have at + * least "some" groups */ + } + tevent_req_done(req); + return; } int sdap_get_initgr_recv(struct tevent_req *req) diff --git a/src/providers/ldap/sdap_async_initgroups_ad.c b/src/providers/ldap/sdap_async_initgroups_ad.c index ad54c1f..1fee4ab 100644 --- a/src/providers/ldap/sdap_async_initgroups_ad.c +++ b/src/providers/ldap/sdap_async_initgroups_ad.c @@ -1412,6 +1412,413 @@ static errno_t sdap_ad_tokengroups_initgr_posix_recv(struct tevent_req *req) return EOK; } +struct sdap_ad_get_domain_local_groups_state { + struct tevent_context *ev; + struct sdap_id_conn_ctx *conn; + struct sdap_options *opts; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + int dp_error; + + struct sdap_search_base **search_bases; + struct sysdb_attrs **groups; + size_t num_groups; + hash_table_t *group_hash; +}; + +static void +sdap_ad_get_domain_local_groups_connect_done(struct tevent_req *subreq); +static void sdap_ad_get_domain_local_groups_done(struct tevent_req *subreq); + +struct tevent_req * +sdap_ad_get_domain_local_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *local_sdom, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sysdb_attrs **groups, + size_t num_groups) +{ + struct sdap_ad_get_domain_local_groups_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct ad_id_ctx *ad_id_ctx; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sdap_ad_get_domain_local_groups_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + ad_id_ctx = talloc_get_type(local_sdom->pvt, struct ad_id_ctx); + state->conn = ad_id_ctx->ldap_ctx; + state->opts = opts; + state->sysdb = sysdb; + state->dom = dom; + state->search_bases = state->conn->id_ctx->opts->sdom->group_search_bases; + state->groups = groups; + state->num_groups = num_groups; + + ret = sss_hash_create(state, 32, &state->group_hash); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto fail; + } + + state->op = sdap_id_op_create(state, state->conn->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n"); + goto fail; + } + + tevent_req_set_callback(subreq, + sdap_ad_get_domain_local_groups_connect_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void +sdap_ad_get_domain_local_groups_connect_done(struct tevent_req *subreq) +{ + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_ad_get_domain_local_groups_state *state = tevent_req_data(req, + struct sdap_ad_get_domain_local_groups_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + subreq = rfc2307bis_nested_groups_send(state, state->ev, state->opts, + state->sysdb, state->dom, + sdap_id_op_handle(state->op), + state->search_bases, + state->groups, state->num_groups, + state->group_hash, 0); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rfc2307bis_nested_groups_send failed.\n"); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, + sdap_ad_get_domain_local_groups_done, req); + + return; +} + +struct sdap_nested_group { + struct sysdb_attrs *group; + struct sysdb_attrs **ldap_parents; + size_t parents_count; +}; + +static errno_t +sdap_ad_get_domain_local_groups_parse_parents(TALLOC_CTX *mem_ctx, + struct sdap_nested_group *gr, + struct sss_domain_info *dom, + struct sysdb_ctx *sysdb, + struct sdap_options *opts, + const char **_sysdb_name, + enum sysdb_member_type *_type, + char ***_add_list, + char ***_del_list) +{ + int ret; + size_t c; + char **groupnamelist = NULL; + struct sysdb_attrs *groups[1]; + enum sysdb_member_type type; + const char *sysdb_name; + const char *group_name; + const char *class; + struct sss_domain_info *obj_dom; + char *local_groups_base_dn; + char **cached_local_parents = NULL; + char **add_list = NULL; + char **del_list = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + local_groups_base_dn = talloc_asprintf(tmp_ctx, SYSDB_TMPL_GROUP_BASE, + dom->name); + if (local_groups_base_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + if (gr->parents_count != 0) { + /* Store the parents if needed */ + ret = sdap_nested_groups_store(sysdb, dom, opts, + gr->ldap_parents, gr->parents_count); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sysdb_attrs_primary_fqdn_list(dom, tmp_ctx, + gr->ldap_parents, gr->parents_count, + opts->group_map[SDAP_AT_GROUP_NAME].name, + &groupnamelist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_primary_fqdn_list failed.\n"); + goto done; + } + } + + ret = sysdb_attrs_get_string(gr->group, SYSDB_NAME, &sysdb_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string failed to get SYSDB_NAME, " + "skipping.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(gr->group, SYSDB_OBJECTCLASS, &class); + if (ret != EOK) { + /* If objectclass is missing gr->group is a nested parent found during + * the nested group lookup. It might not already stored in the cache. + */ + DEBUG(SSSDBG_TRACE_LIBS, + "sysdb_attrs_get_string failed to get SYSDB_OBJECTCLASS " + "for [%s], assuming group.\n", sysdb_name); + + /* make sure group exists in cache */ + groups[0]= gr->group; + ret = sdap_nested_groups_store(sysdb, dom, opts, groups, 1); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + /* Since the object is coming from LDAP it cannot have the internal + * fully-qualified name, so we can expand it unconditionally. */ + group_name = NULL; + ret = sysdb_attrs_primary_name(dom->sysdb, gr->group, + opts->group_map[SDAP_AT_GROUP_NAME].name, + &group_name); + if (ret != EOK || group_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Could not determine primary name\n"); + group_name = sysdb_name; + } + + group_name = sss_create_internal_fqname(tmp_ctx, group_name, + dom->name); + if (group_name != NULL) { + sysdb_name = group_name; + } + + type = SYSDB_MEMBER_GROUP; + } else { + if (class != NULL && strcmp(class, SYSDB_USER_CLASS) == 0) { + type = SYSDB_MEMBER_USER; + } else { + type = SYSDB_MEMBER_GROUP; + } + } + + /* We need to get the cached list of groups form the local domain the + * object is a member of to compare them with the current list just + * retrieved (groupnamelist). Even if this list is empty we have to + * proceed because the membership might have been removed recently on the + * server. */ + + obj_dom = find_domain_by_object_name(get_domains_head(dom), + sysdb_name); + if (obj_dom == NULL) { + obj_dom = dom; + DEBUG(SSSDBG_OP_FAILURE, "Cannot find domain for [%s], " + "trying with local domain [%s].\n", + sysdb_name, obj_dom->name); + } + + ret = sysdb_get_direct_parents(tmp_ctx, obj_dom, dom, type, sysdb_name, + &cached_local_parents); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE,"sysdb_get_direct_parents failed.\n"); + goto done; + } + + if (cached_local_parents != NULL && cached_local_parents[0] == NULL) { + talloc_zfree(cached_local_parents); + } + + if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) { + if (cached_local_parents != NULL) { + for (c = 0; cached_local_parents[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] cached_local_parents [%s].\n", + sysdb_name, cached_local_parents[c]); + } + } + + if (groupnamelist != NULL) { + for (c = 0; groupnamelist[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "[%s] groupnamelist [%s].\n", + sysdb_name, groupnamelist[c]); + } + } + } + + ret = diff_string_lists(tmp_ctx, cached_local_parents, groupnamelist, + &del_list, &add_list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "diff_string_lists failed.\n"); + goto done; + } + + if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) { + if (add_list != NULL) { + for (c = 0; add_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "add: [%s] will be member of [%s].\n", + sysdb_name, add_list[c]); + } + } + if (del_list != NULL) { + for (c = 0; del_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "del: [%s] was member of [%s].\n", + sysdb_name, del_list[c]); + } + } + } + + *_type = type; + *_sysdb_name = talloc_steal(mem_ctx, sysdb_name); + *_add_list = talloc_steal(mem_ctx, groupnamelist); + *_del_list = talloc_steal(mem_ctx, del_list); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static void sdap_ad_get_domain_local_groups_done(struct tevent_req *subreq) +{ + + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sdap_ad_get_domain_local_groups_state *state = tevent_req_data(req, + struct sdap_ad_get_domain_local_groups_state); + int ret; + int hret; + unsigned long count; + hash_value_t *values = NULL; + struct sdap_nested_group *gr; + size_t c; + const char *sysdb_name = NULL; + enum sysdb_member_type type; + char **add_list = NULL; + char **del_list = NULL; + + ret = rfc2307bis_nested_groups_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + hret = hash_values(state->group_hash, &count, &values); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_values failed.\n"); + ret = EIO; + goto done; + } + + for (c = 0; c < count; c++) { + gr = talloc_get_type(values[c].ptr, + struct sdap_nested_group); + + /* The values from the hash are either user or group objects returned + * by sysdb_initgroups() which where used to start the request or + * nested parents found during the request. The nested parents contain + * the processed LDAP data and can be identified by a missing + * objectclass attribute. */ + ret = sdap_ad_get_domain_local_groups_parse_parents(state, gr, + state->dom, + state->sysdb, + state->opts, + &sysdb_name, + &type, + &add_list, + &del_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_get_domain_local_groups_parse_parents failed.\n"); + continue; + } + + if ((add_list == NULL && del_list == NULL) + || (add_list == NULL && del_list != NULL && del_list[0] == NULL) + || (add_list != NULL && add_list[0] == NULL && del_list == NULL) + || (add_list != NULL && add_list[0] == NULL + && del_list != NULL && del_list[0] == NULL) ) { + continue; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Updating domain local memberships for %s\n", + sysdb_name); + ret = sysdb_update_members(state->dom, sysdb_name, type, + (const char *const *) add_list, + (const char *const *) del_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_members failed.\n"); + goto done; + } + } + + ret = EOK; +done: + talloc_zfree(values); + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +errno_t sdap_ad_get_domain_local_groups_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + struct sdap_ad_tokengroups_initgroups_state { bool use_id_mapping; struct sss_domain_info *domain; diff --git a/src/providers/ldap/sdap_async_private.h b/src/providers/ldap/sdap_async_private.h index 4af4f71..266bc03 100644 --- a/src/providers/ldap/sdap_async_private.h +++ b/src/providers/ldap/sdap_async_private.h @@ -173,4 +173,14 @@ errno_t sdap_nested_groups_store(struct sysdb_ctx *sysdb, struct sysdb_attrs **groups, unsigned long count); +struct tevent_req * +sdap_ad_get_domain_local_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_domain *local_sdom, + struct sdap_options *opts, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sysdb_attrs **groups, + size_t num_groups); +errno_t sdap_ad_get_domain_local_groups_recv(struct tevent_req *req); #endif /* _SDAP_ASYNC_PRIVATE_H_ */