about summary refs log tree commit diff
path: root/nscd
diff options
context:
space:
mode:
Diffstat (limited to 'nscd')
-rw-r--r--nscd/Makefile6
-rw-r--r--nscd/cache.c6
-rw-r--r--nscd/connections.c46
-rw-r--r--nscd/netgroupcache.c669
-rw-r--r--nscd/nscd-client.h23
-rw-r--r--nscd/nscd.conf9
-rw-r--r--nscd/nscd.h22
-rw-r--r--nscd/nscd_conf.c3
-rw-r--r--nscd/nscd_netgroup.c290
-rw-r--r--nscd/nscd_proto.h5
-rw-r--r--nscd/selinux.c9
11 files changed, 1070 insertions, 18 deletions
diff --git a/nscd/Makefile b/nscd/Makefile
index 097e6f0f43..be0afed2d8 100644
--- a/nscd/Makefile
+++ b/nscd/Makefile
@@ -22,7 +22,7 @@
 subdir	:= nscd
 
 routines := nscd_getpw_r nscd_getgr_r nscd_gethst_r nscd_getai \
-	    nscd_initgroups nscd_getserv_r
+	    nscd_initgroups nscd_getserv_r nscd_netgroup
 aux	:= nscd_helper
 
 include ../Makeconfig
@@ -34,7 +34,8 @@ nscd-modules := nscd connections pwdcache getpwnam_r getpwuid_r grpcache \
 		getgrnam_r getgrgid_r hstcache gethstbyad_r gethstbynm3_r \
 		getsrvbynm_r getsrvbypt_r servicescache \
 		dbg_log nscd_conf nscd_stat cache mem nscd_setup_thread \
-		xmalloc xstrdup aicache initgrcache gai res_hconf
+		xmalloc xstrdup aicache initgrcache gai res_hconf \
+		netgroupcache
 
 ifeq ($(have-thread-library),yes)
 
@@ -121,6 +122,7 @@ CFLAGS-servicescache.c += $(nscd-cflags)
 CFLAGS-getsrvbynm_r.c += $(nscd-cflags)
 CFLAGS-getsrvbypt_r.c += $(nscd-cflags)
 CFLAGS-res_hconf.c += $(nscd-cflags)
+CFLAGS-netgroupcache.c += $(nscd-cflags)
 
 ifeq (yesyes,$(have-fpie)$(build-shared))
 LDFLAGS-nscd = -Wl,-z,now
diff --git a/nscd/cache.c b/nscd/cache.c
index 58f0bcc5f1..507ca78464 100644
--- a/nscd/cache.c
+++ b/nscd/cache.c
@@ -60,7 +60,9 @@ static time_t (*const readdfcts[LASTREQ]) (struct database_dyn *,
   [GETAI] = readdhstai,
   [INITGROUPS] = readdinitgroups,
   [GETSERVBYNAME] = readdservbyname,
-  [GETSERVBYPORT] = readdservbyport
+  [GETSERVBYPORT] = readdservbyport,
+  [GETNETGRENT] = readdgetnetgrent,
+  [INNETGR] = readdinnetgr
 };
 
 
@@ -70,7 +72,7 @@ static time_t (*const readdfcts[LASTREQ]) (struct database_dyn *,
 
    This function must be called with the read-lock held.  */
 struct datahead *
-cache_search (request_type type, void *key, size_t len,
+cache_search (request_type type, const void *key, size_t len,
 	      struct database_dyn *table, uid_t owner)
 {
   unsigned long int hash = __nis_hash (key, len) % table->head->module;
diff --git a/nscd/connections.c b/nscd/connections.c
index 7d3ff2e43b..2b5c7ef53c 100644
--- a/nscd/connections.c
+++ b/nscd/connections.c
@@ -57,11 +57,6 @@
 #endif
 
 
-/* Wrapper functions with error checking for standard functions.  */
-extern void *xmalloc (size_t n);
-extern void *xcalloc (size_t n, size_t s);
-extern void *xrealloc (void *o, size_t n);
-
 /* Support to run nscd as an unprivileged user */
 const char *server_user;
 static uid_t server_uid;
@@ -100,7 +95,10 @@ const char *const serv2str[LASTREQ] =
   [INITGROUPS] = "INITGROUPS",
   [GETSERVBYNAME] = "GETSERVBYNAME",
   [GETSERVBYPORT] = "GETSERVBYPORT",
-  [GETFDSERV] = "GETFDSERV"
+  [GETFDSERV] = "GETFDSERV",
+  [GETNETGRENT] = "GETNETGRENT",
+  [INNETGR] = "INNETGR",
+  [GETFDNETGR] = "GETFDNETGR"
 };
 
 /* The control data structures for the services.  */
@@ -181,6 +179,25 @@ struct database_dyn dbs[lastdb] =
     .wr_fd = -1,
     .ro_fd = -1,
     .mmap_used = false
+  },
+  [netgrdb] = {
+    .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP,
+    .prune_lock = PTHREAD_MUTEX_INITIALIZER,
+    .prune_run_lock = PTHREAD_MUTEX_INITIALIZER,
+    .enabled = 0,
+    .check_file = 1,
+    .persistent = 0,
+    .propagate = 0,		/* Not used.  */
+    .shared = 0,
+    .max_db_size = DEFAULT_MAX_DB_SIZE,
+    .suggested_module = DEFAULT_SUGGESTED_MODULE,
+    .db_filename = _PATH_NSCD_NETGROUP_DB,
+    .disabled_iov = &netgroup_iov_disabled,
+    .postimeout = 28800,
+    .negtimeout = 20,
+    .wr_fd = -1,
+    .ro_fd = -1,
+    .mmap_used = false
   }
 };
 
