>From 61a132394ba2b394cc3c95676ab8e782252384af Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Wed, 22 Jan 2014 15:21:24 +0100 Subject: [PATCH 6/6] AD: Establish cross-domain memberships after enumeration finishes Because domain enumeration currently works for each domain separately, the code has to establish cross-domain memberships after all domains are enumerated. The code works as follows: 1) check if any *sub*domains were enumerated. If not, do nothing 2) if any of the groups saved had more original members than sysdb members, check if members of these groups can be linked now that all users and groups are saved using the orig_member attribute of the group matched against originalDN member of the user. Related: https://fedorahosted.org/sssd/ticket/2142 --- src/providers/ad/ad_id.c | 511 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 386 insertions(+), 125 deletions(-) diff --git a/src/providers/ad/ad_id.c b/src/providers/ad/ad_id.c index def906ca1e3640ef941500a6dcd3d12fee1a1b3a..150d120d6e6749bcf6fddd8aee5d7b2a84dfb8f7 100644 --- a/src/providers/ad/ad_id.c +++ b/src/providers/ad/ad_id.c @@ -420,10 +420,13 @@ struct ad_enumeration_state { struct tevent_context *ev; struct sdap_domain *sdom; + struct sdap_domain *sditer; }; static void ad_enumeration_conn_done(struct tevent_req *subreq); static void ad_enumeration_master_done(struct tevent_req *subreq); +static errno_t ad_enum_sdom(struct tevent_req *req, struct sdap_domain *sd, + struct ad_id_ctx *id_ctx); static void ad_enumeration_done(struct tevent_req *subreq); struct tevent_req * @@ -452,6 +455,7 @@ ad_enumeration_send(TALLOC_CTX *mem_ctx, state->ectx = ectx; state->ev = ev; state->sdom = ectx->sdom; + state->sditer = state->sdom; state->id_ctx = talloc_get_type(ectx->pvt, struct ad_id_ctx); state->sdap_op = sdap_id_op_create(state, @@ -526,7 +530,6 @@ ad_enumeration_master_done(struct tevent_req *subreq) char *flat_name; char *master_sid; char *forest; - struct sdap_id_conn_ctx *user_conn; ret = ad_master_domain_recv(subreq, state, &flat_name, &master_sid, &forest); @@ -545,32 +548,53 @@ ad_enumeration_master_done(struct tevent_req *subreq) return; } - if (dp_opt_get_bool(state->id_ctx->ad_options->basic, AD_ENABLE_GC)) { - user_conn = state->id_ctx->gc_ctx; + ret = ad_enum_sdom(req, state->sdom, state->id_ctx); + DEBUG(SSSDBG_OP_FAILURE, + ("Could not enumerate domain %s\n", state->sdom->dom->name)); + tevent_req_error(req, ret); + return; +} + +static errno_t +ad_enum_sdom(struct tevent_req *req, + struct sdap_domain *sd, + struct ad_id_ctx *id_ctx) +{ + struct sdap_id_conn_ctx *user_conn; + struct tevent_req *subreq; + struct ad_enumeration_state *state = tevent_req_data(req, + struct ad_enumeration_state); + + if (dp_opt_get_bool(id_ctx->ad_options->basic, AD_ENABLE_GC)) { + user_conn = id_ctx->gc_ctx; } else { - user_conn = state->id_ctx->ldap_ctx; + user_conn = id_ctx->ldap_ctx; } /* Groups are searched for in LDAP, users in GC. Services (if present, * which is unlikely in AD) from LDAP as well */ subreq = sdap_dom_enum_ex_send(state, state->ev, - state->id_ctx->sdap_id_ctx, - state->sdom, - user_conn, /* Users */ - state->id_ctx->ldap_ctx, /* Groups */ - state->id_ctx->ldap_ctx); /* Services */ + id_ctx->sdap_id_ctx, + sd, + user_conn, /* Users */ + id_ctx->ldap_ctx, /* Groups */ + id_ctx->ldap_ctx); /* Services */ if (subreq == NULL) { /* The ptask API will reschedule the enumeration on its own on * failure */ DEBUG(SSSDBG_OP_FAILURE, ("Failed to schedule enumeration, retrying later!\n")); - tevent_req_error(req, ENOMEM); - return; + return ENOMEM; } tevent_req_set_callback(subreq, ad_enumeration_done, req); + + return EOK; } +static errno_t ad_enum_cross_dom_members(struct sdap_options *opts, + struct sss_domain_info *dom); + static void ad_enumeration_done(struct tevent_req *subreq) { @@ -579,121 +603,8 @@ ad_enumeration_done(struct tevent_req *subreq) struct tevent_req); struct ad_enumeration_state *state = tevent_req_data(req, struct ad_enumeration_state); - - ret = sdap_dom_enum_ex_recv(subreq); - talloc_zfree(subreq); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - ("Could not enumerate domain %s\n", state->sdom->dom->name)); - tevent_req_error(req, ret); - return; - } - - tevent_req_done(req); -} - -errno_t -ad_enumeration_recv(struct tevent_req *req) -{ - TEVENT_REQ_RETURN_ON_ERROR(req); - return EOK; -} - -/* Enumerate a subdomain */ -struct ad_enum_subdom_state { - struct ad_id_ctx *id_ctx; struct ad_id_ctx *subdom_id_ctx; - struct ldap_enum_ctx *ectx; - struct tevent_context *ev; - - struct sdap_domain *sdom; -}; - -static void ad_enum_subdom_done(struct tevent_req *subreq); - -struct tevent_req * -ad_enum_subdom_send(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct be_ctx *be_ctx, - struct be_ptask *be_ptask, - void *pvt) -{ - struct tevent_req *req; - struct tevent_req *subreq; - struct ad_enum_subdom_state *state; - struct ldap_enum_ctx *ectx; - errno_t ret; - struct sdap_id_conn_ctx *user_conn; - - req = tevent_req_create(mem_ctx, &state, struct ad_enum_subdom_state); - if (req == NULL) return NULL; - - ectx = talloc_get_type(pvt, struct ldap_enum_ctx); - if (ectx == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot retrieve ldap_enum_ctx!\n")); - ret = EFAULT; - goto fail; - } - - state->ectx = ectx; - state->ev = ev; - state->sdom = ectx->sdom; - state->id_ctx = talloc_get_type(ectx->pvt, struct ad_id_ctx); - if (state->id_ctx == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot retrieve ad_id_ctx!\n")); - ret = EFAULT; - goto fail; - } - - state->subdom_id_ctx = talloc_get_type(state->sdom->pvt, struct ad_id_ctx); - if (state->subdom_id_ctx == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot retrieve subdomain ad_id_ctx!\n")); - ret = EFAULT; - goto fail; - } - - if (dp_opt_get_bool(state->id_ctx->ad_options->basic, AD_ENABLE_GC)) { - user_conn = state->id_ctx->gc_ctx; - } else { - user_conn = state->subdom_id_ctx->ldap_ctx; - } - - /* Groups are searched for in LDAP, users in GC. Services (if present, - * which is unlikely in AD) from LDAP as well - */ - subreq = sdap_dom_enum_ex_send(state, state->ev, state->id_ctx->sdap_id_ctx, - state->sdom, - user_conn, /* Users */ - state->subdom_id_ctx->ldap_ctx, /* Groups */ - state->subdom_id_ctx->ldap_ctx);/* svcs */ - if (subreq == NULL) { - /* The ptask API will reschedule the enumeration on its own on - * failure */ - DEBUG(SSSDBG_OP_FAILURE, - ("Failed to schedule enumeration, retrying later!\n")); - ret = ENOMEM; - goto fail; - } - tevent_req_set_callback(subreq, ad_enum_subdom_done, req); - - return req; - -fail: - tevent_req_error(req, ret); - tevent_req_post(req, ev); - return req; -} - -static void -ad_enum_subdom_done(struct tevent_req *subreq) -{ - errno_t ret; - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct ad_enum_subdom_state *state = tevent_req_data(req, - struct ad_enum_subdom_state); - ret = sdap_dom_enum_ex_recv(subreq); talloc_zfree(subreq); if (ret != EOK) { @@ -703,11 +614,361 @@ ad_enum_subdom_done(struct tevent_req *subreq) return; } + state->sditer = state->sditer->next; + if (state->sditer != NULL) { + subdom_id_ctx = talloc_get_type(state->sdom->pvt, struct ad_id_ctx); + if (subdom_id_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot retrieve subdomain ad_id_ctx!\n")); + tevent_req_error(req, EFAULT); + return; + } + + ret = ad_enum_sdom(req, state->sditer, state->sditer->pvt); + DEBUG(SSSDBG_OP_FAILURE, + ("Could not enumerate domain %s\n", state->sditer->dom->name)); + tevent_req_error(req, ret); + return; + } + + /* No more subdomains to enumerate. Check if we need to fixup + * cross-domain membership + */ + if (state->sditer != state->sdom) { + /* We did enumerate at least one subdomain. Walk the subdomains + * and fixup members for each of them + */ + for (state->sditer = state->sdom; + state->sditer; + state->sditer = state->sditer->next) { + ret = ad_enum_cross_dom_members(state->id_ctx->ad_options->id, + state->sditer->dom); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, ("Could not check cross-domain " + "memberships for %s, group memberships might be " + "incomplete!\n", state->sdom->dom->name)); + continue; + } + } + } + tevent_req_done(req); } +static errno_t ad_group_extra_members(TALLOC_CTX *mem_ctx, + const struct ldb_message *group, + struct sss_domain_info *dom, + char ***_group_only); +static errno_t ad_group_add_member(struct sdap_options *opts, + struct sss_domain_info *group_domain, + struct ldb_dn *group_dn, + const char *member); + +static errno_t +ad_enum_cross_dom_members(struct sdap_options *opts, + struct sss_domain_info *dom) +{ + errno_t ret; + errno_t sret; + char *filter; + TALLOC_CTX *tmp_ctx; + const char *attrs[] = { + SYSDB_NAME, + SYSDB_MEMBER, + SYSDB_ORIG_MEMBER, + NULL + }; + size_t count, i, mi; + struct ldb_message **msgs; + bool in_transaction = false; + char **group_only; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + ret = sysdb_transaction_start(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to start transaction\n")); + goto done; + } + in_transaction = true; + + filter = talloc_asprintf(tmp_ctx, "(%s=*)", SYSDB_NAME); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_groups(tmp_ctx, dom, filter, attrs, &count, &msgs); + if (ret != EOK) { + goto done; + } + + for (i = 0; i < count; i++) { + ret = ad_group_extra_members(tmp_ctx, msgs[i], dom, &group_only); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("Failed to check extra members\n")); + } else if (group_only == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, ("No extra members\n")); + continue; + } + + /* Group has extra members */ + for (mi = 0; group_only[mi]; mi++) { + ret = ad_group_add_member(opts, dom, msgs[i]->dn, group_only[mi]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, ("Failed to add [%s]: %s\n", + group_only[mi], strerror(ret))); + continue; + } + } + } + + ret = sysdb_transaction_commit(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to commit transaction\n")); + goto done; + } + in_transaction = false; + + ret = EOK; +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(dom->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Could not cancel transaction\n")); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static char ** +ad_group_orig_members(TALLOC_CTX *mem_ctx, struct ldb_message_element *om); +static errno_t +ad_group_stored_orig_members(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + struct ldb_dn *dn, char ***_odn_list); + +static errno_t +ad_group_extra_members(TALLOC_CTX *mem_ctx, const struct ldb_message *group, + struct sss_domain_info *dom, char ***_group_only) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *m, *om; + const char *name; + errno_t ret; + char **sysdb_odn_list; + char **group_odn_list; + char **group_only = NULL; + + if (_group_only == NULL) return EINVAL; + *_group_only = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + om = ldb_msg_find_element(group, SYSDB_ORIG_MEMBER); + m = ldb_msg_find_element(group, SYSDB_MEMBER); + name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("A group with no name!\n")); + ret = EFAULT; + goto done; + } + + if (om == NULL || om->num_values == 0) { + DEBUG(SSSDBG_TRACE_FUNC, ("Group %s has no original members\n", name)); + ret = EOK; + goto done; + } + + if (m == NULL || (m->num_values < om->num_values)) { + DEBUG(SSSDBG_TRACE_FUNC, + ("Group %s has %d members but %d original members\n", + name, m ? m->num_values : 0, om->num_values)); + + /* Get the list of originalDN attributes that are already + * linked to the group + */ + ret = ad_group_stored_orig_members(tmp_ctx, dom, group->dn, + &sysdb_odn_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + ("Could not retrieve list of original members for %s\n", + name)); + goto done; + } + + /* Get the list of original DN attributes the group had in AD */ + group_odn_list = ad_group_orig_members(tmp_ctx, om); + if (group_odn_list == NULL) { + ret = EFAULT; + goto done; + } + + /* Compare the two lists */ + ret = diff_string_lists(tmp_ctx, group_odn_list, sysdb_odn_list, + &group_only, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + ("Could not compare lists of members for %s\n", name)); + goto done; + } + } + + ret = EOK; + *_group_only = talloc_steal(mem_ctx, group_only); +done: + talloc_free(tmp_ctx); + return ret; +} + +static char ** +ad_group_orig_members(TALLOC_CTX *mem_ctx, struct ldb_message_element *om) +{ + char **odn_list; + size_t i; + + /* Get the list of original DN attributes the group had in AD */ + odn_list = talloc_zero_array(mem_ctx, char *, om->num_values + 1); + if (odn_list == NULL) { + return NULL; + } + + for (i=0; i < om->num_values; i++) { + odn_list[i] = (char *) om->values[i].data; + } + + return odn_list; +} + +static errno_t +ad_group_stored_orig_members(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + struct ldb_dn *dn, char ***_odn_list) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + size_t m_count, i; + struct ldb_message **members; + const char *attrs[] = { + SYSDB_NAME, + SYSDB_ORIG_DN, + NULL + }; + char **odn_list; + const char *odn; + size_t oi; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + /* Get all entries member element points to */ + ret = sysdb_asq_search(tmp_ctx, dom, dn, NULL, SYSDB_MEMBER, + attrs, &m_count, &members); + if (ret != EOK) { + goto done; + } + + odn_list = talloc_zero_array(tmp_ctx, char *, m_count + 1); + if (odn_list == NULL) { + ret = ENOMEM; + goto done; + } + + /* Get a list of their original DNs */ + oi = 0; + for (i = 0; i < m_count; i++) { + odn = ldb_msg_find_attr_as_string(members[i], SYSDB_ORIG_DN, NULL); + if (odn == NULL) { + continue; + } + + odn_list[oi] = talloc_strdup(odn_list, odn); + if (odn_list[oi] == NULL) { + continue; + } + oi++; + DEBUG(SSSDBG_TRACE_INTERNAL, ("Member %s already in sysdb\n", odn)); + } + + ret = EOK; + *_odn_list = talloc_steal(mem_ctx, odn_list); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ad_group_add_member(struct sdap_options *opts, + struct sss_domain_info *group_domain, + struct ldb_dn *group_dn, + const char *member) +{ + struct sdap_domain *sd; + struct ldb_dn *base_dn; + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *mem_filter; + size_t msgs_count; + struct ldb_message **msgs; + + /* This member would be from a different domain */ + sd = sdap_domain_get_by_dn(opts, member); + if (sd == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, ("No matching domain for %s\n", member)); + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + mem_filter = talloc_asprintf(tmp_ctx, "(%s=%s)", + SYSDB_ORIG_DN, member); + if (mem_filter == NULL) { + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_domain_dn(tmp_ctx, sd->dom); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, sd->dom->sysdb, base_dn, + LDB_SCOPE_SUBTREE, mem_filter, NULL, + &msgs_count, &msgs); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, ("No member [%s] in sysdb\n", member)); + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, ("[%s] found in sysdb\n", member)); + + if (msgs_count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("Search by orig DN returned %zd results!\n", msgs_count)); + ret = EFAULT; + goto done; + } + + ret = sysdb_mod_group_member(group_domain, msgs[0]->dn, group_dn, SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("Could not add [%s] as a member of [%s]\n", + ldb_dn_get_linearized(msgs[0]->dn), + ldb_dn_get_linearized(group_dn))); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + errno_t -ad_enum_subdom_recv(struct tevent_req *req) +ad_enumeration_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; -- 1.8.4.2