/*
    SSSD

    Async LDAP Helper routines for netgroups

    Authors:
        Sumit Bose <sbose@redhat.com>

    Copyright (C) 2010 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/ldap/ldap_common.h"

bool is_dn(const char *str)
{
    int ret;
    LDAPDN dn;

    ret = ldap_str2dn(str, &dn, LDAP_DN_FORMAT_LDAPV3);
    ldap_dnfree(dn);

    return (ret == LDAP_SUCCESS ? true : false);
}

static errno_t sdap_save_netgroup(TALLOC_CTX *memctx,
                                  struct sss_domain_info *dom,
                                  struct sdap_options *opts,
                                  struct sysdb_attrs *attrs,
                                  char **_timestamp,
                                  time_t now)
{
    struct ldb_message_element *el;
    struct sysdb_attrs *netgroup_attrs;
    const char *name = NULL;
    int ret;
    char *timestamp = NULL;
    char **missing = NULL;

    ret = sdap_get_netgroup_primary_name(memctx, opts, attrs, dom, &name);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to get netgroup name\n");
        goto fail;
    }

    DEBUG(SSSDBG_TRACE_FUNC, "Processing netgroup %s\n", name);

    netgroup_attrs = sysdb_new_attrs(memctx);
    if (!netgroup_attrs) {
        ret = ENOMEM;
        goto fail;
    }

    ret = sdap_attrs_add_string(attrs, SYSDB_ORIG_DN,
                                "original DN",
                                name, netgroup_attrs);
    if (ret != EOK) {
        goto fail;
    }

    ret = sysdb_attrs_get_el(attrs,
                         opts->netgroup_map[SDAP_AT_NETGROUP_MODSTAMP].sys_name,
                         &el);
    if (ret) {
        goto fail;
    }
    if (el->num_values == 0) {
        DEBUG(SSSDBG_TRACE_LIBS,
              "Original mod-Timestamp is not available for [%s].\n",
                  name);
    } else {
        ret = sysdb_attrs_add_string(netgroup_attrs,
                         opts->netgroup_map[SDAP_AT_NETGROUP_MODSTAMP].sys_name,
                         (const char*)el->values[0].data);
        if (ret) {
            goto fail;
        }
        timestamp = talloc_strdup(memctx, (const char*)el->values[0].data);
        if (!timestamp) {
            ret = ENOMEM;
            goto fail;
        }
    }

    ret = sdap_attrs_add_list(attrs,
                        opts->netgroup_map[SDAP_AT_NETGROUP_TRIPLE].sys_name,
                        "netgroup triple",
                        name, netgroup_attrs);
    if (ret != EOK) {
        goto fail;
    }

    ret = sdap_attrs_add_list(attrs,
                        opts->netgroup_map[SDAP_AT_NETGROUP_MEMBER].sys_name,
                        "original members",
                        name, netgroup_attrs);
    if (ret != EOK) {
        goto fail;
    }

    ret = sdap_attrs_add_list(attrs, SYSDB_NETGROUP_MEMBER,
                        "members", name, netgroup_attrs);
    if (ret != EOK) {
        goto fail;
    }

    DEBUG(SSSDBG_TRACE_FUNC, "Storing info for netgroup %s\n", name);

    ret = sdap_save_all_names(name, attrs, dom, SYSDB_MEMBER_NETGROUP,
                              netgroup_attrs);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to save netgroup names\n");
        goto fail;
    }

    /* Make sure that any attributes we requested from LDAP that we
     * did not receive are also removed from the sysdb
     */
    ret = list_missing_attrs(attrs, opts->netgroup_map, SDAP_OPTS_NETGROUP,
                             attrs, &missing);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to list missing attributes\n");
        goto fail;
    }

    /* We store memberNisNetgroup from LDAP as originalMemberNisNetgroup in
     * sysdb. It may contain simple name or DN. That's the reason why we always
     * translate/generate simple name and store it in SYSDB_NETGROUP_MEMBER
     * (memberNisNetgroup) in sysdb which is internally used for searching
     * netgropus.
     * We need to ensure if originalMemberNisNetgroup is missing,
     * memberNisNetgroup is missing too.
     */
    if (string_in_list(SYSDB_ORIG_NETGROUP_MEMBER, missing, false)) {
        ret = add_string_to_list(attrs, SYSDB_NETGROUP_MEMBER, &missing);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add string into list\n");
            goto fail;
        }
    }

    ret = sysdb_add_netgroup(dom, name, NULL, netgroup_attrs, missing,
                             dom->netgroup_timeout, now);
    if (ret) goto fail;

    if (_timestamp) {
        *_timestamp = timestamp;
    }

    return EOK;

