update from main archive 961113
[kopensolaris-gnu/glibc.git] / inet / getnetgrent_r.c
index 52a90d0..5600716 100644 (file)
@@ -18,6 +18,7 @@ Boston, MA 02111-1307, USA.  */
 
 #include <libc-lock.h>
 #include <netdb.h>
+#include <string.h>
 #include "netgroup.h"
 #include "nsswitch.h"
 
@@ -28,8 +29,10 @@ __libc_lock_define_initialized (static, lock)
 /* This handle for the NSS data base is shared between all
    set/get/endXXXent functions.  */
 static service_user *nip;
-/* Remember the first service_entry, it's always the same.  */
-static service_user *startp;
+
+/* The whole information for the set/get/endnetgrent functions are
+   kept in this structure.  */
+static struct __netgrent dataset;
 
 
 /* The lookup function for the first entry of this service.  */
@@ -42,7 +45,10 @@ extern int __nss_netgroup_lookup (service_user **nip, const char *name,
 static enum nss_status
 setup (void **fctp, const char *func_name, int all)
 {
+  /* Remember the first service_entry, it's always the same.  */
+  static service_user *startp = NULL;
   int no_more;
+
   if (startp == NULL)
     {
       no_more = __nss_netgroup_lookup (&nip, func_name, fctp);
@@ -62,40 +68,93 @@ setup (void **fctp, const char *func_name, int all)
   return no_more;
 }
 \f
-int
-setnetgrent (const char *group)
+/* Free used memory.  */
+static void
+free_memory (struct __netgrent *data)
+{
+  while (data->known_groups != NULL)
+    {
+      struct name_list *tmp = data->known_groups;
+      data->known_groups = data->known_groups->next;
+      free (tmp->name);
+      free (tmp);
+    }
+
+  while (data->needed_groups != NULL)
+    {
+      struct name_list *tmp = data->needed_groups;
+      data->needed_groups = data->needed_groups->next;
+      free (tmp->name);
+      free (tmp);
+    }
+}
+\f
+static int
+__internal_setnetgrent_reuse (const char *group, struct __netgrent *datap)
 {
-  enum nss_status (*fct) (const char *);
+  enum nss_status (*fct) (const char *, struct __netgrent *);
   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.  */
-      status = (*fct) (group);
+      /* Ignore status, we force check in `__nss_next'.  */
+      status = (*fct) (group, datap);
 
       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 = datap->known_groups;
+      datap->known_groups = new_elem;
+    }
 
   return status == NSS_STATUS_SUCCESS;
 }
 
+int
+__internal_setnetgrent (const char *group, struct __netgrent *datap)
+{
+  /* Free list of all netgroup names from last run.  */
+  free_memory (datap);
+
+  return __internal_setnetgrent_reuse (group, datap);
+}
+
+int
+setnetgrent (const char *group)
+{
+  int result;
+
+  __libc_lock_lock (lock);
+
+  result = __internal_setnetgrent (group, &dataset);
+
+  __libc_lock_unlock (lock);
+
+  return result;
+}
+
 
 void
-endnetgrent (void)
+__internal_endnetgrent (struct __netgrent *datap)
 {
   service_user *old_nip;
-  enum nss_status (*fct) (void);
+  enum nss_status (*fct) (struct __netgrent *);
   int no_more;
 
-  __libc_lock_lock (lock);
-
   /* Remember which was the last used service.  */
   old_nip = nip;
 
@@ -103,51 +162,127 @@ endnetgrent (void)
   no_more = setup ((void **) &fct, "endnetgrent", 1);
   while (! no_more)
     {
-      /* Ignore status, we force check in __NSS_NEXT.  */
-      (void) (*fct) ();
+      /* Ignore status, we force check in `__nss_next'.  */
+      (void) (*fct) (datap);
 
       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 (datap);
+}
+
+
+void
+endnetgrent (void)
+{
+  __libc_lock_lock (lock);
+
+  __internal_endnetgrent (&dataset);
+
   __libc_lock_unlock (lock);
 }
 
 
 int
-__getnetgrent_r (char **hostp, char **userp, char **domainp,
-                char *buffer, int buflen)
+__internal_getnetgrent (char **hostp, char **userp, char **domainp,
+                       struct __netgrent *datap,
+                       char *buffer, size_t buflen)
 {
-  enum nss_status (*fct) (struct __netgrent *, char *, int);
-  struct __netgrent result;
+  enum nss_status (*fct) (struct __netgrent *, char *, size_t);
   int no_more;
 
   /* Initialize status to return if no more functions are found.  */
   enum nss_status status = NSS_STATUS_NOTFOUND;
 
-  __libc_lock_lock (lock);
-
   /* Run through available functions, starting with the same function last
      run.  We will repeat each function as long as it succeeds, and then go
      on to the next service action.  */
   no_more = setup ((void **) &fct, "getnetgrent_r", 0);
   while (! no_more)
     {
-      status = (*fct) (&result, buffer, buflen);
+      status = (*fct) (datap, 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 (datap->needed_groups != NULL && ! found)
+           {
+             struct name_list *tmp = datap->needed_groups;
+             datap->needed_groups = datap->needed_groups->next;
+             tmp->next = datap->known_groups;
+             datap->known_groups = tmp;
+
+             found = __internal_setnetgrent_reuse (datap->known_groups->name,
+                                                   datap);
+           }
+
+         if (found)
+           continue;
+       }
+      else if (status == NSS_STATUS_SUCCESS && datap->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 = datap->known_groups; namep != NULL;
+              namep = namep->next)
+           if (strcmp (datap->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 (datap->val.group)) == NULL)
+           {
+             /* We are out of memory.  */
+             if (namep != NULL)
+               free (namep);
+             status = NSS_STATUS_RETURN;
+           }
+         else
+           {
+             namep->next = datap->needed_groups;
+             datap->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 = datap->val.triple.host;
+      *userp = datap->val.triple.user;
+      *domainp = datap->val.triple.domain;
     }
 
+  return status == NSS_STATUS_SUCCESS ? 1 : 0;
+}
+
+/* The real entry point.  */
+int
+__getnetgrent_r (char **hostp, char **userp, char **domainp,
+                char *buffer, size_t buflen)
+{
+  enum nss_status status;
+
+  __libc_lock_lock (lock);
+
+  status = __internal_getnetgrent (hostp, userp, domainp, &dataset,
+                                  buffer, buflen);
+
   __libc_lock_unlock (lock);
 
-  return status == NSS_STATUS_SUCCESS ? 1 : 0;
+  return status;
 }
 weak_alias (__getnetgrent_r, getnetgrent_r)
 \f
@@ -156,63 +291,132 @@ int
 innetgr (const char *netgroup, const char *host, const char *user,
         const char *domain)
 {
-  int (*setfct) (const char *);
-  void (*endfct) (void);
-  int (*getfct) (struct __netgrent *, char *, int);
+  int (*setfct) (const char *, struct __netgrent *);
+  void (*endfct) (struct __netgrent *);
+  int (*getfct) (struct __netgrent *, char *, size_t);
+  struct name_list *known;
+  struct name_list *needed;
   int result = 0;
   int no_more;
-
-  __libc_lock_lock (lock);
+  const char *current_group = netgroup;
+  int real_entry = 0;
 
   /* Walk through the services until we found an answer or we shall
      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];
+         enum nss_status status;
          struct __netgrent entry;
 
-         while ((*getfct) (&entry, buffer, sizeof buffer)
-                == NSS_STATUS_SUCCESS)
+         /* Clear the space for the netgroup data.  */
+         bzero (&entry, sizeof (entry));
+
+         /* Open netgroup.  */
+         status = (*setfct) (current_group, &entry);
+         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];
+
+             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) (&entry);
 
-         /* 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);
+  /* 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;
+  return result == 1;
 }