Fix broken up NIS groups for compat NSS module.
authorUlrich Drepper <drepper@redhat.com>
Sun, 21 Jun 2009 03:39:19 +0000 (20:39 -0700)
committerUlrich Drepper <drepper@redhat.com>
Sun, 21 Jun 2009 03:39:19 +0000 (20:39 -0700)
The check for the inclusion of a group in the result gave up too early
in case of broken-up NIS groups.  We now fall back automatically to
the slow mode of using getgrent_r.  As an optimization, if there is
not blacklist we need not perform the check in the first place and
therefore can just accept the results of the initgroups_dyn callback.

ChangeLog
nis/nss_compat/compat-initgroups.c

index 8dac4e0..5ae6e3b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2009-06-20  Ulrich Drepper  <drepper@redhat.com>
+
+       [BZ #10085]
+       * nis/nss_compat/compat-initgroups.c (nss_setgrent): New variable.
+       (nss_endgrent): New variable.
+       (struct ent_t): Add need_endgrent and skip_initgroups_dyn
+       fields. Change type of files to bool and adjust all users.
+       (init_nss_interface): Initialize nss_setgrent and nss_endgrent.
+       (internal_endgrent): Call nss_endgrent if necessary.
+       (add_group): New function.  Broken out of...
+       (check_and_add_group): ...here.
+       (getgrent_next_nss): Remove test that any callback is available.
+       Use skip_initgroups_dyn to determine whether to use initgroups_dyn
+       callback.  If there is no blacklist we can trust the results returned
+       by the initgroups_dyn callback.  In case there is a callback and we
+       find a group entry for the group ID but it doesn't contain the
+       correct member, switch to the slow mode and use getgrent_r.
+       (internal_getgrent_r): When we see a +: entry, determine whether
+       there is any callback and which we can use the initgroups_dyn
+       callback.
+
 2009-06-18  Ulrich Drepper  <drepper@redhat.com>
 
        * malloc/malloc.c (_int_malloc): Add some consistency checks.
index 76ca95d..07a3b92 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 1998-2004, 2006, 2007 Free Software Foundation, Inc.
+/* Copyright (C) 1998-2004, 2006, 2007, 2009 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
 
@@ -43,8 +43,10 @@ static enum nss_status (*nss_getgrnam_r) (const char *name,
 static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
                                          char *buffer, size_t buflen,
                                          int *errnop);
+static enum nss_status (*nss_setgrent) (int stayopen);
 static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
                                          size_t buflen, int *errnop);
+static enum nss_status (*nss_endgrent) (void);
 
 /* Protect global state against multiple changers.  */
 __libc_lock_define_initialized (static, lock)
@@ -68,7 +70,9 @@ struct blacklist_t
 
 struct ent_t
 {
-  bool_t files;
+  bool files;
+  bool need_endgrent;
+  bool skip_initgroups_dyn;
   FILE *stream;
   struct blacklist_t blacklist;
 };
@@ -106,7 +110,9 @@ init_nss_interface (void)
       nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
       nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
       nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
+      nss_setgrent = __nss_lookup_function (ni, "setgrent");
       nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
+      nss_endgrent = __nss_lookup_function (ni, "endgrent");
     }
 
   __libc_lock_unlock (lock);
@@ -117,7 +123,7 @@ internal_setgrent (ent_t *ent)
 {
   enum nss_status status = NSS_STATUS_SUCCESS;
 
-  ent->files = TRUE;
+  ent->files = true;
 
   if (ni == NULL)
     init_nss_interface ();
@@ -195,54 +201,68 @@ internal_endgrent (ent_t *ent)
   else
     ent->blacklist.current = 0;
 
+  if (ent->need_endgrent && nss_endgrent != NULL)
+    nss_endgrent ();
+
   return NSS_STATUS_SUCCESS;
 }
 
-/* This function checks, if the user is a member of this group and if
-   yes, add the group id to the list.  */
+/* Add new group record.  */
 static void