fail:
    DEBUG(SSSDBG_OP_FAILURE, "Failed to save netgroup %s\n", name);
    return ret;
}

errno_t update_dn_list(struct dn_item *dn_list, const size_t count,
                       struct ldb_message **res, bool *all_resolved)
{
    struct dn_item *dn_item;
    size_t c;
    const char *dn;
    const char *cn;
    bool not_resolved = false;

    *all_resolved = false;

    DLIST_FOR_EACH(dn_item, dn_list) {
        if (dn_item->cn != NULL) {
            continue;
        }

        for(c = 0; c < count; c++) {
            dn = ldb_msg_find_attr_as_string(res[c], SYSDB_ORIG_DN, NULL);
            if (dn == NULL) {
                DEBUG(SSSDBG_CRIT_FAILURE, "Missing original DN.\n");
                return EINVAL;
            }
            if (strcmp(dn, dn_item->dn) == 0) {
                DEBUG(SSSDBG_TRACE_ALL,
                      "Found matching entry for [%s].\n", dn_item->dn);
                cn = ldb_msg_find_attr_as_string(res[c], SYSDB_NAME, NULL);
                if (cn == NULL) {
                    DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n");
                    return EINVAL;
                }
                dn_item->cn = talloc_strdup(dn_item, cn);
                break;
            }
        }

        if (dn_item->cn == NULL) {
            not_resolved = true;
        }
    }

    *all_resolved = !not_resolved;

    return EOK;
}

struct netgr_translate_members_state {
    struct tevent_context *ev;
    struct sdap_options *opts;
    struct sdap_handle *sh;

    struct sysdb_attrs **netgroups;
    size_t count;
    struct dn_item *dn_list;
    struct dn_item *dn_item;
    struct dn_item *dn_idx;
};

static errno_t netgr_translate_members_ldap_step(struct tevent_req *req);
static void netgr_translate_members_ldap_done(struct tevent_req *subreq);

struct tevent_req *netgr_translate_members_send(TALLOC_CTX *memctx,
                                                struct tevent_context *ev,
                                                struct sdap_options *opts,
                                                struct sdap_handle *sh,
                                                struct sss_domain_info *dom,
                                                struct sysdb_ctx *sysdb,
                                                const size_t count,
                                                struct sysdb_attrs **netgroups)
{
    struct tevent_req *req;
    struct netgr_translate_members_state *state;
    size_t c;
    size_t mc;
    const char **member_list;
    size_t sysdb_count;
    int ret;
    struct ldb_message **sysdb_res;
    struct dn_item *dn_item;
    char *dn_filter;
    char *sysdb_filter;
    struct ldb_dn *netgr_basedn;
    bool all_resolved;
    const char *cn_attr[] = { SYSDB_NAME, SYSDB_ORIG_DN, NULL };

    req = tevent_req_create(memctx, &state,
                            struct netgr_translate_members_state);
    if (req == NULL) {
        return NULL;
    }

    state->ev = ev;
    state->opts = opts;
    state->sh = sh;
    state->netgroups = netgroups;
    state->count = count;
    state->dn_list = NULL;
    state->dn_item = NULL;
    state->dn_idx = NULL;

    for (c = 0; c < count; c++) {
        ret = sysdb_attrs_get_string_array(netgroups[c],
                                           SYSDB_ORIG_NETGROUP_MEMBER, state,
                                           &member_list);
        if (ret != EOK) {
            DEBUG(SSSDBG_TRACE_LIBS, "Missing netgroup members.\n");
            continue;
        }

        for (mc = 0; member_list[mc] != NULL; mc++) {
            if (is_dn(member_list[mc])) {
                dn_item = talloc_zero(state, struct dn_item);
                if (dn_item == NULL) {
                    DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n");
                    ret = ENOMEM;
                    goto fail;
                }

                DEBUG(SSSDBG_TRACE_ALL,
                      "Adding [%s] to DN list.\n", member_list[mc]);
                dn_item->netgroup = netgroups[c];
                dn_item->dn = member_list[mc];
                DLIST_ADD(state->dn_list, dn_item);
            } else {
                ret = sysdb_attrs_add_string(netgroups[c], SYSDB_NETGROUP_MEMBER,
                                             member_list[mc]);
                if (ret != EOK) {
                    DEBUG(SSSDBG_CRIT_FAILURE,
                          "sysdb_attrs_add_string failed.\n");
                    goto fail;
                }
            }
        }
    }

