about summary refs log tree commit diff
path: root/inet/getnetgrent_r.c
diff options
context:
space:
mode:
Diffstat (limited to 'inet/getnetgrent_r.c')
-rw-r--r--inet/getnetgrent_r.c263
1 files changed, 222 insertions, 41 deletions
diff --git a/inet/getnetgrent_r.c b/inet/getnetgrent_r.c
index 52a90d055c..ff6d5395ed 100644
--- a/inet/getnetgrent_r.c
+++ b/inet/getnetgrent_r.c
@@ -31,6 +31,16 @@ static service_user *nip;
 /* Remember the first service_entry, it's always the same.  */
 static service_user *startp;
 
+/* A netgroup can consist of names of other netgroups.  We have to
+   track which netgroups were read and which still have to be read.  */
+struct name_list
+{
+  const char *name;
+  struct name_list *next;
+};
+struct name_list *known_groups;
+struct name_list *needed_groups;
+
 
 /* The lookup function for the first entry of this service.  */
 extern int __nss_netgroup_lookup (service_user **nip, const char *name,
@@ -62,30 +72,79 @@ setup (void **fctp, const char *func_name, int all)
   return no_more;
 }
 
-int
-setnetgrent (const char *group)
+/* Free used memory.  */
+static void
+free_memory (void)
+{
+  while (known_groups != NULL)
+    {
+      struct name_list *tmp = known_groups;
+      known_groups = known_groups->next;
+      free (tmp->name);
+      free (tmp);
+    }
+
+  while (needed_groups != NULL)
+    {
+      struct name_list *tmp = needed_groups;
+      needed_groups = needed_groups->next;
+      free (tmp->name);
+      free (tmp);
+    }
+}
+
+static int
+internal_setnetgrent (const char *group)
 {
   enum nss_status (*fct) (const char *);
   enum nss_status status = NSS_STATUS_UNAVAIL;
+  struct name_list *new_elem;
   int no_more;
 
-  __libc_lock_lock (lock);
-
   /* Cycle through all the services and run their setnetgrent functions.  */
   no_more = setup ((void **) &fct, "setnetgrent", 1);
   while (! no_more)
     {
-      /* Ignore status, we force check in __NSS_NEXT.  */
+      /* Ignore status, we force check in `__nss_next'.  */
       status = (*fct) (group);
 
       no_more = __nss_next (&nip, "setnetgrent", (void **) &fct, status, 0);
     }
 
-  __libc_lock_unlock (lock);
+  /* Add the current group to the list of known groups.  */
+  new_elem = (struct name_list *) malloc (sizeof (struct name_list));
+  if (new_elem == NULL || (new_elem->name = strdup (group)) == NULL)
+    {
+      if (new_elem != NULL)
+	free (new_elem);
+      status == NSS_STATUS_UNAVAIL;
+    }
+  else
+    {
+      new_elem->next = known_groups;
+      known_groups = new_elem;
+    }
 
   return status == NSS_STATUS_SUCCESS;
 }
 
+int
+setnetgrent (const char *group)
+{
+  int result;
+
+  __libc_lock_lock (lock);
+
+  /* Free list of all netgroup names from last run.  */
+  free_memory ();
+
+  result = internal_setnetgrent (group);
+
+  __libc_lock_unlock (lock);
+
+  return result;
+}
+
 
 void
 endnetgrent (void)
@@ -103,13 +162,16 @@ endnetgrent (void)
   no_more = setup ((void **) &fct, "endnetgrent", 1);
   while (! no_more)
     {
-      /* Ignore status, we force check in __NSS_NEXT.  */
+      /* Ignore status, we force check in `__nss_next'.  */
       (void) (*fct) ();
 
       no_more = (nip == old_nip
 		 || __nss_next (&nip, "endnetgrent", (void **) &fct, 0, 1));
     }
 
+  /* Now free list of all netgroup names from last run.  */
+  free_memory ();
+
   __libc_lock_unlock (lock);
 }
 
