Back to index

glibc  2.9
compat-initgroups.c
Go to the documentation of this file.
00001 /* Copyright (C) 1998-2004, 2006, 2007 Free Software Foundation, Inc.
00002    This file is part of the GNU C Library.
00003    Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
00004 
00005    The GNU C Library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Lesser General Public
00007    License as published by the Free Software Foundation; either
00008    version 2.1 of the License, or (at your option) any later version.
00009 
00010    The GNU C Library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Lesser General Public License for more details.
00014 
00015    You should have received a copy of the GNU Lesser General Public
00016    License along with the GNU C Library; if not, write to the Free
00017    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
00018    02111-1307 USA.  */
00019 
00020 #include <alloca.h>
00021 #include <ctype.h>
00022 #include <errno.h>
00023 #include <fcntl.h>
00024 #include <grp.h>
00025 #include <nss.h>
00026 #include <stdio_ext.h>
00027 #include <string.h>
00028 #include <unistd.h>
00029 #include <rpc/types.h>
00030 #include <sys/param.h>
00031 #include <nsswitch.h>
00032 #include <bits/libc-lock.h>
00033 #include <kernel-features.h>
00034 
00035 static service_user *ni;
00036 /* Type of the lookup function.  */
00037 static enum nss_status (*nss_initgroups_dyn) (const char *, gid_t,
00038                                          long int *, long int *,
00039                                          gid_t **, long int, int *);
00040 static enum nss_status (*nss_getgrnam_r) (const char *name,
00041                                      struct group * grp, char *buffer,
00042                                      size_t buflen, int *errnop);
00043 static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
00044                                      char *buffer, size_t buflen,
00045                                      int *errnop);
00046 static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
00047                                      size_t buflen, int *errnop);
00048 
00049 /* Protect global state against multiple changers.  */
00050 __libc_lock_define_initialized (static, lock)
00051 
00052 
00053 /* Get the declaration of the parser function.  */
00054 #define ENTNAME grent
00055 #define STRUCTURE group
00056 #define EXTERN_PARSER
00057 #include <nss/nss_files/files-parse.c>
00058 
00059 /* Structure for remembering -group members ... */
00060 #define BLACKLIST_INITIAL_SIZE 512
00061 #define BLACKLIST_INCREMENT 256
00062 struct blacklist_t
00063 {
00064   char *data;
00065   int current;
00066   int size;
00067 };
00068 
00069 struct ent_t
00070 {
00071   bool_t files;
00072   FILE *stream;
00073   struct blacklist_t blacklist;
00074 };
00075 typedef struct ent_t ent_t;
00076 
00077 
00078 /* Positive if O_CLOEXEC is supported, negative if it is not supported,
00079    zero if it is still undecided.  This variable is shared with the
00080    other compat functions.  */
00081 #ifdef __ASSUME_O_CLOEXEC
00082 # define __compat_have_cloexec 1
00083 #else
00084 # ifdef O_CLOEXEC
00085 extern int __compat_have_cloexec;
00086 # else
00087 #  define __compat_have_cloexec -1
00088 # endif
00089 #endif
00090 
00091 /* Prototypes for local functions.  */
00092 static void blacklist_store_name (const char *, ent_t *);
00093 static int in_blacklist (const char *, int, ent_t *);
00094 
00095 /* Initialize the NSS interface/functions. The calling function must
00096    hold the lock.  */
00097 static void
00098 init_nss_interface (void)
00099 {
00100   __libc_lock_lock (lock);
00101 
00102   /* Retest.  */
00103   if (ni == NULL
00104       && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
00105     {
00106       nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
00107       nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
00108       nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
00109       nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
00110     }
00111 
00112   __libc_lock_unlock (lock);
00113 }
00114 
00115 static enum nss_status
00116 internal_setgrent (ent_t *ent)
00117 {
00118   enum nss_status status = NSS_STATUS_SUCCESS;
00119 
00120   ent->files = TRUE;
00121 
00122   if (ni == NULL)
00123     init_nss_interface ();
00124 
00125   if (ent->blacklist.data != NULL)
00126     {
00127       ent->blacklist.current = 1;
00128       ent->blacklist.data[0] = '|';
00129       ent->blacklist.data[1] = '\0';
00130     }
00131   else
00132     ent->blacklist.current = 0;
00133 
00134   ent->stream = fopen ("/etc/group", "rme");
00135 
00136   if (ent->stream == NULL)
00137     status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
00138   else
00139     {
00140       /* We have to make sure the file is  `closed on exec'.  */
00141       int result = 0;
00142 
00143       if (__compat_have_cloexec <= 0)
00144        {
00145          int flags;
00146          result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
00147          if (result >= 0)
00148            {
00149 #if defined O_CLOEXEC && !defined __ASSUME_O_CLOEXEC
00150              if (__compat_have_cloexec == 0)
00151               __compat_have_cloexec = (flags & FD_CLOEXEC) ? 1 : -1;
00152 
00153              if (__compat_have_cloexec < 0)
00154 #endif
00155               {
00156                 flags |= FD_CLOEXEC;
00157                 result = fcntl (fileno_unlocked (ent->stream), F_SETFD,
00158                               flags);
00159               }
00160            }
00161        }
00162 
00163       if (result < 0)
00164        {
00165          /* Something went wrong.  Close the stream and return a
00166             failure.  */
00167          fclose (ent->stream);
00168          ent->stream = NULL;
00169          status = NSS_STATUS_UNAVAIL;
00170        }
00171       else
00172        /* We take care of locking ourself.  */
00173        __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
00174     }
00175 
00176   return status;
00177 }
00178 
00179 
00180 static enum nss_status
00181 internal_endgrent (ent_t *ent)
00182 {
00183   if (ent->stream != NULL)
00184     {
00185       fclose (ent->stream);
00186       ent->stream = NULL;
00187     }
00188 
00189   if (ent->blacklist.data != NULL)
00190     {
00191       ent->blacklist.current = 1;
00192       ent->blacklist.data[0] = '|';
00193       ent->blacklist.data[1] = '\0';
00194     }
00195   else
00196     ent->blacklist.current = 0;
00197 
00198   return NSS_STATUS_SUCCESS;
00199 }
00200 
00201 /* This function checks, if the user is a member of this group and if
00202    yes, add the group id to the list.  */
00203 static void
00204 check_and_add_group (const char *user, gid_t group, long int *start,
00205                    long int *size, gid_t **groupsp, long int limit,
00206                    struct group *grp)
00207 {
00208   gid_t *groups = *groupsp;
00209   char **member;
00210 
00211   /* Don't add main group to list of groups.  */
00212   if (grp->gr_gid == group)
00213     return;
00214 
00215   for (member = grp->gr_mem; *member != NULL; ++member)
00216     if (strcmp (*member, user) == 0)
00217       {
00218        /* Matches user.  Insert this group.  */
00219        if (*start == *size)
00220          {
00221            /* Need a bigger buffer.  */
00222            gid_t *newgroups;
00223            long int newsize;
00224 
00225            if (limit > 0 && *size == limit)
00226              /* We reached the maximum.  */
00227              return;
00228 
00229            if (limit <= 0)
00230              newsize = 2 * *size;
00231            else
00232              newsize = MIN (limit, 2 * *size);
00233 
00234            newgroups = realloc (groups, newsize * sizeof (*groups));
00235            if (newgroups == NULL)
00236              return;
00237            *groupsp = groups = newgroups;
00238            *size = newsize;
00239          }
00240 
00241        groups[*start] = grp->gr_gid;
00242        *start += 1;
00243 
00244        break;
00245       }
00246 }
00247 
00248 /* Get the next group from NSS  (+ entry). If the NSS module supports
00249    initgroups_dyn, get all entries at once.  */
00250 static enum nss_status
00251 getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
00252                  gid_t group, long int *start, long int *size,
00253                  gid_t **groupsp, long int limit, int *errnop)
00254 {
00255   enum nss_status status;
00256   struct group grpbuf;
00257 
00258   /* if this module does not support getgrent_r and initgroups_dyn,
00259      abort. We cannot find the needed group entries.  */
00260   if (nss_getgrent_r == NULL && nss_initgroups_dyn == NULL)
00261     return NSS_STATUS_UNAVAIL;
00262 
00263   /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
00264      If this function is not supported, step through the whole group
00265      database with getgrent_r.  */
00266   if (nss_initgroups_dyn && nss_getgrgid_r)
00267     {
00268       long int mystart = 0;
00269       long int mysize = limit <= 0 ? *size : limit;
00270       gid_t *mygroups = malloc (mysize * sizeof (gid_t));
00271 
00272       if (mygroups == NULL)
00273        return NSS_STATUS_TRYAGAIN;
00274 
00275       /* For every gid in the list we get from the NSS module,
00276          get the whole group entry. We need to do this, since we
00277          need the group name to check if it is in the blacklist.
00278          In worst case, this is as twice as slow as stepping with
00279          getgrent_r through the whole group database. But for large
00280          group databases this is faster, since the user can only be
00281          in a limited number of groups.  */
00282       if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
00283                            limit, errnop) == NSS_STATUS_SUCCESS)
00284        {
00285          /* A temporary buffer. We use the normal buffer, until we find
00286             an entry, for which this buffer is to small.  In this case, we
00287             overwrite the pointer with one to a bigger buffer.  */
00288          char *tmpbuf = buffer;
00289          size_t tmplen = buflen;
00290          int i;
00291 
00292          for (i = 0; i < mystart; i++)
00293            {
00294              while ((status = nss_getgrgid_r (mygroups[i], &grpbuf, tmpbuf,
00295                                           tmplen,
00296                                           errnop)) == NSS_STATUS_TRYAGAIN
00297                    && *errnop == ERANGE)
00298               if (tmpbuf == buffer)
00299                 {
00300                   tmplen *= 2;
00301                   tmpbuf = __alloca (tmplen);
00302                 }
00303               else
00304                 tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen);
00305 
00306              if (__builtin_expect  (status != NSS_STATUS_NOTFOUND, 1))
00307               {
00308                 if (__builtin_expect  (status != NSS_STATUS_SUCCESS, 0))
00309                   {
00310                     free (mygroups);
00311                     return status;
00312                   }
00313 
00314                 if (!in_blacklist (grpbuf.gr_name,
00315                                  strlen (grpbuf.gr_name), ent))
00316                   check_and_add_group (user, group, start, size, groupsp,
00317                                     limit, &grpbuf);
00318               }
00319            }
00320 
00321          free (mygroups);
00322 
00323          return NSS_STATUS_NOTFOUND;
00324        }
00325 
00326       free (mygroups);
00327     }
00328 
00329   /* If we come here, the NSS module does not support initgroups_dyn
00330      and we have to step through the whole list ourself.  */
00331   do
00332     {
00333       if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
00334          NSS_STATUS_SUCCESS)
00335        return status;
00336     }
00337   while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
00338 
00339   check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
00340   return NSS_STATUS_SUCCESS;
00341 }
00342 
00343 static enum nss_status
00344 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
00345                    gid_t group, long int *start, long int *size,
00346                    gid_t **groupsp, long int limit, int *errnop)
00347 {
00348   struct parser_data *data = (void *) buffer;
00349   struct group grpbuf;
00350 
00351   if (!ent->files)
00352     return getgrent_next_nss (ent, buffer, buflen, user, group,
00353                            start, size, groupsp, limit, errnop);
00354 
00355   while (1)
00356     {
00357       fpos_t pos;
00358       int parse_res = 0;
00359       char *p;
00360 
00361       do
00362        {
00363          /* We need at least 3 characters for one line.  */
00364          if (__builtin_expect (buflen < 3, 0))
00365            {
00366            erange:
00367              *errnop = ERANGE;
00368              return NSS_STATUS_TRYAGAIN;
00369            }
00370 
00371          fgetpos (ent->stream, &pos);
00372          buffer[buflen - 1] = '\xff';
00373          p = fgets_unlocked (buffer, buflen, ent->stream);
00374          if (p == NULL && feof_unlocked (ent->stream))
00375            return NSS_STATUS_NOTFOUND;
00376 
00377          if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
00378            {
00379            erange_reset:
00380              fsetpos (ent->stream, &pos);
00381              goto erange;
00382            }
00383 
00384          /* Terminate the line for any case.  */
00385          buffer[buflen - 1] = '\0';
00386 
00387          /* Skip leading blanks.  */
00388          while (isspace (*p))
00389            ++p;
00390        }
00391       while (*p == '\0' || *p == '#' ||   /* Ignore empty and comment lines. */
00392             /* Parse the line.  If it is invalid, loop to
00393                get the next line of the file to parse.  */
00394             !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
00395                                              errnop)));
00396 
00397       if (__builtin_expect (parse_res == -1, 0))
00398        /* The parser ran out of space.  */
00399        goto erange_reset;
00400 
00401       if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
00402        /* This is a real entry.  */
00403        break;
00404 
00405       /* -group */
00406       if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
00407          && grpbuf.gr_name[1] != '@')
00408        {
00409          blacklist_store_name (&grpbuf.gr_name[1], ent);
00410          continue;
00411        }
00412 
00413       /* +group */
00414       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
00415          && grpbuf.gr_name[1] != '@')
00416        {
00417          if (in_blacklist (&grpbuf.gr_name[1],
00418                          strlen (&grpbuf.gr_name[1]), ent))
00419            continue;
00420          /* Store the group in the blacklist for the "+" at the end of
00421             /etc/group */
00422          blacklist_store_name (&grpbuf.gr_name[1], ent);
00423          if (nss_getgrnam_r == NULL)
00424            return NSS_STATUS_UNAVAIL;
00425          else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer,
00426                                buflen, errnop) != NSS_STATUS_SUCCESS)
00427            continue;
00428 
00429          check_and_add_group (user, group, start, size, groupsp,
00430                             limit, &grpbuf);
00431 
00432          return NSS_STATUS_SUCCESS;
00433        }
00434 
00435       /* +:... */
00436       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
00437        {
00438          ent->files = FALSE;
00439          return getgrent_next_nss (ent, buffer, buflen, user, group,
00440                                 start, size, groupsp, limit, errnop);
00441        }
00442     }
00443 
00444   check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
00445 
00446   return NSS_STATUS_SUCCESS;
00447 }
00448 
00449 
00450 enum nss_status
00451 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
00452                          long int *size, gid_t **groupsp, long int limit,
00453                          int *errnop)
00454 {
00455   size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
00456   char *tmpbuf;
00457   enum nss_status status;
00458   ent_t intern = { TRUE, NULL, {NULL, 0, 0} };
00459 
00460   status = internal_setgrent (&intern);
00461   if (status != NSS_STATUS_SUCCESS)
00462     return status;
00463 
00464   tmpbuf = __alloca (buflen);
00465 
00466   do
00467     {
00468       while ((status = internal_getgrent_r (&intern, tmpbuf, buflen,
00469                                        user, group, start, size,
00470                                        groupsp, limit, errnop))
00471             == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
00472        tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
00473     }
00474   while (status == NSS_STATUS_SUCCESS);
00475 
00476   internal_endgrent (&intern);
00477 
00478   return NSS_STATUS_SUCCESS;
00479 }
00480 
00481 
00482 /* Support routines for remembering -@netgroup and -user entries.
00483    The names are stored in a single string with `|' as separator. */
00484 static void
00485 blacklist_store_name (const char *name, ent_t *ent)
00486 {
00487   int namelen = strlen (name);
00488   char *tmp;
00489 
00490   /* First call, setup cache.  */
00491   if (ent->blacklist.size == 0)
00492     {
00493       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
00494       ent->blacklist.data = malloc (ent->blacklist.size);
00495       if (ent->blacklist.data == NULL)
00496        return;
00497       ent->blacklist.data[0] = '|';
00498       ent->blacklist.data[1] = '\0';
00499       ent->blacklist.current = 1;
00500     }
00501   else
00502     {
00503       if (in_blacklist (name, namelen, ent))
00504        return;                     /* no duplicates */
00505 
00506       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
00507        {
00508          ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
00509          tmp = realloc (ent->blacklist.data, ent->blacklist.size);
00510          if (tmp == NULL)
00511            {
00512              free (ent->blacklist.data);
00513              ent->blacklist.size = 0;
00514              return;
00515            }
00516          ent->blacklist.data = tmp;
00517        }
00518     }
00519 
00520   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
00521   *tmp++ = '|';
00522   *tmp = '\0';
00523   ent->blacklist.current += namelen + 1;
00524 
00525   return;
00526 }
00527 
00528 /* returns TRUE if ent->blacklist contains name, else FALSE */
00529 static bool_t
00530 in_blacklist (const char *name, int namelen, ent_t *ent)
00531 {
00532   char buf[namelen + 3];
00533   char *cp;
00534 
00535   if (ent->blacklist.data == NULL)
00536     return FALSE;
00537 
00538   buf[0] = '|';
00539   cp = stpcpy (&buf[1], name);
00540   *cp++ = '|';
00541   *cp = '\0';
00542   return strstr (ent->blacklist.data, buf) != NULL;
00543 }