    if (state->dn_list == NULL) {
        DEBUG(SSSDBG_TRACE_ALL, "No DNs found among netgroup members.\n");
        tevent_req_done(req);
        tevent_req_post(req, ev);
        return req;
    }

    dn_filter = talloc_strdup(state, "(|");
    if (dn_filter == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
        ret = ENOMEM;
        goto fail;
    }

    DLIST_FOR_EACH(dn_item, state->dn_list) {
            dn_filter = talloc_asprintf_append(dn_filter, "(%s=%s)",
                                               SYSDB_ORIG_DN, dn_item->dn);
            if (dn_filter == NULL) {
                DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n");
                ret = ENOMEM;
                goto fail;
            }
    }

    dn_filter = talloc_asprintf_append(dn_filter, ")");
    if (dn_filter == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n");
        ret = ENOMEM;
        goto fail;
    }

    sysdb_filter = talloc_asprintf(state, "(&(%s)%s)", SYSDB_NC, dn_filter);
    if (sysdb_filter == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
        ret = ENOMEM;
        goto fail;
    }

    netgr_basedn = sysdb_netgroup_base_dn(state, dom);
    if (netgr_basedn == NULL) {
        ret = ENOMEM;
        goto fail;
    }

    ret = sysdb_search_entry(state, sysdb, netgr_basedn, LDB_SCOPE_BASE,
                             sysdb_filter, cn_attr, &sysdb_count, &sysdb_res);
    talloc_zfree(netgr_basedn);
    talloc_zfree(sysdb_filter);
    if (ret != EOK && ret != ENOENT) {
        DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_search_entry failed.\n");
        goto fail;
    }

    if (ret == EOK) {
        ret = update_dn_list(state->dn_list, sysdb_count, sysdb_res,
                             &all_resolved);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE, "update_dn_list failed.\n");
            goto fail;
        }

        if (all_resolved) {
            DLIST_FOR_EACH(dn_item, state->dn_list) {
                    ret = sysdb_attrs_add_string(dn_item->netgroup,
                                                 SYSDB_NETGROUP_MEMBER,
                                                 dn_item->cn);
                    if (ret != EOK) {
                        DEBUG(SSSDBG_CRIT_FAILURE,
                              "sysdb_attrs_add_string failed.\n");
                        goto fail;
                    }
            }

            tevent_req_done(req);
            tevent_req_post(req, ev);
            return req;
        }
    }

    state->dn_idx = state->dn_list;
    ret = netgr_translate_members_ldap_step(req);
    if (ret != EOK && ret != EAGAIN) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "netgr_translate_members_ldap_step failed.\n");
        goto fail;
    }

    if (ret == EOK) {
        tevent_req_done(req);
        tevent_req_post(req, ev);
    }
    return req;

fail:
    tevent_req_error(req, ret);
    tevent_req_post(req, ev);
    return req;
}

/* netgr_translate_members_ldap_step() returns
 *   EOK: if everthing is translated, the caller can call tevent_req_done
 *   EAGAIN: if there are still members waiting to be translated, the caller
 *   should return to the mainloop
 *   Exyz: every other return code indicates an error and tevent_req_error
 *   should be called
 */
static errno_t netgr_translate_members_ldap_step(struct tevent_req *req)
{
    struct netgr_translate_members_state *state = tevent_req_data(req,
                                          struct netgr_translate_members_state);
    const char **cn_attr;
    char *filter = NULL;
    struct tevent_req *subreq;
    int ret;

    DLIST_FOR_EACH(state->dn_item, state->dn_idx) {
        if (state->dn_item->cn == NULL) {
            break;
        }
    }
    if (state->dn_item == NULL) {
        DLIST_FOR_EACH(state->dn_item, state->dn_list) {
                ret = sysdb_attrs_add_string(state->dn_item->netgroup,
                                             SYSDB_NETGROUP_MEMBER,
                                             state->dn_item->cn);
                if (ret != EOK) {
                    DEBUG(SSSDBG_CRIT_FAILURE,
                          "sysdb_attrs_add_string failed.\n");
                    tevent_req_error(req, ret);
                    return ret;
                }
        }

        return EOK;
    }

    if (!sss_ldap_dn_in_search_bases(state, state->dn_item->dn,
                                     state->opts->sdom->netgroup_search_bases,
                                     &filter)) {
        /* not in search base, skip it */
        state->dn_idx = state->dn_item->next;
        DLIST_REMOVE(state->dn_list, state->dn_item);
        return netgr_translate_members_ldap_step(req);
    }

    cn_attr = talloc_array(state, const char *, 3);
    if (cn_attr == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n");
        return ENOMEM;
    }
    cn_attr[0] = state->opts->netgroup_map[SDAP_AT_NETGROUP_NAME].name;
    cn_attr[1] = "objectclass";
    cn_attr[2] = NULL;

    DEBUG(SSSDBG_TRACE_ALL, "LDAP base search for [%s].\n", state->dn_item->dn);
    subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
                                   state->dn_item->dn, LDAP_SCOPE_BASE, filter,
                                   cn_attr, state->opts->netgroup_map,
                                   SDAP_OPTS_NETGROUP,
                                   dp_opt_get_int(state->opts->basic,
                                                  SDAP_SEARCH_TIMEOUT),
                                   false);
    if (!subreq) {
        DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n");
        return ENOMEM;
    }
    talloc_steal(subreq, cn_attr);

    tevent_req_set_callback(subreq, netgr_translate_members_ldap_done, req);
    return EAGAIN;
}