+add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
+          gid_t gid)
+{
+  gid_t *groups = *groupsp;
+
+  /* Matches user.  Insert this group.  */
+  if (__builtin_expect (*start == *size, 0))
+    {
+      /* Need a bigger buffer.  */
+      gid_t *newgroups;
+      long int newsize;
+
+      if (limit > 0 && *size == limit)
+       /* We reached the maximum.  */
+       return;
+
+      if (limit <= 0)
+       newsize = 2 * *size;
+      else
+       newsize = MIN (limit, 2 * *size);
+
+      newgroups = realloc (groups, newsize * sizeof (*groups));
+      if (newgroups == NULL)
+       return;
+      *groupsp = groups = newgroups;
+      *size = newsize;
+    }
+
+  groups[*start] = gid;
+  *start += 1;
+}
+
+/* This function checks, if the user is a member of this group and if
+   yes, add the group id to the list.  Return nonzero is we couldn't
+   handle the group because the user is not in the member list.  */
+static int
 check_and_add_group (const char *user, gid_t group, long int *start,
                     long int *size, gid_t **groupsp, long int limit,
                     struct group *grp)
 {
-  gid_t *groups = *groupsp;
   char **member;
 
   /* Don't add main group to list of groups.  */
   if (grp->gr_gid == group)
-    return;
+    return 0;
 
   for (member = grp->gr_mem; *member != NULL; ++member)
     if (strcmp (*member, user) == 0)
       {
-       /* Matches user.  Insert this group.  */
-       if (*start == *size)
-         {
-           /* Need a bigger buffer.  */
-           gid_t *newgroups;
-           long int newsize;
-
-           if (limit > 0 && *size == limit)
-             /* We reached the maximum.  */
-             return;
-
-           if (limit <= 0)
-             newsize = 2 * *size;
-           else
-             newsize = MIN (limit, 2 * *size);
-
-           newgroups = realloc (groups, newsize * sizeof (*groups));
-           if (newgroups == NULL)
-             return;
-           *groupsp = groups = newgroups;
-           *size = newsize;
-         }
-
-       groups[*start] = grp->gr_gid;
-       *start += 1;
-
-       break;
+       add_group (start, size, groupsp, limit, grp->gr_gid);
+       return 0;
       }
+
+  return 1;
 }
 
 /* Get the next group from NSS  (+ entry). If the NSS module supports
@@ -255,15 +275,10 @@ getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
   enum nss_status status;
   struct group grpbuf;
 
-  /* if this module does not support getgrent_r and initgroups_dyn,
-     abort. We cannot find the needed group entries.  */
-  if (nss_getgrent_r == NULL && nss_initgroups_dyn == NULL)
-    return NSS_STATUS_UNAVAIL;
-
   /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
      If this function is not supported, step through the whole group
      database with getgrent_r.  */