@@ -210,7 +227,10 @@ static struct
   [INITGROUPS] = { true, &dbs[grpdb] },
   [GETSERVBYNAME] = { true, &dbs[servdb] },
   [GETSERVBYPORT] = { true, &dbs[servdb] },
-  [GETFDSERV] = { false, &dbs[servdb] }
+  [GETFDSERV] = { false, &dbs[servdb] },
+  [GETNETGRENT] = { true, &dbs[netgrdb] },
+  [INNETGR] = { true, &dbs[netgrdb] },
+  [GETFDNETGR] = { false, &dbs[netgrdb] }
 };
 
 
@@ -355,7 +375,8 @@ check_use (const char *data, nscd_ssize_t first_free, uint8_t *usemap,
 static int
 verify_persistent_db (void *mem, struct database_pers_head *readhead, int dbnr)
 {
-  assert (dbnr == pwddb || dbnr == grpdb || dbnr == hstdb || dbnr == servdb);
+  assert (dbnr == pwddb || dbnr == grpdb || dbnr == hstdb || dbnr == servdb
+	  || dbnr == netgrdb);
 
   time_t now = time (NULL);
 
@@ -1230,6 +1251,14 @@ request from '%s' [%ld] not handled due to missing permission"),
       addservbyport (db, fd, req, key, uid);
       break;
 
+    case GETNETGRENT:
+      addgetnetgrent (db, fd, req, key, uid);
+      break;
+
+    case INNETGR:
+      addinnetgr (db, fd, req, key, uid);
+      break;
+
     case GETSTAT:
     case SHUTDOWN:
     case INVALIDATE:
@@ -1276,6 +1305,7 @@ request from '%s' [%ld] not handled due to missing permission"),
     case GETFDGR:
     case GETFDHST:
     case GETFDSERV:
+    case GETFDNETGR:
 #ifdef SCM_RIGHTS
       send_ro_fd (reqinfo[req->type].db, key, fd);
 #endif
diff --git a/nscd/netgroupcache.c b/nscd/netgroupcache.c
new file mode 100644
index 0000000000..a406ade995
--- /dev/null
+++ b/nscd/netgroupcache.c
@@ -0,0 +1,669 @@
+/* Cache handling for netgroup lookup.
+   Copyright (C) 2011 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Ulrich Drepper <drepper@gmail.com>, 2011.
+
+   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; version 2 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, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#include <alloca.h>
+#include <assert.h>
+#include <errno.h>
+#include <libintl.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include "../inet/netgroup.h"
+#include "nscd.h"
+#include "dbg_log.h"
+#ifdef HAVE_SENDFILE
+# include <kernel-features.h>
+#endif
+
+
+/* This is the standard reply in case the service is disabled.  */
+static const netgroup_response_header disabled =
+{
+  .version = NSCD_VERSION,
+  .found = -1,
+  .nresults = 0,
+  .result_len = 0
+};
+
+/* This is the struct describing how to write this record.  */
+const struct iovec netgroup_iov_disabled =
+{
+  .iov_base = (void *) &disabled,
+  .iov_len = sizeof (disabled)
+};
+
+
+/* This is the standard reply in case we haven't found the dataset.  */
+static const netgroup_response_header notfound =
+{
+  .version = NSCD_VERSION,
+  .found = 0,
+  .nresults = 0,
+  .result_len = 0
+};
+
+
+struct dataset
+{
+  struct datahead head;
+  netgroup_response_header resp;
+  char strdata[0];
+};
+
+
+static time_t
+addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
+		 const char *key, uid_t uid, struct hashentry *he,
+		 struct datahead *dh, struct dataset **resultp)
+{
+  if (__builtin_expect (debug_level > 0, 0))
+    {
+      if (he == NULL)
+	dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
+      else
+	dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
+    }
+
+  static service_user *netgroup_database;
+  time_t timeout;
+  struct dataset *dataset;
+  bool cacheable = false;
+  ssize_t total;
+
+  char *key_copy = NULL;
+  struct __netgrent data;
+  size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
+  size_t buffilled = sizeof (*dataset);
+  char *buffer = NULL;
+  size_t nentries = 0;
+  bool use_malloc = false;
+  size_t group_len = strlen (key) + 1;
+  union
+  {
+    struct name_list elem;
+    char mem[sizeof (struct name_list) + group_len];
+  } first_needed;
+
+  if (netgroup_database == NULL
+      && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
+    {
+      /* No such service.  */
+      total = sizeof (notfound);
+      timeout = time (NULL) + db->negtimeout;
+
+      if (fd != -1)
+	TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
+
+      dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
+      /* If we cannot permanently store the result, so be it.  */
+      if (dataset != NULL)
+	{
+	  dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
+	  dataset->head.recsize = total;
+	  dataset->head.notfound = true;
+	  dataset->head.nreloads = 0;
+	  dataset->head.usable = true;
+
+	  /* Compute the timeout time.  */
+	  timeout = dataset->head.timeout = time (NULL) + db->negtimeout;
+	  dataset->head.ttl = db->negtimeout;
+
+	  /* This is the reply.  */
+	  memcpy (&dataset->resp, &notfound, total);
+
+	  /* Copy the key data.  */
+	  memcpy (dataset->strdata, key, req->key_len);
+
+	  cacheable = true;
+	}
+
+      goto writeout;
+    }
+
+  memset (&data, '\0', sizeof (data));
+  buffer = alloca (buflen);
+  first_needed.elem.next = &first_needed.elem;
+  memcpy (first_needed.elem.name, key, group_len);
+  data.needed_groups = &first_needed.elem;
+
+  while (data.needed_groups != NULL)
+    {
+      /* Add the next group to the list of those which are known.  */
+      struct name_list *this_group = data.needed_groups->next;
+      if (this_group == data.needed_groups)
+	data.needed_groups = NULL;
+      else
+	data.needed_groups->next = this_group->next;
+      this_group->next = data.known_groups;
+      data.known_groups = this_group;
+
+      union
+      {
+	enum nss_status (*f) (const char *, struct __netgrent *);
+	void *ptr;
+      } setfct;
+
+      service_user *nip = netgroup_database;
+      int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
+      while (!no_more)
+	{
+	  enum nss_status status
+	    = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
+
+	  if (status == NSS_STATUS_SUCCESS)
+	    {
+	      union
+	      {
+		enum nss_status (*f) (struct __netgrent *, char *, size_t,
+				      int *);
+		void *ptr;
+	      } getfct;
+	      getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
+	      if (getfct.f != NULL)
+		while (1)
+		  {
+		    int e;
+		    status = getfct.f (&data, buffer + buffilled,
+				       buflen - buffilled, &e);
+		    if (status == NSS_STATUS_RETURN)
+		      /* This was the last one for this group.  Look
+			 at next group if available.  */
+		      break;
+		    if (status == NSS_STATUS_SUCCESS)
+		      {
+			if (data.type == triple_val)
+			  {
+			    const char *nhost = data.val.triple.host;
+			    const char *nuser = data.val.triple.user;
+			    const char *ndomain = data.val.triple.domain;
+
+			    if (data.val.triple.host > data.val.triple.user
+				|| data.val.triple.user > data.val.triple.domain)
+			      {
+				const char *last = MAX (nhost,
+							MAX (nuser, ndomain));
+				size_t bufused = (last + strlen (last) + 1
+						  - buffer);
+
+				/* We have to make temporary copies.  */
+				size_t hostlen = strlen (nhost) + 1;
+				size_t userlen = strlen (nuser) + 1;
+				size_t domainlen = strlen (ndomain) + 1;
+				size_t needed = hostlen + userlen + domainlen;
+
+				if (buflen - req->key_len - bufused < needed)
+				  {
+				    size_t newsize = MAX (2 * buflen,
+							  buflen + 2 * needed);
+				    if (use_malloc || newsize > 1024 * 1024)
+				      {
+					buflen = newsize;
+					char *newbuf = xrealloc (use_malloc
+								 ? buffer
+								 : NULL,
+								 buflen);
+
+					buffer = newbuf;
+					use_malloc = true;
+				      }
+				    else
+				      extend_alloca (buffer, buflen, newsize);
+				  }
+
+				nhost = memcpy (buffer + bufused,
+						nhost, hostlen);
+				nuser = memcpy ((char *) nhost + hostlen,
+						nuser, userlen);
+				ndomain = memcpy ((char *) nuser + userlen,
+						  ndomain, domainlen);
+			      }
+
+			    char *wp = buffer + buffilled;
+			    wp = stpcpy (wp, nhost) + 1;
+			    wp = stpcpy (wp, nuser) + 1;
+			    wp = stpcpy (wp, ndomain) + 1;
+			    buffilled = wp - buffer;
+			    ++nentries;
+			  }
+			else
+			  {
+			    /* Check that the group has not been
+			       requested before.  */
+			    struct name_list *runp = data.needed_groups;
+			    if (runp != NULL)
+			      while (1)
+				{
+				  if (strcmp (runp->name, data.val.group) == 0)
+				    break;
+
+				  runp = runp->next;
+				  if (runp == data.needed_groups)
+				    {
+				      runp = NULL;
+				      break;
+				    }
+				}
+
+			    if (runp == NULL)
+			      {
+				runp = data.known_groups;
+				while (runp != NULL)
+				  if (strcmp (runp->name, data.val.group) == 0)
+				    break;
+				  else
+				    runp = runp->next;
+				}
+
+			    if (runp == NULL)
+			      {
+				/* A new group is requested.  */
+				size_t namelen = strlen (data.val.group) + 1;
+				struct name_list *newg = alloca (sizeof (*newg)
+								 + namelen);
+				memcpy (newg->name, data.val.group, namelen);
+				if (data.needed_groups == NULL)
+				  data.needed_groups = newg->next = newg;
+				else
+				  {
+				    newg->next = data.needed_groups->next;
+				    data.needed_groups->next = newg;
+				    data.needed_groups = newg;
+				  }
+			      }
+			  }
+		      }
+		    else if (status == NSS_STATUS_UNAVAIL && e == ERANGE)
+		      {
+			size_t newsize = 2 * buflen;
+			if (use_malloc || newsize > 1024 * 1024)
+			  {
+			    buflen = newsize;
+			    char *newbuf = xrealloc (use_malloc
+						     ? buffer : NULL, buflen);
+
+			    buffer = newbuf;
+			    use_malloc = true;
+			  }
+			else
+			  extend_alloca (buffer, buflen, newsize);
+		      }
+		  }
+
+	      enum nss_status (*endfct) (struct __netgrent *);
+	      endfct = __nss_lookup_function (nip, "endnetgrent");
+	      if (endfct != NULL)
+		(void) DL_CALL_FCT (*endfct, (&data));
+
+	      break;
+	    }
+
+	  no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
+				 status, 0);
+	}
+    }
+
+  total = buffilled;
+
+  /* Fill in the dataset.  */
+  dataset = (struct dataset *) buffer;
+  dataset->head.allocsize = total + req->key_len;
+  dataset->head.recsize = total - offsetof (struct dataset, resp);
+  dataset->head.notfound = false;
+  dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
+  dataset->head.usable = true;
+  dataset->head.ttl = db->postimeout;
+  timeout = dataset->head.timeout = time (NULL) + dataset->head.ttl;
+
+  dataset->resp.version = NSCD_VERSION;
+  dataset->resp.found = 1;
+  dataset->resp.nresults = nentries;
+  dataset->resp.result_len = buffilled - sizeof (*dataset);
+
+  assert (buflen - buffilled >= req->key_len);
+  key_copy = memcpy (buffer + buffilled, key, req->key_len);
+  buffilled += req->key_len;
+
+  /* Now we can determine whether on refill we have to create a new
+     record or not.  */
+  if (he != NULL)
+    {
+      assert (fd == -1);
+
+      if (dataset->head.allocsize == dh->allocsize
+	  && dataset->head.recsize == dh->recsize
+	  && memcmp (&dataset->resp, dh->data,
+		     dh->allocsize - offsetof (struct dataset, resp)) == 0)
+	{
+	  /* The data has not changed.  We will just bump the timeout
+	     value.  Note that the new record has been allocated on
+	     the stack and need not be freed.  */
+	  dh->timeout = dataset->head.timeout;
+	  dh->ttl = dataset->head.ttl;
+	  ++dh->nreloads;
+	  dataset = (struct dataset *) dh;
+
+	  goto out;
+	}
+    }
+
+  {
+    struct dataset *newp
+      = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
+    if (__builtin_expect (newp != NULL, 1))
+      {
+	/* Adjust pointer into the memory block.  */
+	key_copy = (char *) newp + (key_copy - buffer);
+
+	dataset = memcpy (newp, dataset, total + req->key_len);
+	cacheable = true;
+
+	if (he != NULL)
+	  /* Mark the old record as obsolete.  */
+	  dh->usable = false;
+      }
+  }
+
+  if (he == NULL && fd != -1)
+    {
+      /* We write the dataset before inserting it to the database
+	 since while inserting this thread might block and so would
+	 unnecessarily let the receiver wait.  */
+    writeout:
+#ifdef HAVE_SENDFILE
+      if (__builtin_expect (db->mmap_used, 1) && cacheable)
+	{
+	  assert (db->wr_fd != -1);
+	  assert ((char *) &dataset->resp > (char *) db->data);
+	  assert ((char *) dataset - (char *) db->head + total
+		  <= (sizeof (struct database_pers_head)
+		      + db->head->module * sizeof (ref_t)
+		      + db->head->data_size));
+# ifndef __ASSUME_SENDFILE
+	  ssize_t written =
+# endif
+	    sendfileall (fd, db->wr_fd, (char *) &dataset->resp
+			 - (char *) db->head, dataset->head.recsize);
+# ifndef __ASSUME_SENDFILE
+	  if (written == -1 && errno == ENOSYS)
+	    goto use_write;
+# endif
+	}
+      else
+	{
+# ifndef __ASSUME_SENDFILE
+	use_write:
+# endif
+#endif
+	  writeall (fd, &dataset->resp, dataset->head.recsize);
+	}
+    }
+
+  if (cacheable)
+    {
+      /* If necessary, we also propagate the data to disk.  */
+      if (db->persistent)
+	{
+	  // XXX async OK?
+	  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
+	  msync ((void *) pval,
+		 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
+		 MS_ASYNC);
+	}
+
+      (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
+			true, db, uid, he == NULL);
+
+      pthread_rwlock_unlock (&db->lock);
+
+      /* Mark the old entry as obsolete.  */
+      if (dh != NULL)
+	dh->usable = false;
+    }
+
+ out:
+  if (use_malloc)
+    free (buffer);
+
+  *resultp = dataset;
+
+  return timeout;
+}
+
+
+static time_t
+addinnetgrX (struct database_dyn *db, int fd, request_header *req,
+	     char *key, uid_t uid, struct hashentry *he,
+	     struct datahead *dh)
+{
+  const char *group = key;
+  key = (char *) rawmemchr (key, '\0') + 1;
+  size_t group_len = key - group - 1;
+  const char *host = *key++ ? key : NULL;
+  if (host != NULL)
+    key = (char *) rawmemchr (key, '\0') + 1;
+  const char *user = *key++ ? key : NULL;
+  if (user != NULL)
+    key = (char *) rawmemchr (key, '\0') + 1;
+  const char *domain = *key++ ? key : NULL;
+
+  if (__builtin_expect (debug_level > 0, 0))
+    {
+      if (he == NULL)
+	dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
+		 group, host ?: "", user ?: "", domain ?: "");
+      else
+	dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
+		 group, host ?: "", user ?: "", domain ?: "");
+    }
+
+  struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
+							    group, group_len,
+							    db, uid);
+  time_t timeout;
+  if (result != NULL)
+    timeout = result->head.timeout;
+  else
+    {
+      request_header req_get =
+	{
+	  .type = GETNETGRENT,
+	  .key_len = group_len
+	};
+      timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
+				 &result);
+    }
+
+  struct indataset
+  {
+    struct datahead head;
+    innetgroup_response_header resp;
+  } *dataset
+      = (struct indataset *) mempool_alloc (db,
+					    sizeof (*dataset) + req->key_len,
+					    1);
+  struct indataset dataset_mem;
+  bool cacheable = true;
+  if (__builtin_expect (dataset == NULL, 0))
+    {
+      cacheable = false;
+      dataset = &dataset_mem;
+    }
+
+  dataset->head.allocsize = sizeof (*dataset) + req->key_len;
+  dataset->head.recsize = sizeof (innetgroup_response_header);
+  dataset->head.notfound = result->head.notfound;
+  dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
+  dataset->head.usable = true;
+  dataset->head.ttl = result->head.ttl;
+  dataset->head.timeout = timeout;
+
+  dataset->resp.version = NSCD_VERSION;
+  dataset->resp.found = result->resp.found;
+  /* Until we find a matching entry the result is 0.  */
+  dataset->resp.result = 0;
+
+  char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
+
+  if (dataset->resp.found)
+    {
+      const char *triplets = (const char *) (&result->resp + 1);
+
+      for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
+	{
+	  bool success = true;
+
+	  if (host != NULL)
+	    success = strcmp (host, triplets) == 0;
+	  triplets = (const char *) rawmemchr (triplets, '\0') + 1;
+
+	  if (success && user != NULL)
+	    success = strcmp (user, triplets) == 0;
+	  triplets = (const char *) rawmemchr (triplets, '\0') + 1;
+
+	  if (success && (domain == NULL || strcmp (domain, triplets) == 0))
+	    {
+	      dataset->resp.result = 1;
+	      break;
+	    }
+	  triplets = (const char *) rawmemchr (triplets, '\0') + 1;
+	}
+    }
+
+  if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
+    {
+      /* The data has not changed.  We will just bump the timeout
+	 value.  Note that the new record has been allocated on
+	 the stack and need not be freed.  */
+      dh->timeout = timeout;
+      dh->ttl = dataset->head.ttl;
+      ++dh->nreloads;
+      return timeout;
+    }
+
+  if (he == NULL)
+    {
+      /* We write the dataset before inserting it to the database
+	 since while inserting this thread might block and so would
+	 unnecessarily let the receiver wait.  */
+       assert (fd != -1);
+
+#ifdef HAVE_SENDFILE
+      if (__builtin_expect (db->mmap_used, 1) && cacheable)
+	{
+	  assert (db->wr_fd != -1);
+	  assert ((char *) &dataset->resp > (char *) db->data);
+	  assert ((char *) dataset - (char *) db->head + sizeof (*dataset)
+		  <= (sizeof (struct database_pers_head)
+		      + db->head->module * sizeof (ref_t)
+		      + db->head->data_size));
+# ifndef __ASSUME_SENDFILE
+	  ssize_t written =
+# endif
+	    sendfileall (fd, db->wr_fd,
+			 (char *) &dataset->resp - (char *) db->head,
+			 sizeof (innetgroup_response_header));
+# ifndef __ASSUME_SENDFILE
+	  if (written == -1 && errno == ENOSYS)
+	    goto use_write;
+# endif
+	}
+      else
+	{
+# ifndef __ASSUME_SENDFILE
+	use_write:
+# endif
+#endif
+	  writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
+	}
+    }
+
+  if (cacheable)
+    {
+      /* If necessary, we also propagate the data to disk.  */
+      if (db->persistent)
+	{
+	  // XXX async OK?
+	  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
+	  msync ((void *) pval,
+		 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
+		 + req->key_len,
+		 MS_ASYNC);
+	}
+
+      (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
+			true, db, uid, he == NULL);
+
+      pthread_rwlock_unlock (&db->lock);
+
+      /* Mark the old entry as obsolete.  */
+      if (dh != NULL)
+	dh->usable = false;
+    }
+
+  return timeout;
+}
+
+
+void
+addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
+		void *key, uid_t uid)
+{
+  struct dataset *ignore;
+
+  addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
+}
+
+
+time_t
+readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
+		  struct datahead *dh)
+{
+  request_header req =
+    {
+      .type = GETNETGRENT,
+      .key_len = he->len
+    };
+  struct dataset *ignore;
+
+  return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
+			  &ignore);
+}
+
+
+void
+addinnetgr (struct database_dyn *db, int fd, request_header *req,
+	    void *key, uid_t uid)
+{
+  addinnetgrX (db, fd, req, key, uid, NULL, NULL);
+}
+
+
+time_t
+readdinnetgr (struct database_dyn *db, struct hashentry *he,
+	      struct datahead *dh)
+{
+  request_header req =
+    {
+      .type = INNETGR,
+      .key_len = he->len
+    };
+
+  return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);
+}
diff --git a/nscd/nscd-client.h b/nscd/nscd-client.h
index 482b052d16..caad26a9cc 100644
--- a/nscd/nscd-client.h
+++ b/nscd/nscd-client.h
@@ -70,6 +70,9 @@ typedef enum
   GETSERVBYNAME,
   GETSERVBYPORT,
   GETFDSERV,