static void netgr_translate_members_ldap_done(struct tevent_req *subreq)
{
    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    struct netgr_translate_members_state *state = tevent_req_data(req,
                                          struct netgr_translate_members_state);
    int ret;
    size_t count;
    struct sysdb_attrs **netgroups;
    const char *str;

    ret = sdap_get_generic_recv(subreq, state, &count, &netgroups);
    talloc_zfree(subreq);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic request failed.\n");
        goto fail;
    }

    switch (count) {
        case 0:
            DEBUG(SSSDBG_FATAL_FAILURE,
                  "sdap_get_generic_recv found no entry for [%s].\n",
                      state->dn_item->dn);
            break;
        case 1:
            ret = sysdb_attrs_get_string(netgroups[0], SYSDB_NAME, &str);
            if (ret != EOK) {
                DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_add_string failed.\n");
                break;
            }
            state->dn_item->cn = talloc_strdup(state->dn_item, str);
            if (state->dn_item->cn == NULL) {
                DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
            }
            break;
        default:
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "Unexpected number of results [%zu] for base search.\n",
                   count);
    }

    if (state->dn_item->cn == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Failed to resolve netgroup name for DN [%s], using DN.\n",
                  state->dn_item->dn);
        state->dn_item->cn = talloc_strdup(state->dn_item, state->dn_item->dn);
    }

    state->dn_idx = state->dn_item->next;
    ret = netgr_translate_members_ldap_step(req);
    if (ret != EOK && ret != EAGAIN) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "netgr_translate_members_ldap_step failed.\n");
        goto fail;
    }

    if (ret == EOK) {
        tevent_req_done(req);
    }
    return;

fail:
    tevent_req_error(req, ret);
    return;
}

static errno_t netgroup_translate_ldap_members_recv(struct tevent_req *req,
                                                TALLOC_CTX *mem_ctx,
                                                size_t *count,
                                                struct sysdb_attrs ***netgroups)
{
    struct netgr_translate_members_state *state = tevent_req_data(req,
                                          struct netgr_translate_members_state);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    *count = state->count;
    *netgroups = talloc_steal(mem_ctx, state->netgroups);

    return EOK;
}

/* ==Search-Netgroups-with-filter============================================ */

struct sdap_get_netgroups_state {
    struct tevent_context *ev;
    struct sdap_options *opts;
    struct sdap_handle *sh;
    struct sss_domain_info *dom;
    struct sysdb_ctx *sysdb;
    const char **attrs;
    const char *base_filter;
    char *filter;
    int timeout;

    char *higher_timestamp;
    struct sysdb_attrs **netgroups;
    size_t count;

    size_t base_iter;
    struct sdap_search_base **search_bases;
};

static errno_t sdap_get_netgroups_next_base(struct tevent_req *req);
static void sdap_get_netgroups_process(struct tevent_req *subreq);
static void netgr_translate_members_done(struct tevent_req *subreq);