@@ -135,14 +197,63 @@ __getnetgrent_r (char **hostp, char **userp, char **domainp,
     {
       status = (*fct) (&result, buffer, buflen);
 
+      if (status == NSS_STATUS_RETURN)
+	{
+	  /* This was the last one for this group.  Look at next group
+	     if available.  */
+	  int found = 0;
+	  while (needed_groups != NULL && ! found)
+	    {
+	      struct name_list *tmp = needed_groups;
+	      needed_groups = needed_groups->next;
+	      tmp->next = known_groups;
+	      known_groups = tmp;
+
+	      found = internal_setnetgrent (known_groups->name);
+	    }
+
+	  if (found)
+	    continue;
+	}
+      else if (status == NSS_STATUS_SUCCESS && result.type == group_val)
+	{
+	  /* The last entry was a name of another netgroup.  */
+	  struct name_list *namep;
+
+	  /* Ignore if we've seen the name before.  */
+	  for (namep = known_groups; namep != NULL; namep = namep->next)
+	    if (strcmp (result.val.group, namep->name) == 0)
+	      break;
+	  if (namep != NULL)
+	    /* Really ignore.  */
+	    continue;
+
+	  namep = (struct name_list *) malloc (sizeof (struct name_list));
+	  if (namep == NULL
+	      || (namep->name = strdup (result.val.group)) == NULL)
+	    {
+	      /* We are out of memory.  */
+	      if (namep != NULL)
+		free (namep);
+	      status = NSS_STATUS_RETURN;
+	    }
+	  else
+	    {
+	      namep->next = needed_groups;
+	      needed_groups = namep;
+	      /* And get the next entry.  */
+	      continue;
+	    }
+	}
+
       no_more = __nss_next (&nip, "getnetgrent_r", (void **) &fct, status, 0);
     }
 
   if (status == NSS_STATUS_SUCCESS)
     {
-      *hostp = result.host;
-      *userp = result.user;
-      *domainp = result.domain;
+      *hostp = result.val.triple.host;
+      *userp = result.val.triple.user;
+      *domainp = result.val.triple.domain;
     }
 
   __libc_lock_unlock (lock);
@@ -161,6 +272,10 @@ innetgr (const char *netgroup, const char *host, const char *user,
   int (*getfct) (struct __netgrent *, char *, int);
   int result = 0;
   int no_more;
+  struct name_list *known = NULL;
+  struct name_list *needed = NULL;
+  const char *current_group = netgroup;
+  int real_entry = 0;
 
   __libc_lock_lock (lock);
 
@@ -168,51 +283,117 @@ innetgr (const char *netgroup, const char *host, const char *user,
      not work further.  We can do some optimization here.  Since all
      services must provide the `setnetgrent' function we can do all
      the work during one walk through the service list.  */
-  no_more = setup ((void **) &setfct, "setnetgrent", 1);
-  while (! no_more)
+  while (1)
     {
-      enum nss_status status;
-
-      /* Open netgroup.  */
-      status = (*setfct) (netgroup);
-      if (status == NSS_STATUS_SUCCESS
-	  && __nss_lookup (&nip, "getnetgrent_r", (void **) &getfct) == 0)
+      no_more = setup ((void **) &setfct, "setnetgrent", 1);
+      while (! no_more)
 	{
-	  char buffer[1024];
-	  struct __netgrent entry;
+	  enum nss_status status;
 
-	  while ((*getfct) (&entry, buffer, sizeof buffer)
-		 == NSS_STATUS_SUCCESS)
+	  /* Open netgroup.  */
+	  status = (*setfct) (current_group);
+	  if (status == NSS_STATUS_SUCCESS
+	      && __nss_lookup (&nip, "getnetgrent_r", (void **) &getfct) == 0)
 	    {
-	      if ((entry.host == NULL || host == NULL
-		   || strcmp (entry.host, host) == 0)
-		  && (entry.user == NULL || user == NULL
-		      || strcmp (entry.user, user) == 0)
-		  && (entry.domain == NULL || domain == NULL
-		      || strcmp (entry.domain, domain) == 0))
+	      char buffer[1024];
+	      struct __netgrent entry;
+
+	      while ((*getfct) (&entry, buffer, sizeof buffer)
+		     == NSS_STATUS_SUCCESS)
 		{
-		  result = 1;
-		  break;
+		  if (entry.type == group_val)
+		    {
+		      /* Make sure we haven't seen the name before.  */
+		      struct name_list *namep;
+
+		      for (namep = known; namep != NULL; namep = namep->next)
+			if (strcmp (entry.val.group, namep->name) == 0)
+			  break;
+		      if (namep == NULL
+			  && strcmp (netgroup, entry.val.group) != 0)
+			{
+			  namep =
+			    (struct name_list *) malloc (sizeof (*namep));
+			  if (namep == NULL
+			      || ((namep->name = strdup (entry.val.group))
+				  == NULL))
+			    {
+			      /* Out of memory, simply return.  */
+			      if (namep != NULL)
+				free (namep);
+			      result = -1;
+			      break;
+			    }
+
+			  namep->next = needed;
+			  needed = namep;
+			}
+		    }
+		  else
+		    {
+		      real_entry = 1;
+
+		      if ((entry.val.triple.host == NULL || host == NULL
+			   || strcmp (entry.val.triple.host, host) == 0)
+			  && (entry.val.triple.user == NULL || user == NULL
+			      || strcmp (entry.val.triple.user, user) == 0)
+			  && (entry.val.triple.domain == NULL || domain == NULL
+			      || strcmp (entry.val.triple.domain, domain) == 0))
+			{
+			  result = 1;
+			  break;
+			}
+		    }
 		}
+
+	      if (result != 0)
+		break;
+
+	      /* If we found one service which does know the given
+		 netgroup we don't try further.  */
+	      status = NSS_STATUS_RETURN;
 	    }
 
-	  if (result != 0)
-	    break;
+	  /* Free all resources of the service.  */
+	  if (__nss_lookup (&nip, "endnetgrent", (void **) &endfct) == 0)
+	    (*endfct) ();
 
-	  /* If we found one service which does know the given
-	     netgroup we don't try further.  */
-	  status = NSS_STATUS_RETURN;
+	  /* Look for the next service.  */
+	  no_more = __nss_next (&nip, "setnetgrent",
+				(void **) &setfct, status, 0);
 	}
 
-      /* Free all resources of the service.  */
-      if (__nss_lookup (&nip, "endnetgrent", (void **) &endfct) == 0)
-	(*endfct) ();
+      if (result == 0 && needed != NULL)
+	{
+	  struct name_list *tmp = needed;
+	  needed = tmp->next;
+	  tmp->next = known;
+	  known = tmp;
+	  current_group = known->name;
+	  continue;
+	}
 
-      /* Look for the next service.  */
-      no_more = __nss_next (&nip, "setnetgrent", (void **) &setfct, status, 0);
+      /* No way out.  */
+      break;
     }
 
   __libc_lock_unlock (lock);
 
-  return result;
+  /* Free the memory.  */
+  while (known != NULL)
+    {
+      struct name_list *tmp = known;
+      known = known->next;
+      free (tmp->name);
+      free (tmp);
+    }
+  while (needed != NULL)
+    {
+      struct name_list *tmp = needed;
+      needed = needed->next;
+      free (tmp->name);
+      free (tmp);
+    }
+
+  return result == 1;
 }