+  GETNETGRENT,
+  INNETGR,
+  GETFDNETGR,
   LASTREQ
 } request_type;
 
@@ -171,6 +174,24 @@ typedef struct
 } serv_response_header;
 
 
+/* Structure send in reply to netgroup query.  Note that this struct is
+   sent also if the service is disabled or there is no record found.  */
+typedef struct
+{
+  int32_t version;
+  int32_t found;
+  nscd_ssize_t nresults;
+  nscd_ssize_t result_len;
+} netgroup_response_header;
+
+typedef struct
+{
+  int32_t version;
+  int32_t found;
+  int32_t result;
+} innetgroup_response_header;
+
+
 /* Type for offsets in data part of database.  */
 typedef uint32_t ref_t;
 /* Value for invalid/no reference.  */
@@ -210,6 +231,8 @@ struct datahead
     ai_response_header aidata;
     initgr_response_header initgrdata;
     serv_response_header servdata;
+    netgroup_response_header netgroupdata;
+    innetgroup_response_header innetgroupdata;
     nscd_ssize_t align1;
     nscd_time_t align2;
   } data[0];
diff --git a/nscd/nscd.conf b/nscd/nscd.conf
index ada88e6a3d..39b875912d 100644
--- a/nscd/nscd.conf
+++ b/nscd/nscd.conf
@@ -77,3 +77,12 @@
 	persistent		services	yes
 	shared			services	yes
 	max-db-size		services	33554432
