From d8ab5b155bc0d4b451702d89c2fea9c3f84b9f46 Mon Sep 17 00:00:00 2001
From: Jan Zeleny <jzeleny@redhat.com>
Date: Wed, 16 Nov 2011 04:19:57 -0500
Subject: [PATCH 5/7] Added support for fetching netgroups in IPA provider

---
 Makefile.am                       |    1 +
 src/providers/ipa/ipa_netgroups.c |  992 +++++++++++++++++++++++++++++++++++++
 2 files changed, 993 insertions(+), 0 deletions(-)
 create mode 100644 src/providers/ipa/ipa_netgroups.c

diff --git a/Makefile.am b/Makefile.am
index 58c7928254d6faab3404d1e799a78085e913567e..d62c1b98967bbd9bfd6ddfd48000984c954de030 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -912,6 +912,7 @@ libsss_ipa_la_SOURCES = \
     src/providers/ipa/ipa_init.c \
     src/providers/ipa/ipa_common.c \
     src/providers/ipa/ipa_utils.c \
+    src/providers/ipa/ipa_netgroups.c \
     src/providers/ipa/ipa_auth.c \
     src/providers/ipa/ipa_access.c \
     src/providers/ipa/ipa_dyndns.c \
diff --git a/src/providers/ipa/ipa_netgroups.c b/src/providers/ipa/ipa_netgroups.c
new file mode 100644
index 0000000000000000000000000000000000000000..ddebe61bdc115c8f264882245e59c773a05f2bb6
--- /dev/null
+++ b/src/providers/ipa/ipa_netgroups.c
@@ -0,0 +1,992 @@
+/*
+    SSSD
+
+    Async IPA Helper routines for netgroups
+
+    Authors:
+        Jan Zeleny <jzeleny@redhat.com>
+
+    Copyright (C) 2011 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 "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/sdap_async_private.h"
+#include "providers/ipa/ipa_id.h"
+#include "db/sysdb.h"
+#include <ctype.h>
+
+#define ENTITY_NG 1
+#define ENTITY_USER 2
+#define ENTITY_HOST 4
+
+struct ipa_get_netgroups_state {
+    struct tevent_context *ev;
+    struct sdap_options *opts;
+    struct ipa_options *ipa_opts;
+    struct sdap_handle *sh;
+    struct sysdb_ctx *sysdb;
+    const char **attrs;
+    int timeout;
+
+    char *filter;
+    const char *base_filter;
+
+    size_t netgr_base_iter;
+    size_t host_base_iter;
+    size_t user_base_iter;
+
+    /* Entities which have been already asked for
+     * and are scheduled for inspection */
+    hash_table_t *new_netgroups;
+    hash_table_t *new_users;
+    hash_table_t *new_hosts;
+
+    int current_entity;
+    int entities_found;
+
+    struct sysdb_attrs **netgroups;
+    int netgroups_count;
+};
+
+static errno_t ipa_save_netgroup(TALLOC_CTX *mem_ctx,
+                                 struct sysdb_ctx *ctx,
+                                 struct sdap_options *opts,
+                                 struct sysdb_attrs *attrs)
+{
+    struct ldb_message_element *el;
+    struct sysdb_attrs *netgroup_attrs;
+    const char *name = NULL;
+    int ret;
+    size_t c;
+
+    ret = sysdb_attrs_get_el(attrs,
+                             opts->netgroup_map[IPA_AT_NETGROUP_NAME].sys_name,
+                             &el);
+    if (ret) goto fail;
+    if (el->num_values == 0) {
+        ret = EINVAL;
+        goto fail;
+    }
+    name = (const char *)el->values[0].data;
+    DEBUG(SSSDBG_TRACE_INTERNAL, ("Storing netgroup %s\n", name));
+
+    netgroup_attrs = sysdb_new_attrs(mem_ctx);
+    if (!netgroup_attrs) {
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &el);
+    if (ret) {
+        goto fail;
+    }
+    if (el->num_values == 0) {
+        DEBUG(7, ("Original DN is not available for [%s].\n", name));
+    } else {
+        DEBUG(7, ("Adding original DN [%s] to attributes of [%s].\n",
+                  el->values[0].data, name));
+        ret = sysdb_attrs_add_string(netgroup_attrs, SYSDB_ORIG_DN,
+                                     (const char *)el->values[0].data);
+        if (ret) {
+            goto fail;
+        }
+    }
+
+    ret = sysdb_attrs_get_el(attrs, SYSDB_NETGROUP_TRIPLE, &el);
+    if (ret) {
+        goto fail;
+    }
+    if (el->num_values == 0) {
+        DEBUG(7, ("No netgroup triples for netgroup [%s].\n", name));
+    } else {
+        for(c = 0; c < el->num_values; c++) {
+            ret = sysdb_attrs_add_string(netgroup_attrs,
+                                         SYSDB_NETGROUP_TRIPLE,
+                                         (const char*)el->values[c].data);
+            if (ret) {
+                goto fail;
+            }
+        }
+    }
+
+    ret = sysdb_attrs_get_el(attrs,
+                       opts->netgroup_map[IPA_AT_NETGROUP_MEMBER].sys_name,
+                       &el);
+    if (ret != EOK) {
+        goto fail;
+    }
+    if (el->num_values == 0) {
+        DEBUG(7, ("No original members for netgroup [%s]\n", name));
+
+    } else {
+        DEBUG(7, ("Adding original members to netgroup [%s]\n", name));
+        for(c = 0; c < el->num_values; c++) {
+            ret = sysdb_attrs_add_string(netgroup_attrs,
+                       opts->netgroup_map[IPA_AT_NETGROUP_MEMBER].sys_name,
+                       (const char*)el->values[c].data);
+            if (ret) {
+                goto fail;
+            }
+        }
+    }
+
+
+    ret = sysdb_attrs_get_el(attrs, SYSDB_NETGROUP_MEMBER, &el);
+    if (ret != EOK) {
+        goto fail;
+    }
+    if (el->num_values == 0) {
+        DEBUG(7, ("No members for netgroup [%s]\n", name));
+
+    } else {
+        DEBUG(7, ("Adding members to netgroup [%s]\n", name));
+        for(c = 0; c < el->num_values; c++) {
+            ret = sysdb_attrs_add_string(netgroup_attrs, SYSDB_NETGROUP_MEMBER,
+                                         (const char*)el->values[c].data);
+            if (ret) {
+                goto fail;
+            }
+        }
+    }
+
+    DEBUG(6, ("Storing info for netgroup %s\n", name));
+
+    ret = sysdb_add_netgroup(ctx, name, NULL, netgroup_attrs,
+                             dp_opt_get_int(opts->basic,
+                                            SDAP_ENTRY_CACHE_TIMEOUT),
+                             0);
+    if (ret) goto fail;
+
+    return EOK;
+
+fail:
+    DEBUG(2, ("Failed to save netgroup %s\n", name));
+    return ret;
+}
+
+static errno_t ipa_netgr_next_base(struct tevent_req *req);
+static void ipa_get_netgroups_process(struct tevent_req *subreq);
+static int ipa_netgr_process_all(struct ipa_get_netgroups_state *state);
+
+struct tevent_req *ipa_get_netgroups_send(TALLOC_CTX *memctx,
+                                          struct tevent_context *ev,
+                                          struct sysdb_ctx *sysdb,
+                                          struct sdap_options *opts,
+                                          struct ipa_options *ipa_options,
+                                          struct sdap_handle *sh,
+                                          const char **attrs,
+                                          const char *filter,
+                                          int timeout)
+{
+    struct tevent_req *req;
+    struct ipa_get_netgroups_state *state;
+    int ret;
+
+    req = tevent_req_create(memctx, &state, struct ipa_get_netgroups_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->opts = opts;
+    state->ipa_opts = ipa_options;
+    state->sh = sh;
+    state->sysdb = sysdb;
+    state->attrs = attrs;
+    state->timeout = timeout;
+    state->base_filter = filter;
+    state->netgr_base_iter = 0;
+
+    ret = sss_hash_create(state, 32, &state->new_netgroups);
+    if (ret != EOK) goto done;
+    ret = sss_hash_create(state, 32, &state->new_users);
+    if (ret != EOK) goto done;
+    ret = sss_hash_create(state, 32, &state->new_hosts);
+    if (ret != EOK) goto done;
+
+
+    ret = ipa_netgr_next_base(req);
+
+done:
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        tevent_req_post(req, ev);
+    }
+
+    return req;
+}
+
+static errno_t ipa_netgr_next_base(struct tevent_req *req)
+{
+    struct tevent_req *subreq;
+    struct ipa_get_netgroups_state *state;
+    struct sdap_search_base **netgr_bases;
+
+    state = tevent_req_data(req, struct ipa_get_netgroups_state);
+    netgr_bases = state->ipa_opts->id->netgroup_search_bases;
+
+    talloc_zfree(state->filter);
+    state->filter = sdap_get_id_specific_filter(
+            state,
+            state->base_filter,
+            netgr_bases[state->netgr_base_iter]->filter);
+    if (!state->filter) {
+        return ENOMEM;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+            ("Searching for netgroups with base [%s]\n",
+             netgr_bases[state->netgr_base_iter]->basedn));
+
+    subreq = sdap_get_generic_send(
+            state, state->ev, state->opts, state->sh,
+            netgr_bases[state->netgr_base_iter]->basedn,
+            netgr_bases[state->netgr_base_iter]->scope,
+            state->filter, state->attrs,
+            state->opts->netgroup_map, IPA_OPTS_NETGROUP,
+            state->timeout);
+    if (!subreq) {
+        return ENOMEM;
+    }
+    tevent_req_set_callback(subreq, ipa_get_netgroups_process, req);
+
+    return EOK;
+}
+
+static int ipa_netgr_fetch_netgroups(struct ipa_get_netgroups_state *state,
+                                     struct tevent_req *req);
+static int ipa_netgr_fetch_users(struct ipa_get_netgroups_state *state,
+                                 struct tevent_req *req);
+static int ipa_netgr_fetch_hosts(struct ipa_get_netgroups_state *state,
+                                 struct tevent_req *req);
+static void ipa_netgr_members_process(struct tevent_req *subreq);
+
+static void ipa_get_netgroups_process(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ipa_get_netgroups_state *state = tevent_req_data(req,
+                                               struct ipa_get_netgroups_state);
+    int i, ret;
+    struct ldb_message_element *ng_found;
+    struct ldb_message_element *host_found;
+    struct ldb_message_element *user_found;
+    struct sdap_search_base **netgr_bases;
+    struct sysdb_attrs **netgroups;
+    size_t netgroups_count;
+    const char *orig_dn;
+    char *dn;
+    char *filter;
+    bool fetch_members = false;
+    hash_key_t key;
+    hash_value_t value;
+
+    netgr_bases = state->ipa_opts->id->netgroup_search_bases;
+
+    ret = sdap_get_generic_recv(subreq, state, &netgroups_count, &netgroups);
+    talloc_zfree(subreq);
+    if (ret) {
+        goto done;
+    }
+
+    DEBUG(6, ("Search for netgroups, returned %d results.\n", netgroups_count));
+
+    if (netgroups_count == 0) {
+        /* No netgroups found in this search */
+        state->netgr_base_iter++;
+        if (netgr_bases[state->netgr_base_iter]) {
+            /* There are more search bases to try */
+            ret = ipa_netgr_next_base(req);
+            if (ret != EOK) {
+                tevent_req_error(req, ENOENT);
+            }
+            return;
+        }
+
+        ret = ENOENT;
+        goto done;
+    }
+
+    filter = talloc_strdup(state, "(|");
+    if (filter == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; i < netgroups_count; i++) {
+        ret = sysdb_attrs_get_el(netgroups[i], SYSDB_ORIG_NETGROUP_MEMBER,
+                                 &ng_found);
+        if (ret != EOK) goto done;
+
+        ret = sysdb_attrs_get_el(netgroups[i], SYSDB_ORIG_NETGROUP_MEMBER_USER,
+                                 &user_found);
+        if (ret != EOK) goto done;
+
+        ret = sysdb_attrs_get_el(netgroups[i], SYSDB_ORIG_NETGROUP_MEMBER_HOST,
+                                 &host_found);
+        if (ret != EOK) goto done;
+
+        ret = sysdb_attrs_get_string(netgroups[i], SYSDB_ORIG_DN, &orig_dn);
+        if (ret != EOK) {
+            goto done;
+        }
+
+        key.type = HASH_KEY_STRING;
+        value.type = HASH_VALUE_PTR;
+        key.str = discard_const(orig_dn);
+        value.ptr = netgroups[i];
+        ret = hash_enter(state->new_netgroups, &key, &value);
+        if (ret != HASH_SUCCESS) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        if (ng_found->num_values) state->entities_found |= ENTITY_NG;
+        if (user_found->num_values) state->entities_found |= ENTITY_USER;
+        if (host_found->num_values) state->entities_found |= ENTITY_HOST;
+
+        if (state->entities_found == 0) {
+            continue;
+        }
+
+        ret = sss_filter_sanitize(state, orig_dn, &dn);
+        if (ret != EOK) {
+            goto done;
+        }
+        /* Add this to the filter */
+        filter = talloc_asprintf_append(filter, "(%s=%s)",
+                            state->opts->netgroup_map[IPA_AT_NETGROUP_MEMBER_OF].name,
+                            dn);
+        if (filter == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+        fetch_members = true;
+    }
+
+    if (!fetch_members) {
+        ret = ipa_netgr_process_all(state);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+        } else {
+            tevent_req_done(req);
+        }
+        return;
+    }
+
+    state->filter = talloc_asprintf_append(filter, ")");
+    if (filter == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (state->entities_found & ENTITY_NG) {
+        state->netgr_base_iter = 0;
+        ret = ipa_netgr_fetch_netgroups(state, req);
+        if (ret != EOK) goto done;
+    } else if (state->entities_found & ENTITY_USER) {
+        ret = ipa_netgr_fetch_users(state, req);
+        if (ret != EOK) goto done;
+    } else if (state->entities_found & ENTITY_HOST) {
+        ret = ipa_netgr_fetch_hosts(state, req);
+        if (ret != EOK) goto done;
+    }
+
+    return;
+done:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static int ipa_netgr_fetch_netgroups(struct ipa_get_netgroups_state *state,
+                                     struct tevent_req *req)
+{
+    char *filter;
+    const char *base_filter;
+    struct tevent_req *subreq;
+    struct sdap_search_base **bases;
+
+    bases = state->ipa_opts->id->netgroup_search_bases;
+    if (bases[state->netgr_base_iter] == NULL) {
+        /* No more bases to try */
+        return ENOENT;
+    }
+    base_filter = bases[state->netgr_base_iter]->filter;
+
+    filter = talloc_asprintf(state, "(&%s%s(objectclass=%s))",
+                             state->filter,
+                             base_filter?base_filter:"",
+                             state->opts->netgroup_map[SDAP_OC_NETGROUP].name);
+    if (filter == NULL)
+        return ENOMEM;
+
+    subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+                                   bases[state->netgr_base_iter]->basedn,
+                                   bases[state->netgr_base_iter]->scope,
+                                   filter, state->attrs, state->opts->netgroup_map,
+                                   IPA_OPTS_NETGROUP, state->timeout);
+
+    state->current_entity = ENTITY_NG;
+    if (subreq == NULL) {
+        return ENOMEM;
+    }
+
+    tevent_req_set_callback(subreq, ipa_netgr_members_process, req);
+
+    return EOK;
+}
+
+static int ipa_netgr_fetch_users(struct ipa_get_netgroups_state *state,
+                                 struct tevent_req *req)
+{
+    const char *attrs[] = { state->opts->user_map[SDAP_AT_USER_NAME].name,
+                            state->opts->user_map[SDAP_AT_USER_MEMBEROF].name,
+                            "objectclass", NULL };
+    char *filter;
+    const char *base_filter;
+    struct tevent_req *subreq;
+    struct sdap_search_base **bases;
+
+    bases = state->ipa_opts->id->user_search_bases;
+    if (bases[state->user_base_iter] == NULL) {
+        return ENOENT;
+    }
+    base_filter = bases[state->user_base_iter]->filter;
+
+    filter = talloc_asprintf(state, "(&%s%s(objectclass=%s))",
+                             state->filter,
+                             base_filter?base_filter:"",
+                             state->opts->user_map[SDAP_OC_USER].name);
+    if (filter == NULL)
+        return ENOMEM;
+
+    subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+                                   dp_opt_get_string(state->opts->basic,
+                                                     SDAP_USER_SEARCH_BASE),
+                                   LDAP_SCOPE_SUBTREE,
+                                   filter, attrs,
+                                   state->opts->user_map,
+                                   SDAP_OPTS_USER, state->timeout);
+
+    state->current_entity = ENTITY_USER;
+    if (subreq == NULL) {
+        talloc_free(attrs);
+        return ENOMEM;
+    }
+
+    tevent_req_set_callback(subreq, ipa_netgr_members_process, req);
+
+    return EOK;
+}
+
+static int ipa_netgr_fetch_hosts(struct ipa_get_netgroups_state *state,
+                                 struct tevent_req *req)
+{
+    const char **attrs;
+    char *filter;
+    const char *base_filter;
+    struct tevent_req *subreq;
+    int ret;
+    struct sdap_search_base **bases;
+
+    bases = state->ipa_opts->host_search_bases;
+    if (bases[state->host_base_iter] == NULL) {
+        return ENOENT;
+    }
+    base_filter = bases[state->host_base_iter]->filter;
+
+    filter = talloc_asprintf(state, "(&%s%s(objectclass=%s))",
+                             state->filter,
+                             base_filter?base_filter:"",
+                             state->opts->host_map[IPA_OC_HOST].name);
+    if (filter == NULL)
+        return ENOMEM;
+
+    ret = build_attrs_from_map(state, state->opts->host_map,
+                               IPA_OPTS_HOST, &attrs);
+    if (ret != EOK) {
+        talloc_free(filter);
+        return ret;
+    }
+
+    subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+                                   bases[state->host_base_iter]->basedn,
+                                   bases[state->host_base_iter]->scope,
+                                   filter, attrs,
+                                   state->opts->host_map,
+                                   IPA_OPTS_HOST, state->timeout);
+
+    state->current_entity = ENTITY_HOST;
+    if (subreq == NULL) {
+        talloc_free(filter);
+        return ENOMEM;
+    }
+
+    tevent_req_set_callback(subreq, ipa_netgr_members_process, req);
+
+    return EOK;
+}
+
+static void ipa_netgr_members_process(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ipa_get_netgroups_state *state = tevent_req_data(req,
+                                               struct ipa_get_netgroups_state);
+    struct sysdb_attrs **entities;
+    size_t count;
+    int ret, i;
+    const char *orig_dn;
+    char *orig_dn_lower;
+    hash_table_t *table;
+    hash_key_t key;
+    hash_value_t value;
+    int (* next_call)(struct ipa_get_netgroups_state *,
+                      struct tevent_req *);
+    bool next_batch_scheduled = false;
+
+    ret = sdap_get_generic_recv(subreq, state, &count, &entities);
+    talloc_zfree(subreq);
+    if (ret) {
+        goto fail;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, ("Found %u members in current search base\n", count));
+
+    next_call = NULL;
+    /* While processing a batch of entities from one search base,
+     * schedule query for another search base if there is one
+     *
+     * If there is no other search base, another class of entities
+     * will be scheduled for lookup after processing of current
+     * batch. The order of lookup is: netgroups -> users -> hosts
+     */
+    if (state->current_entity == ENTITY_NG) {
+        /* We just received a batch of netgroups */
+        state->netgr_base_iter++;
+        ret = ipa_netgr_fetch_netgroups(state, req);
+        table = state->new_netgroups;
+        /* If there is a member netgroup, we always have to
+         * ask for both member users and hosts
+         * -> now schedule users
+         */
+        next_call = ipa_netgr_fetch_users;
+    } else if (state->current_entity == ENTITY_USER) {
+        /* We just received a batch of users */
+        state->user_base_iter++;
+        ret = ipa_netgr_fetch_users(state, req);
+        table = state->new_users;
+        if (state->entities_found & ENTITY_HOST ||
+            state->entities_found & ENTITY_NG) {
+            next_call = ipa_netgr_fetch_hosts;
+        }
+    } else if (state->current_entity == ENTITY_HOST) {
+        /* We just received a batch of hosts */
+        state->host_base_iter++;
+        ret = ipa_netgr_fetch_hosts(state, req);
+        table = state->new_hosts;
+    }
+
+    if (ret == EOK) {
+        /* Next search base has been scheduled for inspection,
+         * don't try to look for other type of entities
+         */
+        next_batch_scheduled = true;
+    } else if (ret != ENOENT) {
+        goto fail;
+    }
+
+    /* Process all member entites and store them in the designated hash table */
+    key.type = HASH_KEY_STRING;
+    value.type = HASH_VALUE_PTR;
+    for (i = 0; i < count; i++) {
+        ret = sysdb_attrs_get_string(entities[i], SYSDB_ORIG_DN, &orig_dn);
+        if (ret != EOK) {
+            goto fail;
+        }
+
+        orig_dn_lower = talloc_strdup(table, orig_dn);
+        if (orig_dn_lower == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+        /* Transform the DN to lower case.
+         * this is important, as the member/memberof attributes
+         * have the value also in lower-case
+         */
+        key.str = orig_dn_lower;
+        while (*orig_dn_lower != '\0') {
+            *orig_dn_lower = tolower(*orig_dn_lower);
+            orig_dn_lower++;
+        }
+        value.ptr = entities[i];
+        ret = hash_enter(table, &key, &value);
+        if (ret !=  HASH_SUCCESS) {
+            goto fail;
+        }
+    }
+
+    if (next_batch_scheduled) {
+        /* The next search base is already scheduled to be searched */
+        return;
+    }
+
+    if (next_call) {
+        /* There is another class of members that has to be retrieved
+         * - schedule the lookup
+         */
+        ret = next_call(state, req);
+        if (ret != EOK) goto fail;
+    } else {
+        /* All members, that could have been fetched, were fetched */
+        ret = ipa_netgr_process_all(state);
+        if (ret != EOK) goto fail;
+
+        tevent_req_done(req);
+    }
+
+    return;
+
+fail:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static bool extract_netgroups(hash_entry_t *entry, void *pvt)
+{
+    struct ipa_get_netgroups_state *state;
+    state = talloc_get_type(pvt, struct ipa_get_netgroups_state);
+
+    state->netgroups[state->netgroups_count] = talloc_get_type(entry->value.ptr,
+                                                               struct sysdb_attrs);
+    state->netgroups_count++;
+
+    return true;
+}
+
+struct extract_state {
+    const char *group;
+
+    const char **entries;
+    int entries_count;
+};
+
+static bool extract_users(hash_entry_t *entry, void *pvt)
+{
+    int i, ret;
+    struct extract_state *state;
+    struct sysdb_attrs *member;
+    struct ldb_message_element *el;
+    struct ldb_message_element *name_el;
+
+    state = talloc_get_type(pvt, struct extract_state);
+    member = talloc_get_type(entry->value.ptr, struct sysdb_attrs);
+
+    ret = sysdb_attrs_get_el(member, SYSDB_MEMBEROF, &el);
+    if (ret != EOK) return false;
+
+    ret = sysdb_attrs_get_el(member, SYSDB_NAME, &name_el);
+    if (ret != EOK || name_el == NULL || name_el->num_values == 0) {
+        return false;
+    }
+
+    for (i = 0; i < el->num_values; i++) {
+        if (strcmp((char *)el->values[i].data, state->group) == 0) {
+
+            state->entries = talloc_realloc(state, state->entries, const char *,
+                                            state->entries_count + 1);
+            if (state->entries == NULL) {
+                return false;
+            }
+            state->entries[state->entries_count] = (char *)name_el->values[0].data;
+            state->entries_count++;
+            break;
+        }
+    }
+
+    return true;
+}
+
+static int extract_members(TALLOC_CTX *mem_ctx,
+                           struct sysdb_attrs *netgroup,
+                           const char *member_type,
+                           hash_table_t *lookup_table,
+                           const char ***_ret_array,
+                           int *_ret_count)
+{
+    struct extract_state *state;
+    struct ldb_message_element *el;
+    struct sysdb_attrs *member;
+    hash_key_t key;
+    hash_value_t value;
+    const char **process = NULL;
+    const char **ret_array = NULL;
+    int process_count = 0;
+    int ret_count = 0;
+    int ret, i;
+
+    key.type = HASH_KEY_STRING;
+    value.type = HASH_VALUE_PTR;
+
+    state = talloc_zero(mem_ctx, struct extract_state);
+    if (state == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = sysdb_attrs_get_el(netgroup, member_type, &el);
+    if (ret != EOK && ret != ENOENT) {
+        goto done;
+    }
+
+    if (ret == EOK) {
+        for (i = 0; i < el->num_values; i++) {
+            key.str = (char *)el->values[i].data;
+            ret = hash_lookup(lookup_table, &key, &value);
+            if (ret != HASH_SUCCESS && ret != HASH_ERROR_KEY_NOT_FOUND) {
+                ret = ENOENT;
+                goto done;
+            }
+
+            if (ret == HASH_ERROR_KEY_NOT_FOUND) {
+                process = talloc_realloc(mem_ctx, process, const char *, process_count + 1);
+                if (process == NULL) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+
+                process[process_count] = (char *)el->values[i].data;
+                process_count++;
+            } else {
+                ret_array = talloc_realloc(mem_ctx, ret_array, const char *, ret_count + 1);
+                if (ret_array == NULL) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+                member = talloc_get_type(value.ptr, struct sysdb_attrs);
+                ret = sysdb_attrs_get_string(member, SYSDB_NAME, &ret_array[ret_count]);
+                if (ret != EOK) {
+                    goto done;
+                }
+                ret_count++;
+            }
+
+            for (i = 0; i < process_count; i++) {
+                state->group = process[i];
+                hash_iterate(lookup_table, extract_users, state);
+                if (state->entries_count > 0) {
+                    ret_array = talloc_realloc(mem_ctx, ret_array, const char *,
+                            ret_count + state->entries_count);
+                    if (ret_array == NULL) {
+                        ret = ENOMEM;
+                        goto done;
+                    }
+                    memcpy(&ret_array[ret_count], state->entries,
+                            state->entries_count*sizeof(const char *));
+                    ret_count += state->entries_count;
+                }
+                state->entries_count = 0;
+                talloc_zfree(state->entries);
+            }
+        }
+    } else {
+        ret_array = NULL;
+    }
+
+    *_ret_array = ret_array;
+    *_ret_count = ret_count;
+    ret = EOK;
+
+done:
+    return ret;
+}
+
+static int ipa_netgr_process_all(struct ipa_get_netgroups_state *state)
+{
+    int i, j, k, ret;
+    const char **members;
+    struct sysdb_attrs *member;
+    const char *member_name;
+    struct extract_state *extract_state;
+    struct ldb_message_element *external_hosts;
+    const char *dash[] = {"-"};
+    const char **uids = NULL;
+    const char **hosts = NULL;
+    int uids_count = 0;
+    int hosts_count = 0;
+    hash_key_t key;
+    hash_value_t value;
+    const char *domain;
+    char *triple;
+
+    state->netgroups = talloc_zero_array(state, struct sysdb_attrs *,
+                                         hash_count(state->new_netgroups));
+    if (state->netgroups == NULL) {
+        return ENOMEM;
+    }
+
+    extract_state = talloc_zero(state, struct extract_state);
+    if (extract_state == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    key.type = HASH_KEY_STRING;
+    value.type = HASH_VALUE_PTR;
+
+    hash_iterate(state->new_netgroups, extract_netgroups, state);
+    for (i = 0; i < state->netgroups_count; i++) {
+        /* load all its member netgroups, translate */
+        DEBUG(SSSDBG_TRACE_INTERNAL, ("Extracting netgroup members of netgroup %d\n", i));
+        ret = sysdb_attrs_get_string_array(state->netgroups[i],
+                                           SYSDB_ORIG_NETGROUP_MEMBER,
+                                           state, &members);
+        if (ret != EOK && ret != ENOENT) {
+            goto done;
+        }
+
+        if (ret == EOK) {
+            for (j = 0; members[j]; j++) {
+                key.str = discard_const(members[j]);
+                ret = hash_lookup(state->new_netgroups, &key, &value);
+                if (ret != HASH_SUCCESS) {
+                    ret = ENOENT;
+                    goto done;
+                }
+
+                member = talloc_get_type(value.ptr, struct sysdb_attrs);
+                ret = sysdb_attrs_get_string(member, SYSDB_NAME, &member_name);
+                if (ret != EOK) {
+                    goto done;
+                }
+
+                ret = sysdb_attrs_add_string(state->netgroups[i],
+                                             SYSDB_NETGROUP_MEMBER,
+                                             member_name);
+                if (ret != EOK) {
+                    goto done;
+                }
+            }
+            talloc_zfree(members);
+        }
+
+        /* Load all UIDs */
+        DEBUG(SSSDBG_TRACE_INTERNAL, ("Extracting user members of netgroup %d\n", i));
+        ret = extract_members(state, state->netgroups[i],
+                              SYSDB_ORIG_NETGROUP_MEMBER_USER,
+                              state->new_users,
+                              &uids, &uids_count);
+        if (ret != EOK) {
+            goto done;
+        }
+
+        DEBUG(SSSDBG_TRACE_INTERNAL, ("Extracting host members of netgroup %d\n", i));
+        ret = extract_members(state, state->netgroups[i],
+                              SYSDB_ORIG_NETGROUP_MEMBER_HOST,
+                              state->new_hosts,
+                              &hosts, &hosts_count);
+        if (ret != EOK) {
+            goto done;
+        }
+
+        ret = sysdb_attrs_get_el(state->netgroups[i],
+                                 SYSDB_ORIG_NETGROUP_EXTERNAL_HOST,
+                                 &external_hosts);
+        if (ret != EOK) {
+            goto done;
+        }
+
+        if (external_hosts->num_values > 0) {
+            hosts = talloc_realloc(state, hosts, const char *,
+                                   hosts_count + external_hosts->num_values);
+            if (hosts == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+
+            for (j = 0; j < external_hosts->num_values; j++) {
+                hosts[hosts_count] = talloc_strdup(hosts, (char *)external_hosts->values[j].data);
+                if (hosts[hosts_count] == NULL) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+                hosts_count++;
+            }
+        }
+
+        ret = sysdb_attrs_get_string(state->netgroups[i], SYSDB_NETGROUP_DOMAIN,
+                                     &domain);
+        if (ret != EOK) {
+            goto done;
+        }
+
+        if (uids_count == 0) {
+            uids_count = 1;
+            uids = dash;
+        }
+
+        if (hosts_count == 0) {
+            hosts_count = 1;
+            hosts = dash;
+        }
+
+        DEBUG(SSSDBG_TRACE_INTERNAL, ("Putting together triples of netgroup %d\n", i));
+        for (j = 0; j < uids_count; j++) {
+            for (k = 0; k < hosts_count; k++) {
+                triple = talloc_asprintf(state, "(%s,%s,%s)",
+                                         hosts[k], uids[j],
+                                         domain);
+                if (triple == NULL) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+
+                ret = sysdb_attrs_add_string(state->netgroups[i], SYSDB_NETGROUP_TRIPLE, triple);
+                if (ret != EOK) {
+                    goto done;
+                }
+            }
+        }
+        ret = ipa_save_netgroup(state, state->sysdb, state->opts,
+                                state->netgroups[i]);
+        if (ret != EOK) {
+            goto done;
+        }
+    }
+
+done:
+    return ret;
+}
+
+int ipa_get_netgroups_recv(struct tevent_req *req,
+                           TALLOC_CTX *mem_ctx,
+                           size_t *reply_count,
+                           struct sysdb_attrs ***reply)
+{
+    struct ipa_get_netgroups_state *state = tevent_req_data(req,
+                                               struct ipa_get_netgroups_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (reply_count) {
+        *reply_count = state->netgroups_count;
+    }
+
+    if (reply) {
+        *reply = talloc_steal(mem_ctx, state->netgroups);
+    }
+
+    return EOK;
+}
-- 
1.7.6.4