struct tevent_req *sdap_get_netgroups_send(TALLOC_CTX *memctx,
                                           struct tevent_context *ev,
                                           struct sss_domain_info *dom,
                                           struct sysdb_ctx *sysdb,
                                           struct sdap_options *opts,
                                           struct sdap_search_base **search_bases,
                                           struct sdap_handle *sh,
                                           const char **attrs,
                                           const char *filter,
                                           int timeout)
{
    errno_t ret;
    struct tevent_req *req;
    struct sdap_get_netgroups_state *state;

    req = tevent_req_create(memctx, &state, struct sdap_get_netgroups_state);
    if (!req) return NULL;

    state->ev = ev;
    state->opts = opts;
    state->dom = dom;
    state->sh = sh;
    state->sysdb = sysdb;
    state->attrs = attrs;
    state->higher_timestamp = NULL;
    state->netgroups =  NULL;
    state->count = 0;
    state->timeout = timeout;
    state->base_filter = filter;
    state->base_iter = 0;
    state->search_bases = search_bases;

    if (!state->search_bases) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Netgroup lookup request without a netgroup search base\n");
        ret = EINVAL;
        goto done;
    }


    ret = sdap_get_netgroups_next_base(req);

done:
    if (ret != EOK) {
        tevent_req_error(req, ret);
        tevent_req_post(req, state->ev);
    }
    return req;
}

static errno_t sdap_get_netgroups_next_base(struct tevent_req *req)
{
    struct tevent_req *subreq;
    struct sdap_get_netgroups_state *state;

    state = tevent_req_data(req, struct sdap_get_netgroups_state);

    talloc_zfree(state->filter);
    state->filter = sdap_combine_filters(state, state->base_filter,
                        state->search_bases[state->base_iter]->filter);
    if (!state->filter) {
        return ENOMEM;
    }

    DEBUG(SSSDBG_TRACE_FUNC,
          "Searching for netgroups 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->netgroup_map, SDAP_OPTS_NETGROUP,
            state->timeout,
            false);
    if (!subreq) {
        return ENOMEM;
    }
    tevent_req_set_callback(subreq, sdap_get_netgroups_process, req);

    return EOK;
}

static void sdap_get_netgroups_process(struct tevent_req *subreq)
{
    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    struct sdap_get_netgroups_state *state = tevent_req_data(req,
                                               struct sdap_get_netgroups_state);
    int ret;

    ret = sdap_get_generic_recv(subreq, state,
                                &state->count, &state->netgroups);
    talloc_zfree(subreq);
    if (ret) {
        tevent_req_error(req, ret);
        return;
    }

    DEBUG(SSSDBG_TRACE_FUNC,
          "Search for netgroups, returned %zu results.\n", state->count);

    if (state->count == 0) {
        /* No netgroups found in this search */
        state->base_iter++;
        if (state->search_bases[state->base_iter]) {
            /* There are more search bases to try */
            ret = sdap_get_netgroups_next_base(req);
            if (ret != EOK) {
                tevent_req_error(req, ENOENT);
            }
            return;
        }

        tevent_req_error(req, ENOENT);
        return;
    }

    subreq = netgr_translate_members_send(state, state->ev, state->opts,
                                          state->sh, state->dom, state->sysdb,
                                          state->count, state->netgroups);
    if (!subreq) {
        tevent_req_error(req, ENOMEM);
        return;
    }
    tevent_req_set_callback(subreq, netgr_translate_members_done, req);

    return;

}

static void netgr_translate_members_done(struct tevent_req *subreq)
{
    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    struct sdap_get_netgroups_state *state = tevent_req_data(req,
                                               struct sdap_get_netgroups_state);
    int ret;
    size_t c;
    time_t now;

    ret = netgroup_translate_ldap_members_recv(subreq, state, &state->count,
                                               &state->netgroups);
    talloc_zfree(subreq);
    if (ret) {
        tevent_req_error(req, ret);
        return;
    }

    now = time(NULL);
    for (c = 0; c < state->count; c++) {
        ret = sdap_save_netgroup(state,
                                 state->dom,
                                 state->opts,
                                 state->netgroups[c],
                                 &state->higher_timestamp,
                                 now);
        if (ret) {
            DEBUG(SSSDBG_OP_FAILURE, "Failed to store netgroups.\n");
            tevent_req_error(req, ret);
            return;
        }
    }

    DEBUG(SSSDBG_TRACE_ALL, "Saving %zu Netgroups - Done\n", state->count);

    tevent_req_done(req);
}

int sdap_get_netgroups_recv(struct tevent_req *req,
                            TALLOC_CTX *mem_ctx, char **timestamp,
                            size_t *reply_count,
                            struct sysdb_attrs ***reply)
{
    struct sdap_get_netgroups_state *state = tevent_req_data(req,
                                               struct sdap_get_netgroups_state);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    if (timestamp) {
        *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
    }

    if (reply_count) {
        *reply_count = state->count;
    }

    if (reply) {
        *reply = talloc_steal(mem_ctx, state->netgroups);
    }

    return EOK;
}