+
+	enable-cache		netgroup	yes
+	positive-time-to-live	netgroup	28800
+	negative-time-to-live	netgroup	20
+	suggested-size		netgroup	211
+	check-files		netgroup	yes
+	persistent		netgroup	yes
+	shared			netgroup	yes
+	max-db-size		netgroup	33554432
diff --git a/nscd/nscd.h b/nscd/nscd.h
index c15e88bb6f..fdaf01bfbe 100644
--- a/nscd/nscd.h
+++ b/nscd/nscd.h
@@ -38,6 +38,7 @@ typedef enum
   grpdb,
   hstdb,
   servdb,
+  netgrdb,
   lastdb
 } dbtype;
 
@@ -116,6 +117,7 @@ struct database_dyn
 #define _PATH_NSCD_GROUP_DB	"/var/db/nscd/group"
 #define _PATH_NSCD_HOSTS_DB	"/var/db/nscd/hosts"
 #define _PATH_NSCD_SERVICES_DB	"/var/db/nscd/services"
+#define _PATH_NSCD_NETGROUP_DB	"/var/db/nscd/netgroup"
 
 /* Path used when not using persistent storage.  */
 #define _PATH_NSCD_XYZ_DB_TMP	"/var/run/nscd/dbXXXXXX"
@@ -149,6 +151,7 @@ extern const struct iovec pwd_iov_disabled;
 extern const struct iovec grp_iov_disabled;
 extern const struct iovec hst_iov_disabled;
 extern const struct iovec serv_iov_disabled;
