From 45bb9b29aec341a43581888ae140c638c51489bf Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Wed, 22 Sep 2010 16:47:06 -0400
Subject: [PATCH 10/10] Add netgroup support to the NSS responder

---
 Makefile.am                         |    3 +
 src/providers/data_provider.h       |    1 +
 src/responder/common/responder.h    |    4 +
 src/responder/common/responder_dp.c |    3 +
 src/responder/nss/nsssrv.c          |    8 +
 src/responder/nss/nsssrv.h          |    1 +
 src/responder/nss/nsssrv_cmd.c      |   11 +-
 src/responder/nss/nsssrv_netgroup.c |  863 +++++++++++++++++++++++++++++++++++
 src/responder/nss/nsssrv_netgroup.h |   34 ++
 9 files changed, 926 insertions(+), 2 deletions(-)
 create mode 100644 src/responder/nss/nsssrv_netgroup.c
 create mode 100644 src/responder/nss/nsssrv_netgroup.h

diff --git a/Makefile.am b/Makefile.am
index 7c8c847672f2eba6ced39bcd231009ada97fecd1..63d59137af7de32ec117e32482a3bcdba3875ebb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -318,6 +318,8 @@ dist_noinst_HEADERS = \
     src/responder/common/responder_packet.h \
     src/responder/pam/pamsrv.h \
     src/responder/nss/nsssrv.h \
+    src/responder/nss/nsssrv_private.h \
+    src/responder/nss/nsssrv_netgroup.h \
     src/responder/common/negcache.h \
     src/sbus/sbus_client.h \
     src/sbus/sssd_dbus.h \
@@ -376,6 +378,7 @@ sssd_LDADD = \
 sssd_nss_SOURCES = \
     src/responder/nss/nsssrv.c \
     src/responder/nss/nsssrv_cmd.c \
+    src/responder/nss/nsssrv_netgroup.c \
     $(SSSD_UTIL_OBJ) \
     $(SSSD_RESPONDER_OBJ)
 sssd_nss_LDADD = \
diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h
index 7f424b4e076f90d806712b17fcfeff67e9d36a9c..062c36e3449923b37703a10b91020f8747d80d8e 100644
--- a/src/providers/data_provider.h
+++ b/src/providers/data_provider.h
@@ -145,6 +145,7 @@
 #define BE_REQ_USER 0x0001
 #define BE_REQ_GROUP 0x0002
 #define BE_REQ_INITGROUPS 0x0003
+#define BE_REQ_NETGROUP 0x0004
 #define BE_REQ_FAST 0x1000
 
 /* AUTH related common data and functions */
diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h
index 0f59ffd4321fb8a7312d4d9d63290afa7bf76e69..980e5618939df92e55eb7dedcd7412769fdb6a0b 100644
--- a/src/responder/common/responder.h
+++ b/src/responder/common/responder.h
@@ -115,6 +115,9 @@ struct cli_ctx {
 
     int grent_dom_idx;
     int grent_cur;
+
+    char *netgr_name;
+    int netgrent_cur;
 };
 
 struct sss_cmd_table {
@@ -153,6 +156,7 @@ struct cli_protocol_version *register_cli_protocol_version(void);
 #define SSS_DP_USER 1
 #define SSS_DP_GROUP 2
 #define SSS_DP_INITGROUPS 3
+#define SSS_DP_NETGR 4
 
 typedef void (*sss_dp_callback_t)(uint16_t err_maj, uint32_t err_min,
                                   const char *err_msg, void *ptr);
diff --git a/src/responder/common/responder_dp.c b/src/responder/common/responder_dp.c
index a5c1c510060f88ebb6c6d50f32decc4a6ed3f0be..fd7b9761d0f80c04ca926da38d12cea1978c8763 100644
--- a/src/responder/common/responder_dp.c
+++ b/src/responder/common/responder_dp.c
@@ -275,6 +275,9 @@ int sss_dp_send_acct_req(struct resp_ctx *rctx, TALLOC_CTX *callback_memctx,
     case SSS_DP_INITGROUPS:
         be_type = BE_REQ_INITGROUPS;
         break;
+    case SSS_DP_NETGR:
+        be_type = BE_REQ_NETGROUP;
+        break;
     default:
         return EINVAL;
     }
diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c
index f14d698f2195b70021bf831296e5ce04c3746ce4..4f5aa62932da3d6b5493c5580ab78bf2f9e73cef 100644
--- a/src/responder/nss/nsssrv.c
+++ b/src/responder/nss/nsssrv.c
@@ -162,6 +162,7 @@ int nss_process_init(TALLOC_CTX *mem_ctx,
     struct be_conn *iter;
     struct nss_ctx *nctx;
     int ret, max_retries;
+    int hret;
 
     nctx = talloc_zero(mem_ctx, struct nss_ctx);
     if (!nctx) {
@@ -212,6 +213,13 @@ int nss_process_init(TALLOC_CTX *mem_ctx,
                             nss_dp_reconnect_init, iter);
     }
 
+    /* Create the lookup table for netgroup results */
+    hret = hash_create(10, &nctx->netgroups, NULL, NULL);
+    if (hret != HASH_SUCCESS) {
+        DEBUG(0,("Unable to initialize netgroup hash table\n"));
+        return EIO;
+    }
+
     DEBUG(1, ("NSS Initialization complete\n"));
 
     return EOK;
diff --git a/src/responder/nss/nsssrv.h b/src/responder/nss/nsssrv.h
index d53143dc0a522e76a927b01dae5db46a3867de42..0b7265b7c2205b0fdacb4e8dfb922721913067c4 100644
--- a/src/responder/nss/nsssrv.h
+++ b/src/responder/nss/nsssrv.h
@@ -54,6 +54,7 @@ struct nss_ctx {
 
     struct getent_ctx *pctx;
     struct getent_ctx *gctx;
+    hash_table_t *netgroups;
 
     bool filter_users_in_groups;
 
diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 6e1dc7bb3811a94c5166aa47caa8939db74ad146..d0db2ef86b3469426f7fa1b44973a56176fc8c46 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -22,6 +22,7 @@
 #include "util/util.h"
 #include "responder/nss/nsssrv.h"
 #include "responder/nss/nsssrv_private.h"
+#include "responder/nss/nsssrv_netgroup.h"
 #include "responder/common/negcache.h"
 #include "confdb/confdb.h"
 #include "db/sysdb.h"
@@ -363,8 +364,11 @@ errno_t check_cache(struct nss_dom_ctx *dctx,
     struct cli_ctx *cctx = cmdctx->cctx;
     bool off_band_update = false;
 
-    /* when searching for a user, more than one reply is a db error */
-    if ((req_type == SSS_DP_USER) && (res->count > 1)) {
+    /* when searching for a user or netgroup, more than one reply is a
+     * db error
+     */
+    if ((req_type == SSS_DP_USER || req_type == SSS_DP_NETGR) &&
+            (res->count > 1)) {
         DEBUG(1, ("getpwXXX call returned more than one result!"
                   " DB Corrupted?\n"));
         ret = nss_cmd_send_error(cmdctx, ENOENT);
@@ -3011,6 +3015,9 @@ static struct sss_cmd_table nss_cmds[] = {
     {SSS_NSS_GETGRENT, nss_cmd_getgrent},
     {SSS_NSS_ENDGRENT, nss_cmd_endgrent},
     {SSS_NSS_INITGR, nss_cmd_initgroups},
+    {SSS_NSS_SETNETGRENT, nss_cmd_setnetgrent},
+    {SSS_NSS_GETNETGRENT, nss_cmd_getnetgrent},
+    {SSS_NSS_ENDNETGRENT, nss_cmd_endnetgrent},
     {SSS_CLI_NULL, NULL}
 };
 
diff --git a/src/responder/nss/nsssrv_netgroup.c b/src/responder/nss/nsssrv_netgroup.c
new file mode 100644
index 0000000000000000000000000000000000000000..be99de7857156fd8315ee0dcbe6e074e62c1c280
--- /dev/null
+++ b/src/responder/nss/nsssrv_netgroup.c
@@ -0,0 +1,863 @@
+/*
+    SSSD
+
+    nsssrv_netgroup.c
+
+    Authors:
+        Stephen Gallagher <sgallagh@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 <collection.h>
+#include "util/util.h"
+#include "responder/nss/nsssrv.h"
+#include "responder/nss/nsssrv_private.h"
+#include "responder/nss/nsssrv_netgroup.h"
+#include "responder/common/negcache.h"
+#include "confdb/confdb.h"
+#include "db/sysdb.h"
+
+static errno_t get_netgroup_entry(struct nss_ctx *nctx,
+                                  char *name,
+                                  struct getent_ctx **netgr)
+{
+    hash_key_t key;
+    hash_value_t value;
+    int hret;
+
+    key.type = HASH_KEY_STRING;
+    key.str = name;
+
+    hret = hash_lookup(nctx->netgroups, &key, &value);
+    if (hret == HASH_SUCCESS) {
+        *netgr = talloc_get_type(value.ptr, struct getent_ctx);
+        return EOK;
+    } else if (hret == HASH_ERROR_KEY_NOT_FOUND) {
+        return ENOENT;
+    }
+
+    DEBUG(1, ("Unexpected error reading from netgroup hash [%d][%s]\n",
+              hret, hash_error_string(hret)));
+    return EIO;
+}
+
+static struct tevent_req *setnetgrent_send(TALLOC_CTX *mem_ctx,
+                                           const char *rawname,
+                                           struct nss_cmd_ctx *cmdctx);
+static void nss_cmd_setnetgrent_done(struct tevent_req *req);
+int nss_cmd_setnetgrent(struct cli_ctx *client)
+{
+    struct nss_cmd_ctx *cmdctx;
+    struct tevent_req *req;
+    const char *rawname;
+    uint8_t *body;
+    size_t blen;
+    errno_t ret = EOK;
+
+    /* Reset the result cursor to zero */
+    client->netgrent_cur = 0;
+
+    cmdctx = talloc_zero(client, struct nss_cmd_ctx);
+    if (!cmdctx) {
+        return ENOMEM;
+    }
+    cmdctx->cctx = client;
+
+    /* get netgroup name to query */
+    sss_packet_get_body(client->creq->in, &body, &blen);
+
+    /* if not terminated fail */
+    if (body[blen -1] != '\0') {
+        ret = EINVAL;
+        goto done;
+    }
+    rawname = (const char *)body;
+
+    req = setnetgrent_send(cmdctx, rawname, cmdctx);
+    if (!req) {
+        DEBUG(0, ("Fatal error calling setnetgrent_send\n"));
+        ret = EIO;
+        goto done;
+    }
+    tevent_req_set_callback(req, nss_cmd_setnetgrent_done, cmdctx);
+
+done:
+    return nss_cmd_done(cmdctx, ret);
+}
+
+static int netgr_hash_remove (TALLOC_CTX *ctx)
+{
+    int hret;
+    hash_key_t key;
+    struct getent_ctx *netgr =
+            talloc_get_type(ctx, struct getent_ctx);
+
+    key.type = HASH_KEY_STRING;
+    key.str = netgr->name;
+
+    /* Remove the netgroup result object from the lookup table */
+    hret = hash_delete(netgr->lookup_table, &key);
+    if (hret != HASH_SUCCESS) {
+        DEBUG(0, ("Could not remove key from table! [%d][%s]\n",
+                  hret, hash_error_string(hret)));
+        return -1;
+    }
+    return 0;
+}
+
+struct setnetgrent_ctx {
+    struct nss_ctx *nctx;
+    struct nss_cmd_ctx *cmdctx;
+    struct nss_dom_ctx *dctx;
+    char *netgr_shortname;
+    struct getent_ctx *netgr;
+};
+static errno_t lookup_netgr_step(struct setent_step_ctx *step_ctx);
+static struct tevent_req *setnetgrent_send(TALLOC_CTX *mem_ctx,
+                                           const char *rawname,
+                                           struct nss_cmd_ctx *cmdctx)
+{
+    char *domname;
+    errno_t ret;
+    struct tevent_req *req;
+    struct setnetgrent_ctx *state;
+    int hret;
+    struct nss_dom_ctx *dctx;
+    struct setent_step_ctx *step_ctx;
+    hash_key_t key;
+    hash_value_t value;
+
+    struct cli_ctx *client = cmdctx->cctx;
+    struct nss_ctx *nctx =
+            talloc_get_type(client->rctx->pvt_ctx, struct nss_ctx);
+
+    req = tevent_req_create(mem_ctx, &state, struct setnetgrent_ctx);
+    if (!req) {
+        DEBUG(0, ("Could not create tevent request for setnetgrent\n"));
+        return NULL;
+    }
+
+    state->nctx = nctx;
+    state->cmdctx = cmdctx;
+
+    state->dctx = talloc_zero(state, struct nss_dom_ctx);
+    if (!state->dctx) {
+        goto error;
+    }
+    dctx = state->dctx;
+    dctx->cmdctx = state->cmdctx;
+
+    ret = sss_parse_name(state, client->rctx->names, rawname,
+                         &domname, &state->netgr_shortname);
+    if (ret != EOK) {
+        DEBUG(2, ("Invalid name received [%s]\n", rawname));
+        goto error;
+    }
+
+    DEBUG(4, ("Requesting info for netgroup [%s] from [%s]\n",
+              state->netgr_shortname, domname?domname:"<ALL>"));
+
+    if (domname) {
+        dctx->domain = nss_get_dom(client->rctx->domains, domname);
+        if (!dctx->domain) {
+            goto error;
+        }
+
+        /* Save the netgroup name for getnetgrent */
+        client->netgr_name = talloc_strdup(client, rawname);
+        if (!client->netgr_name) {
+            ret = ENOMEM;
+            goto error;
+        }
+    } else {
+        /* this is a multidomain search */
+        dctx->domain = client->rctx->domains;
+        cmdctx->check_next = true;
+
+        /* Save the netgroup name for getnetgrent */
+        client->netgr_name = talloc_strdup(client, state->netgr_shortname);
+        if (!client->netgr_name) {
+            goto error;
+        }
+    }
+
+    dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+    /* Is the result context already available?
+     * Check for existing lookups for this netgroup
+     */
+    ret = get_netgroup_entry(nctx, client->netgr_name, &state->netgr);
+    if (ret == EOK) {
+        /* Another process already requested this netgroup
+         * Check whether it's ready for processing.
+         */
+        if (state->netgr->ready) {
+            /* Ready to process results */
+            tevent_req_done(req);
+            tevent_req_post(req, nctx->rctx->ev);
+            return req;
+        }
+
+        /* Result object is still being constructed
+         * Register for notification when it's ready
+         */
+        ret = setent_add_ref(cmdctx->cctx, state->netgr, req);
+        if (ret != EOK) {
+            goto error;
+        }
+        /* Will return control below */
+    } else if (ret == ENOENT) {
+        /* This is the first attempt to request this netgroup
+         */
+        state->netgr = talloc_zero(nctx, struct getent_ctx);
+        if (!state->netgr) {
+            goto error;
+        }
+        dctx->netgr = state->netgr;
+
+        /* Save the name used for the lookup table
+         * so we can remove it in the destructor
+         */
+        state->netgr->name = talloc_strdup(state->netgr,
+                                           client->netgr_name);
+        if (!state->netgr->name) {
+            talloc_free(state->netgr);
+            goto error;
+        }
+
+        state->netgr->lookup_table = nctx->netgroups;
+
+        /* Add a reference for ourselves */
+        ret = setent_add_ref(cmdctx->cctx, state->netgr, req);
+        if (ret != EOK) {
+            talloc_free(state->netgr);
+            goto error;
+        }
+
+        /* Add this entry to the hash table */
+        key.type = HASH_KEY_STRING;
+        key.str = client->netgr_name;
+        value.type = HASH_VALUE_PTR;
+        value.ptr = state->netgr;
+        hret = hash_enter(nctx->netgroups, &key, &value);
+        if (hret != EOK) {
+            DEBUG(0, ("Unable to add hash table entry for [%s]", key.str));
+            DEBUG(4, ("Hash error [%d][%s]", hret, hash_error_string(hret)));
+            talloc_free(state->netgr);
+            goto error;
+        }
+        talloc_set_destructor((TALLOC_CTX *)state->netgr, netgr_hash_remove);
+
+        /* Perform lookup */
+        step_ctx = talloc_zero(state->netgr, struct setent_step_ctx);
+        if (!step_ctx) {
+            ret = ENOMEM;
+            goto error;
+        }
+
+        /* Steal the dom_ctx onto the step_ctx so it doesn't go out of scope if
+         * this request is canceled while other requests are in-progress.
+         */
+        step_ctx->dctx = talloc_steal(step_ctx, state->dctx);
+        step_ctx->nctx = state->nctx;
+        step_ctx->getent_ctx = state->netgr;
+        step_ctx->rctx = client->rctx;
+        step_ctx->check_next = cmdctx->check_next;
+        step_ctx->name =
+                talloc_strdup(step_ctx, state->netgr->name);
+        if (!step_ctx->name) {
+            ret = ENOMEM;
+            goto error;
+        }
+
+        ret = lookup_netgr_step(step_ctx);
+        if (ret != EOK) {
+            if (ret == EAGAIN) {
+                /* We need to reenter the mainloop
+                 * We may be refreshing the cache
+                 */
+                return req;
+            }
+
+            /* An unexpected error occurred */
+            goto error;
+        }
+        tevent_req_post(req, cmdctx->cctx->ev);
+        /* Will return control below */
+    } else {
+        /* Unexpected error from hash_lookup */
+        goto error;
+    }
+
+    return req;
+
+error:
+    talloc_free(req);
+    return NULL;
+}
+
+static void lookup_netgr_dp_callback(uint16_t err_maj, uint32_t err_min,
+                                     const char *err_msg, void *ptr);
+
+static void setnetgrent_result_timeout(struct tevent_context *ev,
+                                       struct tevent_timer *te,
+                                       struct timeval current_time,
+                                       void *pvt);
+static errno_t lookup_netgr_step(struct setent_step_ctx *step_ctx)
+{
+    errno_t ret;
+    struct sss_domain_info *dom = step_ctx->dctx->domain;
+    struct getent_ctx *netgr;
+    struct sysdb_ctx *sysdb;
+    struct timeval tv;
+    struct tevent_timer *te;
+
+    /* Check each domain for this netgroup name */
+    while (dom) {
+        /* if it is a domainless search, skip domains that require fully
+          * qualified names instead */
+         while (dom && step_ctx->check_next && dom->fqnames) {
+             dom = dom->next;
+         }
+
+         /* No domains left to search */
+         if (!dom) break;
+
+         if (dom != step_ctx->dctx->domain) {
+             /* make sure we reset the check_provider flag when we check
+              * a new domain */
+             step_ctx->dctx->check_provider =
+                     NEED_CHECK_PROVIDER(dom->provider);
+         }
+
+         /* make sure to update the dctx if we changed domain */
+         step_ctx->dctx->domain = dom;
+
+         /* verify this netgroup has not yet been negatively cached */
+         ret = sss_ncache_check_netgr(step_ctx->nctx->ncache,
+                                      step_ctx->nctx->neg_timeout,
+                                      dom->name, step_ctx->name);
+
+         /* if neg cached, return we didn't find it */
+         if (ret == EEXIST) {
+             DEBUG(2, ("Netgroup [%s] does not exist! (negative cache)\n",
+                       step_ctx->name));
+             /* if a multidomain search, try with next */
+             if (step_ctx->check_next) {
+                 dom = dom->next;
+                 continue;
+             }
+         }
+
+         DEBUG(4, ("Requesting info for [%s@%s]\n",
+                   step_ctx->name, dom->name));
+         ret = sysdb_get_ctx_from_list(step_ctx->rctx->db_list, dom, &sysdb);
+         if (ret != EOK) {
+             DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+             return EIO;
+         }
+
+         /* Look up the netgroup in the cache */
+         ret = sysdb_getnetgr(step_ctx->dctx, sysdb, dom,
+                              step_ctx->name,
+                              &step_ctx->dctx->res);
+         if (ret == ENOENT) {
+             /* This netgroup was not found in this domain */
+             if (!step_ctx->dctx->check_provider) {
+                 if (step_ctx->check_next) {
+                     dom = dom->next;
+                     continue;
+                 }
+                 else break;
+             }
+             ret = EOK;
+         }
+
+         if (ret != EOK) {
+             DEBUG(1, ("Failed to make request to our cache!\n"));
+             return EIO;
+         }
+
+         ret = get_netgroup_entry(step_ctx->nctx, step_ctx->name,
+                                  &netgr);
+         if (ret != EOK) {
+             /* Something really bad happened! */
+             DEBUG(0, ("Netgroup entry was lost!\n"));
+             return ret;
+         }
+
+         /* Convert the result to a list of triples */
+         ret = sysdb_netgr_to_triples(netgr, step_ctx->dctx->res,
+                                      &netgr->triples);
+         if (ret == ENOENT) {
+             /* This netgroup was not found in this domain */
+             if (!step_ctx->dctx->check_provider) {
+                 if (step_ctx->check_next) {
+                     dom = dom->next;
+                     continue;
+                 }
+                 else break;
+             }
+             ret = EOK;
+         }
+
+         if (ret != EOK) {
+             DEBUG(1, ("Failed to convert results into triples\n"));
+             return EIO;
+         }
+
+         /* if this is a caching provider (or if we haven't checked the cache
+          * yet) then verify that the cache is uptodate */
+         if (step_ctx->dctx->check_provider) {
+             ret = check_cache(step_ctx->dctx,
+                               step_ctx->nctx,
+                               step_ctx->dctx->res,
+                               SSS_DP_NETGR,
+                               step_ctx->name, 0,
+                               lookup_netgr_dp_callback,
+                               step_ctx);
+             if (ret != EOK) {
+                 /* May return EAGAIN legitimately to indicate that
+                  * we need to reenter the mainloop
+                  */
+                 return ret;
+             }
+         }
+
+         /* Results found */
+         DEBUG(6, ("Returning info for netgroup [%s@%s]\n",
+                   step_ctx->name, dom->name));
+         netgr->ready = true;
+
+         /* Set up a lifetime timer for this result object
+          * We don't want this result object to outlive the
+          * entry cache refresh timeout
+          */
+         tv = tevent_timeval_current_ofs(dom->entry_cache_timeout, 0);
+         te = tevent_add_timer(step_ctx->nctx->rctx->ev,
+                               step_ctx->nctx->gctx, tv,
+                               setnetgrent_result_timeout,
+                               netgr);
+         if (!te) {
+             DEBUG(0, ("Could not set up life timer for setnetgrent result object. "
+                       "Entries may become stale.\n"));
+         }
+
+         return EOK;
+    }
+
+    /* If we've gotten here, then no domain contained this netgroup */
+    DEBUG(2, ("No matching domain found for [%s], fail!\n",
+              step_ctx->name));
+    netgr->ready = true;
+    netgr->triples = NULL;
+    return ENOENT;
+}
+
+static void lookup_netgr_dp_callback(uint16_t err_maj, uint32_t err_min,
+                                     const char *err_msg, void *ptr)
+{
+    struct setent_step_ctx *step_ctx =
+            talloc_get_type(ptr, struct setent_step_ctx);
+    struct nss_dom_ctx *dctx = step_ctx->dctx;
+    struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+    int ret;
+
+    if (err_maj) {
+        DEBUG(2, ("Unable to get information from Data Provider\n"
+                  "Error: %u, %u, %s\n"
+                  "Will try to return what we have in cache\n",
+                  (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+        /* Loop to the next domain if possible */
+        if (dctx->domain->next && cmdctx->check_next) {
+            dctx->domain = dctx->domain->next;
+            dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+        }
+    }
+
+    /* ok the backend returned, search to see if we have updated results */
+    ret = lookup_netgr_step(step_ctx);
+    if (ret != EOK) {
+        if (ret == EAGAIN) {
+            return;
+        }
+    }
+
+    /* We have results to return */
+    while(dctx->netgr->reqs) {
+        if (ret == EOK) {
+            tevent_req_done(dctx->netgr->reqs->req);
+        } else {
+            tevent_req_error(dctx->netgr->reqs->req, ret);
+        }
+        /* Freeing each entry in the list removes it from the dlist */
+        talloc_free(dctx->netgr->reqs);
+    }
+}
+
+static void setnetgrent_result_timeout(struct tevent_context *ev,
+                                       struct tevent_timer *te,
+                                       struct timeval current_time,
+                                       void *pvt)
+{
+    struct getent_ctx *netgr =
+            talloc_get_type(pvt, struct getent_ctx);
+
+    /* Free the netgroup result context
+     * The destructor for the netgroup will remove itself
+     * from the hash table
+     *
+     * If additional getnetgrent() requests come in, they
+     * will invoke an implicit setnetgrent() call and
+     * refresh the result object
+     */
+    talloc_free(netgr);
+}
+
+static errno_t setnetgrent_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
+static void nss_cmd_setnetgrent_done(struct tevent_req *req)
+{
+    errno_t reqret;
+    errno_t ret;
+    struct sss_packet *packet;
+    uint8_t *body;
+    size_t blen;
+
+    struct nss_cmd_ctx *cmdctx =
+            tevent_req_callback_data(req, struct nss_cmd_ctx);
+
+    reqret = setnetgrent_recv(req);
+    talloc_zfree(req);
+    if (reqret != EOK && reqret != ENOENT) {
+        DEBUG(1, ("setnetgrent failed\n"));
+        nss_cmd_done(cmdctx, reqret);
+        return;
+    }
+
+    /* Either we succeeded or no domains were eligible */
+    ret = sss_packet_new(cmdctx->cctx->creq, 0,
+                         sss_packet_get_cmd(cmdctx->cctx->creq->in),
+                         &cmdctx->cctx->creq->out);
+    if (ret == EOK) {
+        if (reqret == ENOENT) {
+            /* Notify the caller that this entry wasn't found */
+            fill_empty(cmdctx->cctx->creq->out);
+        } else {
+            packet = cmdctx->cctx->creq->out;
+            ret = sss_packet_grow(packet, 2*sizeof(uint32_t));
+            if (ret != EOK) {
+                DEBUG(1, ("Couldn't grow the packet\n"));
+                NSS_CMD_FATAL_ERROR(cmdctx);
+            }
+
+            sss_packet_get_body(packet, &body, &blen);
+            ((uint32_t *)body)[0] = 1; /* Got some results */
+            ((uint32_t *)body)[1] = 0; /* reserved */
+        }
+
+        sss_cmd_done(cmdctx->cctx, NULL);
+        return;
+    }
+
+    DEBUG(1, ("Error creating packet\n"));
+}
+
+static void setnetgrent_implicit_done(struct tevent_req *req);
+static errno_t nss_cmd_getnetgrent_process(struct nss_cmd_ctx *cmdctx,
+                                           struct getent_ctx *netgr);
+int nss_cmd_getnetgrent(struct cli_ctx *client)
+{
+    errno_t ret;
+    struct nss_ctx *nctx;
+    struct nss_cmd_ctx *cmdctx;
+    struct getent_ctx *netgr;
+    struct tevent_req *req;
+
+    DEBUG(4, ("Requesting netgroup data\n"));
+
+    cmdctx = talloc(client, struct nss_cmd_ctx);
+    if (!cmdctx) {
+        return ENOMEM;
+    }
+    cmdctx->cctx = client;
+
+    nctx = talloc_get_type(client->rctx->pvt_ctx, struct nss_ctx);
+
+    if (!client->netgr_name) {
+        /* Tried to run getnetgrent without a preceding
+         * setnetgrent. There is no way to determine which
+         * netgroup is being requested.
+         */
+        return nss_cmd_done(cmdctx, EINVAL);
+    }
+
+    /* Look up the results from the hash */
+    ret = get_netgroup_entry(nctx, client->netgr_name, &netgr);
+    if (ret == ENOENT) {
+        /* We need to invoke an implicit setnetgrent() to
+         * wait for the result object to become available.
+         */
+
+        req = setnetgrent_send(cmdctx, client->netgr_name, cmdctx);
+        if (!req) {
+            return nss_cmd_done(cmdctx, EIO);
+        }
+        tevent_req_set_callback(req, setnetgrent_implicit_done, cmdctx);
+
+        return EOK;
+    } else if (ret != EOK) {
+        DEBUG(1, ("An unexpected error occurred: [%d][%s]\n",
+                  ret, strerror(ret)));
+
+        return nss_cmd_done(cmdctx, ret);
+    }
+
+    /* Hash entry was found. Is it ready? */
+    if (!netgr->ready) {
+        /* We need to invoke an implicit setnetgrent() to
+         * wait for the result object to become available.
+         */
+        req = setnetgrent_send(cmdctx, client->netgr_name, cmdctx);
+        if (!req) {
+            return nss_cmd_done(cmdctx, EIO);
+        }
+        tevent_req_set_callback(req, setnetgrent_implicit_done, cmdctx);
+
+        return EOK;
+    }
+
+    DEBUG(6, ("Returning results for [%s]\n", client->netgr_name));
+
+    /* Read the result strings */
+    ret = nss_cmd_getnetgrent_process(cmdctx, netgr);
+    if (ret != EOK) {
+        DEBUG(1, ("Failed: [%d][%s]\n", ret, strerror(ret)));
+    }
+    return ret;
+}
+
+static void setnetgrent_implicit_done(struct tevent_req *req)
+{
+    errno_t ret;
+    struct getent_ctx *netgr;
+    struct nss_cmd_ctx *cmdctx =
+            tevent_req_callback_data(req, struct nss_cmd_ctx);
+    struct nss_ctx *nctx =
+            talloc_get_type(cmdctx->cctx->rctx->pvt_ctx, struct nss_ctx);
+
+    ret = setnetgrent_recv(req);
+    talloc_zfree(req);
+
+    /* ENOENT is acceptable, it just means there were no values
+     * to be returned. This will be handled gracefully in
+     * nss_cmd_retnetgrent later
+     */
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(0, ("Implicit setnetgrent failed with unexpected error "
+                  "[%d][%s]\n", ret, strerror(ret)));
+        NSS_CMD_FATAL_ERROR(cmdctx);
+    }
+
+    if (ret == ENOENT) {
+        /* No entries found for this netgroup */
+        nss_cmd_done(cmdctx, ret);
+        return;
+    }
+
+    /* Look up the results from the hash */
+    ret = get_netgroup_entry(nctx, cmdctx->cctx->netgr_name, &netgr);
+    if (ret == ENOENT) {
+        /* Critical error. This should never happen */
+        DEBUG(0, ("Implicit setnetgrent returned success without creating "
+                  "result object.\n"));
+        NSS_CMD_FATAL_ERROR(cmdctx);
+    } else if (ret != EOK) {
+        DEBUG(1, ("An unexpected error occurred: [%d][%s]\n",
+                  ret, strerror(ret)));
+
+        NSS_CMD_FATAL_ERROR(cmdctx);
+    }
+
+    if (!netgr->ready) {
+        /* Critical error. This should never happen */
+        DEBUG(0, ("Implicit setnetgrent returned success without creating "
+                  "result object.\n"));
+        NSS_CMD_FATAL_ERROR(cmdctx);
+    }
+
+    ret = nss_cmd_getnetgrent_process(cmdctx, netgr);
+    if (ret != EOK) {
+        DEBUG(0, ("Immediate retrieval failed with unexpected error "
+                  "[%d][%s]\n", ret, strerror(ret)));
+        NSS_CMD_FATAL_ERROR(cmdctx);
+    }
+}
+
+static errno_t nss_cmd_retnetgrent(struct cli_ctx *client,
+                                   struct sysdb_netgroup_ctx **triples,
+                                   int num);
+static errno_t nss_cmd_getnetgrent_process(struct nss_cmd_ctx *cmdctx,
+                                           struct getent_ctx *netgr)
+{
+    struct cli_ctx *client = cmdctx->cctx;
+    uint8_t *body;
+    size_t blen;
+    uint32_t num;
+    errno_t ret;
+
+    /* get max num of entries to return in one call */
+    sss_packet_get_body(client->creq->in, &body, &blen);
+    if (blen != sizeof(uint32_t)) {
+        return EINVAL;
+    }
+    num = *((uint32_t *)body);
+
+    /* create response packet */
+    ret = sss_packet_new(client->creq, 0,
+                         sss_packet_get_cmd(client->creq->in),
+                         &client->creq->out);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    if (!netgr->triples || netgr->triples[0] == NULL) {
+        /* No entries */
+        DEBUG(5, ("No triples found\n"));
+        ret = fill_empty(client->creq->out);
+        if (ret != EOK) {
+            return nss_cmd_done(cmdctx, ret);
+        }
+        goto done;
+    }
+
+    ret = nss_cmd_retnetgrent(client, netgr->triples, num);
+
+done:
+    sss_packet_set_error(client->creq->out, ret);
+    sss_cmd_done(client, cmdctx);
+
+    return EOK;
+}
+
+static errno_t nss_cmd_retnetgrent(struct cli_ctx *client,
+                                   struct sysdb_netgroup_ctx **triples,
+                                   int count)
+{
+    size_t len;
+    size_t hostlen = 0;
+    size_t userlen = 0;
+    size_t domainlen = 0;
+    uint8_t *body;
+    size_t blen, rp;
+    errno_t ret;
+    struct sss_packet *packet = client->creq->out;
+    int num, start;
+
+    /* first 2 fields (len and reserved), filled up later */
+    rp = 2*sizeof(uint32_t);
+    ret = sss_packet_grow(packet, rp);
+    if (ret != EOK) return ret;
+
+    start = client->netgrent_cur;
+    num = 0;
+    while (triples[client->netgrent_cur] &&
+            (client->netgrent_cur - start) < count) {
+        hostlen = 1;
+        if (triples[client->netgrent_cur]->hostname) {
+            hostlen += strlen(triples[client->netgrent_cur]->hostname);
+        }
+
+        userlen = 1;
+        if (triples[client->netgrent_cur]->username) {
+            userlen += strlen(triples[client->netgrent_cur]->username);
+        }
+
+        domainlen = 1;
+        if (triples[client->netgrent_cur]->domainname) {
+            domainlen += strlen(triples[client->netgrent_cur]->domainname);
+        }
+
+        len = hostlen + userlen + domainlen;
+        ret = sss_packet_grow(packet, len);
+        if (ret != EOK) {
+            return ret;
+        }
+        sss_packet_get_body(packet, &body, &blen);
+
+        if (hostlen == 1) {
+            body[rp] = '\0';
+        } else {
+            memcpy(&body[rp],
+                   triples[client->netgrent_cur]->hostname,
+                   hostlen);
+        }
+        rp += hostlen;
+
+        if (userlen == 1) {
+            body[rp] = '\0';
+        } else {
+            memcpy(&body[rp],
+                   triples[client->netgrent_cur]->username,
+                   userlen);
+        }
+        rp += userlen;
+
+        if (domainlen == 1) {
+            body[rp] = '\0';
+        } else {
+            memcpy(&body[rp],
+                   triples[client->netgrent_cur]->domainname,
+                   domainlen);
+        }
+        rp += domainlen;
+
+        num++;
+        client->netgrent_cur++;
+    }
+
+    sss_packet_get_body(packet, &body, &blen);
+    ((uint32_t *)body)[0] = num; /* num results */
+    ((uint32_t *)body)[1] = 0; /* reserved */
+
+    return EOK;
+}
+
+int nss_cmd_endnetgrent(struct cli_ctx *client)
+{
+    errno_t ret;
+
+    /* create response packet */
+    ret = sss_packet_new(client->creq, 0,
+                         sss_packet_get_cmd(client->creq->in),
+                         &client->creq->out);
+
+    if (ret != EOK) {
+        return ret;
+    }
+
+    /* Reset the indices so that subsequent requests start at zero */
+    client->netgrent_cur = 0;
+    talloc_zfree(client->netgr_name);
+
+    sss_cmd_done(client, NULL);
+    return EOK;
+}
diff --git a/src/responder/nss/nsssrv_netgroup.h b/src/responder/nss/nsssrv_netgroup.h
new file mode 100644
index 0000000000000000000000000000000000000000..ed345c43487d852709eef0f1ed55027f09b605fd
--- /dev/null
+++ b/src/responder/nss/nsssrv_netgroup.h
@@ -0,0 +1,34 @@
+/*
+    SSSD
+
+    nssrv_netgroup.h
+
+    Authors:
+        Stephen Gallagher <sgallagh@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/>.
+*/
+
+#ifndef NSSRV_NETGROUP_H_
+#define NSSRV_NETGROUP_H_
+
+#define SSS_COL_NETGR 5000
+
+int nss_cmd_setnetgrent(struct cli_ctx *cctx);
+int nss_cmd_getnetgrent(struct cli_ctx *cctx);
+int nss_cmd_endnetgrent(struct cli_ctx *cctx);
+
+#endif /* NSSRV_NETGROUP_H_ */
-- 
1.7.2.3