-  if (nss_initgroups_dyn && nss_getgrgid_r)
+  if (! ent->skip_initgroups_dyn)
     {
       long int mystart = 0;
       long int mysize = limit <= 0 ? *size : limit;
@@ -282,39 +297,56 @@ getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
       if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
                              limit, errnop) == NSS_STATUS_SUCCESS)
        {
-         /* A temporary buffer. We use the normal buffer, until we find
-            an entry, for which this buffer is to small.  In this case, we
-            overwrite the pointer with one to a bigger buffer.  */
-         char *tmpbuf = buffer;
-         size_t tmplen = buflen;
-         int i;
-
-         for (i = 0; i < mystart; i++)
+         /* If there is no blacklist we can trust the underlying
+            initgroups implementation.  */
+         if (ent->blacklist.current <= 1)
+           for (int i = 0; i < mystart; i++)
+             add_group (start, size, groupsp, limit, mygroups[i]);
+         else
            {
-             while ((status = nss_getgrgid_r (mygroups[i], &grpbuf, tmpbuf,
-                                              tmplen,
-                                              errnop)) == NSS_STATUS_TRYAGAIN
-                    && *errnop == ERANGE)
-               if (tmpbuf == buffer)
-                 {
-                   tmplen *= 2;
-                   tmpbuf = __alloca (tmplen);
-                 }
-               else
-                 tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen);
-
-             if (__builtin_expect  (status != NSS_STATUS_NOTFOUND, 1))
+             /* A temporary buffer. We use the normal buffer, until we find
+                an entry, for which this buffer is to small.  In this case, we
+                overwrite the pointer with one to a bigger buffer.  */
+             char *tmpbuf = buffer;
+             size_t tmplen = buflen;
+
+             for (int i = 0; i < mystart; i++)
                {
-                 if (__builtin_expect  (status != NSS_STATUS_SUCCESS, 0))
+                 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf,
+                                                  tmpbuf, tmplen, errnop))
+                        == NSS_STATUS_TRYAGAIN
+                        && *errnop == ERANGE)
+                   if (tmpbuf == buffer)
+                     {
+                       tmplen *= 2;
+                       tmpbuf = __alloca (tmplen);
+                     }
+                   else
+                     tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen);
+
+                 if (__builtin_expect  (status != NSS_STATUS_NOTFOUND, 1))
                    {
-                     free (mygroups);
-                     return status;
+                     if (__builtin_expect  (status != NSS_STATUS_SUCCESS, 0))
+                       {
+                         free (mygroups);
+                         return status;
+                       }
+
+                     if (!in_blacklist (grpbuf.gr_name,
+                                        strlen (grpbuf.gr_name), ent)
+                         && check_and_add_group (user, group, start, size,
+                                                 groupsp, limit, &grpbuf))
+                       {
+                         if (nss_setgrent != NULL)
+                           {
+                             nss_setgrent (1);
+                             ent->need_endgrent = true;
+                           }
+                         ent->skip_initgroups_dyn = true;
+
+                         goto iter;
+                       }
                    }
-
-                 if (!in_blacklist (grpbuf.gr_name,
-                                    strlen (grpbuf.gr_name), ent))
-                   check_and_add_group (user, group, start, size, groupsp,
-                                        limit, &grpbuf);
                }
            }
 
@@ -327,17 +359,21 @@ getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
     }
 
   /* If we come here, the NSS module does not support initgroups_dyn
-     and we have to step through the whole list ourself.  */
+     or we were confronted with a split group.  In these cases we have
+     to step through the whole list ourself.  */
+ iter:
   do
     {
       if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
          NSS_STATUS_SUCCESS)
-       return status;
+       break;
     }
   while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
 
-  check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
-  return NSS_STATUS_SUCCESS;
+  if (status == NSS_STATUS_SUCCESS)
+    check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
+
+  return status;
 }
 
 static enum nss_status
@@ -435,7 +471,21 @@ internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
       /* +:... */
       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
        {
-         ent->files = FALSE;
+         /* If the selected module does not support getgrent_r or
+            initgroups_dyn, abort. We cannot find the needed group
+            entries.  */
+         if (nss_getgrent_r == NULL && nss_initgroups_dyn == NULL)
+           return NSS_STATUS_UNAVAIL;
+
+         ent->files = false;
+
+         if (nss_initgroups_dyn == NULL && nss_setgrent != NULL)
+           {
+             nss_setgrent (1);
+             ent->need_endgrent = true;
+           }
+         ent->skip_initgroups_dyn = true;
+
          return getgrent_next_nss (ent, buffer, buflen, user, group,
                                    start, size, groupsp, limit, errnop);
        }
@@ -455,7 +505,7 @@ _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
   size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
   char *tmpbuf;
   enum nss_status status;
-  ent_t intern = { TRUE, NULL, {NULL, 0, 0} };
+  ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
 
   status = internal_setgrent (&intern);
   if (status != NSS_STATUS_SUCCESS)