+extern const struct iovec netgroup_iov_disabled;
 
 
 /* Initial number of threads to run.  */
@@ -197,6 +200,11 @@ extern gid_t old_gid;
 
 /* Prototypes for global functions.  */
 
+/* Wrapper functions with error checking for standard functions.  */
+extern void *xmalloc (size_t n);
+extern void *xcalloc (size_t n, size_t s);
+extern void *xrealloc (void *o, size_t n);
+
 /* nscd.c */
 extern void termination_handler (int signum) __attribute__ ((__noreturn__));
 extern int nscd_open_socket (void);
@@ -216,8 +224,8 @@ extern void send_stats (int fd, struct database_dyn dbs[lastdb]);
 extern int receive_print_stats (void) __attribute__ ((__noreturn__));
 
 /* cache.c */
-extern struct datahead *cache_search (request_type, void *key, size_t len,
-				      struct database_dyn *table,
+extern struct datahead *cache_search (request_type, const void *key,
+				      size_t len, struct database_dyn *table,
 				      uid_t owner);
 extern int cache_add (int type, const void *key, size_t len,
 		      struct datahead *packet, bool first,
@@ -286,6 +294,16 @@ extern void addservbyport (struct database_dyn *db, int fd,
 extern time_t readdservbyport (struct database_dyn *db, struct hashentry *he,
 			       struct datahead *dh);
 
+/* netgroupcache.c */
+extern void addinnetgr (struct database_dyn *db, int fd, request_header *req,
+			void *key, uid_t uid);
+extern time_t readdinnetgr (struct database_dyn *db, struct hashentry *he,
+			    struct datahead *dh);
+extern void addgetnetgrent (struct database_dyn *db, int fd,
+			    request_header *req, void *key, uid_t uid);
+extern time_t readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
+				struct datahead *dh);
+
 /* mem.c */
 extern void *mempool_alloc (struct database_dyn *db, size_t len,
 			    int data_alloc);
diff --git a/nscd/nscd_conf.c b/nscd/nscd_conf.c
index 3b6cbb0b15..98b59280c6 100644
--- a/nscd/nscd_conf.c
+++ b/nscd/nscd_conf.c
@@ -43,7 +43,8 @@ const char *const dbnames[lastdb] =
   [pwddb] = "passwd",
   [grpdb] = "group",
   [hstdb] = "hosts",
-  [servdb] = "services"
+  [servdb] = "services",
+  [netgrdb] = "netgroup"
 };
 
 
diff --git a/nscd/nscd_netgroup.c b/nscd/nscd_netgroup.c
new file mode 100644
index 0000000000..8457cec9d3
--- /dev/null
+++ b/nscd/nscd_netgroup.c
@@ -0,0 +1,290 @@
+/* Copyright (C) 2011 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Ulrich Drepper <drepper@gmail.com>, 2011.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#include <alloca.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <not-cancel.h>
+
+#include "nscd-client.h"
+#include "nscd_proto.h"
+
+int __nss_not_use_nscd_netgroup;
+
+
+libc_locked_map_ptr (static, map_handle);
+/* Note that we only free the structure if necessary.  The memory
+   mapping is not removed since it is not visible to the malloc
+   handling.  */
+libc_freeres_fn (pw_map_free)
+{
+  if (map_handle.mapped != NO_MAPPING)
+    {
+      void *p = map_handle.mapped;
+      map_handle.mapped = NO_MAPPING;
+      free (p);
+    }
+}
+
+
+int
+__nscd_setnetgrent (const char *group, struct __netgrent *datap)
+{
+  int gc_cycle;
+  int nretries = 0;
+  size_t group_len = strlen (group);
+
+  /* If the mapping is available, try to search there instead of
+     communicating with the nscd.  */
+  struct mapped_database *mapped;
+  mapped = __nscd_get_map_ref (GETFDNETGR, "netgroup", &map_handle, &gc_cycle);
+
+ retry:;
+  char *respdata = NULL;
+  int retval = -1;
+  netgroup_response_header netgroup_resp;
+
+  if (mapped != NO_MAPPING)
+    {
+      struct datahead *found = __nscd_cache_search (GETNETGRENT, group,
+						    group_len, mapped,
+						    sizeof netgroup_resp);
+      if (found != NULL)
+	{
+	  respdata = (char *) (&found->data[0].netgroupdata + 1);
+	  netgroup_resp = found->data[0].netgroupdata;
+	  /* Now check if we can trust pw_resp fields.  If GC is
+	     in progress, it can contain anything.  */
+	  if (mapped->head->gc_cycle != gc_cycle)
+	    {
+	      retval = -2;
+	      goto out;
+	    }
+	}
+    }
+
+  int sock = -1;
+  if (respdata == NULL)
+    {
+      sock = __nscd_open_socket (group, group_len, GETNETGRENT,
+				 &netgroup_resp, sizeof (netgroup_resp));
+      if (sock == -1)
+	{
+	  /* nscd not running or wrong version.  */
+	  __nss_not_use_nscd_netgroup = 1;
+	  goto out;
+	}
+    }
+
+  if (netgroup_resp.found == 1)
+    {
+      size_t datalen = netgroup_resp.result_len;
+
+      /* If we do not have to read the data here it comes from the
+	 mapped data and does not have to be freed.  */
+      if (respdata == NULL)
+	{
+	  /* The data will come via the socket.  */
+	  respdata = malloc (datalen);
+	  if (respdata == NULL)
+	    goto out_close;
+
+	  if ((size_t) __readall (sock, respdata, datalen) != datalen)
+	    {
+	      free (respdata);
+	      goto out_close;
+	    }
+	}
+
+      datap->data = respdata;
+      datap->data_size = datalen;
+      datap->cursor = respdata;
+      datap->first = 1;
+      datap->nip = (service_user *) -1l;
+      datap->known_groups = NULL;
+      datap->needed_groups = NULL;
+
+      retval = 1;
+    }
+  else
+    {
+      if (__builtin_expect (netgroup_resp.found == -1, 0))
+	{
+	  /* The daemon does not cache this database.  */
+	  __nss_not_use_nscd_netgroup = 1;
+	  goto out_close;
+	}
+
+      /* Set errno to 0 to indicate no error, just no found record.  */
+      __set_errno (0);
+      /* Even though we have not found anything, the result is zero.  */
+      retval = 0;
+    }
+
+ out_close:
+  if (sock != -1)
+    close_not_cancel_no_status (sock);
+ out:
+  if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+    {
+      /* When we come here this means there has been a GC cycle while we
+	 were looking for the data.  This means the data might have been
+	 inconsistent.  Retry if possible.  */
+      if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+	{
+	  /* nscd is just running gc now.  Disable using the mapping.  */
+	  if (atomic_decrement_val (&mapped->counter) == 0)
+	    __nscd_unmap (mapped);
+	  mapped = NO_MAPPING;
+	}
+
+      if (retval != -1)
+	goto retry;
+    }
+
+  return retval;
+}
+
+
+int
+__nscd_innetgr (const char *netgroup, const char *host, const char *user,
+		const char *domain)
+{
+  size_t key_len = (strlen (netgroup) + strlen (host ?: "")
+		    + strlen (user ?: "") + strlen (domain ?: "") + 7);
+  char *key;
+  bool use_alloca = __libc_use_alloca (key_len);
+  if (use_alloca)
+    key = alloca (key_len);
+  else
+    {
+      key = malloc (key_len);
+      if (key == NULL)
+	return -1;
+    }
+  char *wp = stpcpy (key, netgroup) + 1;
+  if (host != NULL)
+    {
+      *wp++ = '\1';
+      wp = stpcpy (wp, host) + 1;
+    }
+  else
+    *wp++ = '\0';
+  if (user != NULL)
+    {
+      *wp++ = '\1';
+      wp = stpcpy (wp, user) + 1;
+    }
+  else
+    *wp++ = '\0';
+  if (domain != NULL)
+    {
+      *wp++ = '\1';
+      wp = stpcpy (wp, domain) + 1;
+    }
+  else
+    *wp++ = '\0';
+  key_len = wp - key;
+
+  /* If the mapping is available, try to search there instead of
+     communicating with the nscd.  */
+  int gc_cycle;
+  int nretries = 0;
+  struct mapped_database *mapped;
+  mapped = __nscd_get_map_ref (GETFDNETGR, "netgroup", &map_handle, &gc_cycle);
+
+ retry:;
+  int retval = -1;
+  innetgroup_response_header innetgroup_resp;
+  int sock = -1;
+
+  if (mapped != NO_MAPPING)
+    {
+      struct datahead *found = __nscd_cache_search (INNETGR, key,
+						    key_len, mapped,
+						    sizeof innetgroup_resp);
+      if (found != NULL)
+	{
+	  innetgroup_resp = found->data[0].innetgroupdata;
+	  /* Now check if we can trust pw_resp fields.  If GC is
+	     in progress, it can contain anything.  */
+	  if (mapped->head->gc_cycle != gc_cycle)
+	    {
+	      retval = -2;
+	      goto out;
+	    }
+
+	  goto found_entry;
+	}
+    }
+
+  sock = __nscd_open_socket (key, key_len, INNETGR,
+			     &innetgroup_resp, sizeof (innetgroup_resp));
+  if (sock == -1)
+    {
+      /* nscd not running or wrong version.  */
+      __nss_not_use_nscd_netgroup = 1;
+      goto out;
+    }
+
+ found_entry:
+  if (innetgroup_resp.found == 1)
+    retval = innetgroup_resp.result;
+  else
+    {
+      if (__builtin_expect (innetgroup_resp.found == -1, 0))
+	{
+	  /* The daemon does not cache this database.  */
+	  __nss_not_use_nscd_netgroup = 1;
+	  goto out_close;
+	}
+
+      /* Set errno to 0 to indicate no error, just no found record.  */
+      __set_errno (0);
+      /* Even though we have not found anything, the result is zero.  */
+      retval = 0;
+    }
+
+ out_close:
+  if (sock != -1)
+    close_not_cancel_no_status (sock);
+ out:
+  if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
+    {
+      /* When we come here this means there has been a GC cycle while we
+	 were looking for the data.  This means the data might have been
+	 inconsistent.  Retry if possible.  */
+      if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
+	{
+	  /* nscd is just running gc now.  Disable using the mapping.  */
+	  if (atomic_decrement_val (&mapped->counter) == 0)
+	    __nscd_unmap (mapped);
+	  mapped = NO_MAPPING;
+	}
+
+      if (retval != -1)
+	goto retry;
+    }
+
+  if (! use_alloca)
+    free (key);
+
+  return retval;
+}
diff --git a/nscd/nscd_proto.h b/nscd/nscd_proto.h
index 573ca2b2e7..8aa29732ef 100644
--- a/nscd/nscd_proto.h
+++ b/nscd/nscd_proto.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 1998-2000, 2002, 2004, 2007 Free Software Foundation, Inc.
+/* Copyright (C) 1998-2000,2002,2004,2007,2011 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
 
@@ -36,6 +36,7 @@ extern int __nss_not_use_nscd_passwd attribute_hidden;
 extern int __nss_not_use_nscd_group attribute_hidden;
 extern int __nss_not_use_nscd_hosts attribute_hidden;
 extern int __nss_not_use_nscd_services attribute_hidden;
+extern int __nss_not_use_nscd_netgroup attribute_hidden;
 
 extern int __nscd_getpwnam_r (const char *name, struct passwd *resultbuf,
 			      char *buffer, size_t buflen,
@@ -71,5 +72,7 @@ extern int __nscd_getservbyname_r (const char *name, const char *proto,
 extern int __nscd_getservbyport_r (int port, const char *proto,
 				   struct servent *result_buf, char *buf,
 				   size_t buflen, struct servent **result);
+extern int __nscd_innetgr (const char *netgroup, const char *host,
+			   const char *user, const char *domain);
 
 #endif /* _NSCD_PROTO_H */
diff --git a/nscd/selinux.c b/nscd/selinux.c
index e07a454bf8..f618640dd6 100644
--- a/nscd/selinux.c
+++ b/nscd/selinux.c
@@ -1,5 +1,5 @@
 /* SELinux access controls for nscd.
-   Copyright (C) 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
+   Copyright (C) 2004,2005,2006,2007,2009,2011 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Matthew Rickard <mjricka@epoch.ncsc.mil>, 2004.
 
@@ -46,7 +46,7 @@
 int selinux_enabled;
 
 /* Define mappings of access vector permissions to request types.  */
-static const int perms[LASTREQ] =
+static const access_vector_t perms[LASTREQ] =
 {
   [GETPWBYNAME] = NSCD__GETPWD,
   [GETPWBYUID] = NSCD__GETPWD,
@@ -69,6 +69,11 @@ static const int perms[LASTREQ] =
   [GETSERVBYPORT] = NSCD__GETSERV,
   [GETFDSERV] = NSCD__SHMEMSERV,
 #endif
+#ifdef NSCD__GETNETGRP
+  [GETNETGRENT] = NSCD__GETNETGRP,
+  [INNETGR] = NSCD__GETNETGRP,
+  [GETFDNETGR] = NSCD__SHMEMNETGRP,
+#endif
 };
 
 /* Store an entry ref to speed AVC decisions.  */