(root)/
glibc-2.38/
nss/
nss_compat/
compat-initgroups.c
       1  /* Copyright (C) 1998-2023 Free Software Foundation, Inc.
       2     This file is part of the GNU C Library.
       3  
       4     The GNU C Library is free software; you can redistribute it and/or
       5     modify it under the terms of the GNU Lesser General Public
       6     License as published by the Free Software Foundation; either
       7     version 2.1 of the License, or (at your option) any later version.
       8  
       9     The GNU C Library is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12     Lesser General Public License for more details.
      13  
      14     You should have received a copy of the GNU Lesser General Public
      15     License along with the GNU C Library; if not, see
      16     <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <ctype.h>
      19  #include <errno.h>
      20  #include <fcntl.h>
      21  #include <grp.h>
      22  #include <nss.h>
      23  #include <stdio_ext.h>
      24  #include <string.h>
      25  #include <unistd.h>
      26  #include <sys/param.h>
      27  #include <nsswitch.h>
      28  #include <libc-lock.h>
      29  #include <kernel-features.h>
      30  #include <scratch_buffer.h>
      31  #include <nss_files.h>
      32  
      33  NSS_DECLARE_MODULE_FUNCTIONS (compat)
      34  
      35  static nss_action_list ni;
      36  static enum nss_status (*initgroups_dyn_impl) (const char *, gid_t,
      37  					       long int *, long int *,
      38  					       gid_t **, long int, int *);
      39  static enum nss_status (*getgrnam_r_impl) (const char *name,
      40  					   struct group * grp, char *buffer,
      41  					   size_t buflen, int *errnop);
      42  static enum nss_status (*getgrgid_r_impl) (gid_t gid, struct group * grp,
      43  					   char *buffer, size_t buflen,
      44  					   int *errnop);
      45  static enum nss_status (*setgrent_impl) (int stayopen);
      46  static enum nss_status (*getgrent_r_impl) (struct group * grp, char *buffer,
      47  					   size_t buflen, int *errnop);
      48  static enum nss_status (*endgrent_impl) (void);
      49  
      50  /* Protect global state against multiple changers.  */
      51  __libc_lock_define_initialized (static, lock)
      52  
      53  
      54  /* Get the declaration of the parser function.  */
      55  #define ENTNAME grent
      56  #define STRUCTURE group
      57  #define EXTERN_PARSER
      58  #include <nss/nss_files/files-parse.c>
      59  
      60  /* Structure for remembering -group members ... */
      61  #define BLACKLIST_INITIAL_SIZE 512
      62  #define BLACKLIST_INCREMENT 256
      63  struct blacklist_t
      64  {
      65    char *data;
      66    int current;
      67    int size;
      68  };
      69  
      70  struct ent_t
      71  {
      72    bool files;
      73    bool need_endgrent;
      74    bool skip_initgroups_dyn;
      75    FILE *stream;
      76    struct blacklist_t blacklist;
      77  };
      78  typedef struct ent_t ent_t;
      79  
      80  /* Prototypes for local functions.  */
      81  static void blacklist_store_name (const char *, ent_t *);
      82  static bool in_blacklist (const char *, int, ent_t *);
      83  
      84  /* Initialize the NSS interface/functions. The calling function must
      85     hold the lock.  */
      86  static void
      87  init_nss_interface (void)
      88  {
      89    __libc_lock_lock (lock);
      90  
      91    /* Retest.  */
      92    if (ni == NULL
      93        && __nss_database_get (nss_database_group_compat, &ni))
      94      {
      95        initgroups_dyn_impl = __nss_lookup_function (ni, "initgroups_dyn");
      96        getgrnam_r_impl = __nss_lookup_function (ni, "getgrnam_r");
      97        getgrgid_r_impl = __nss_lookup_function (ni, "getgrgid_r");
      98        setgrent_impl = __nss_lookup_function (ni, "setgrent");
      99        getgrent_r_impl = __nss_lookup_function (ni, "getgrent_r");
     100        endgrent_impl = __nss_lookup_function (ni, "endgrent");
     101      }
     102  
     103    __libc_lock_unlock (lock);
     104  }
     105  
     106  static enum nss_status
     107  internal_setgrent (ent_t *ent)
     108  {
     109    enum nss_status status = NSS_STATUS_SUCCESS;
     110  
     111    ent->files = true;
     112  
     113    if (ni == NULL)
     114      init_nss_interface ();
     115  
     116    if (ent->blacklist.data != NULL)
     117      {
     118        ent->blacklist.current = 1;
     119        ent->blacklist.data[0] = '|';
     120        ent->blacklist.data[1] = '\0';
     121      }
     122    else
     123      ent->blacklist.current = 0;
     124  
     125    ent->stream = __nss_files_fopen ("/etc/group");
     126  
     127    if (ent->stream == NULL)
     128      status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
     129  
     130    return status;
     131  }
     132  
     133  
     134  static enum nss_status __attribute_warn_unused_result__
     135  internal_endgrent (ent_t *ent)
     136  {
     137    if (ent->stream != NULL)
     138      {
     139        fclose (ent->stream);
     140        ent->stream = NULL;
     141      }
     142  
     143    if (ent->blacklist.data != NULL)
     144      {
     145        ent->blacklist.current = 1;
     146        ent->blacklist.data[0] = '|';
     147        ent->blacklist.data[1] = '\0';
     148      }
     149    else
     150      ent->blacklist.current = 0;
     151  
     152    if (ent->need_endgrent && endgrent_impl != NULL)
     153      endgrent_impl ();
     154  
     155    return NSS_STATUS_SUCCESS;
     156  }
     157  
     158  /* Like internal_endgrent, but preserve errno in all cases.  */
     159  static void
     160  internal_endgrent_noerror (ent_t *ent)
     161  {
     162    int saved_errno = errno;
     163    enum nss_status unused __attribute__ ((unused)) = internal_endgrent (ent);
     164    __set_errno (saved_errno);
     165  }
     166  
     167  /* Add new group record.  */
     168  static void
     169  add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
     170  	   gid_t gid)
     171  {
     172    gid_t *groups = *groupsp;
     173  
     174    /* Matches user.  Insert this group.  */
     175    if (__glibc_unlikely (*start == *size))
     176      {
     177        /* Need a bigger buffer.  */
     178        gid_t *newgroups;
     179        long int newsize;
     180  
     181        if (limit > 0 && *size == limit)
     182  	/* We reached the maximum.  */
     183  	return;
     184  
     185        if (limit <= 0)
     186  	newsize = 2 * *size;
     187        else
     188  	newsize = MIN (limit, 2 * *size);
     189  
     190        newgroups = realloc (groups, newsize * sizeof (*groups));
     191        if (newgroups == NULL)
     192  	return;
     193        *groupsp = groups = newgroups;
     194        *size = newsize;
     195      }
     196  
     197    groups[*start] = gid;
     198    *start += 1;
     199  }
     200  
     201  /* This function checks, if the user is a member of this group and if
     202     yes, add the group id to the list.  Return nonzero is we couldn't
     203     handle the group because the user is not in the member list.  */
     204  static int
     205  check_and_add_group (const char *user, gid_t group, long int *start,
     206  		     long int *size, gid_t **groupsp, long int limit,
     207  		     struct group *grp)
     208  {
     209    char **member;
     210  
     211    /* Don't add main group to list of groups.  */
     212    if (grp->gr_gid == group)
     213      return 0;
     214  
     215    for (member = grp->gr_mem; *member != NULL; ++member)
     216      if (strcmp (*member, user) == 0)
     217        {
     218  	add_group (start, size, groupsp, limit, grp->gr_gid);
     219  	return 0;
     220        }
     221  
     222    return 1;
     223  }
     224  
     225  /* Get the next group from NSS  (+ entry). If the NSS module supports
     226     initgroups_dyn, get all entries at once.  */
     227  static enum nss_status
     228  getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
     229  		   gid_t group, long int *start, long int *size,
     230  		   gid_t **groupsp, long int limit, int *errnop)
     231  {
     232    enum nss_status status;
     233    struct group grpbuf;
     234  
     235    /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
     236       If this function is not supported, step through the whole group
     237       database with getgrent_r.  */
     238    if (! ent->skip_initgroups_dyn)
     239      {
     240        long int mystart = 0;
     241        long int mysize = limit <= 0 ? *size : limit;
     242        gid_t *mygroups = malloc (mysize * sizeof (gid_t));
     243  
     244        if (mygroups == NULL)
     245  	return NSS_STATUS_TRYAGAIN;
     246  
     247        /* For every gid in the list we get from the NSS module,
     248  	 get the whole group entry. We need to do this, since we
     249  	 need the group name to check if it is in the blacklist.
     250  	 In worst case, this is as twice as slow as stepping with
     251  	 getgrent_r through the whole group database. But for large
     252  	 group databases this is faster, since the user can only be
     253  	 in a limited number of groups.  */
     254        if (initgroups_dyn_impl (user, group, &mystart, &mysize, &mygroups,
     255  			       limit, errnop) == NSS_STATUS_SUCCESS)
     256  	{
     257  	  status = NSS_STATUS_NOTFOUND;
     258  
     259  	  /* If there is no blacklist we can trust the underlying
     260  	     initgroups implementation.  */
     261  	  if (ent->blacklist.current <= 1)
     262  	    for (int i = 0; i < mystart; i++)
     263  	      add_group (start, size, groupsp, limit, mygroups[i]);
     264  	  else
     265  	    {
     266  	      /* A temporary buffer. We use the normal buffer, until we find
     267  		 an entry, for which this buffer is to small.  In this case, we
     268  		 overwrite the pointer with one to a bigger buffer.  */
     269  	      char *tmpbuf = buffer;
     270  	      size_t tmplen = buflen;
     271  
     272  	      for (int i = 0; i < mystart; i++)
     273  		{
     274  		  while ((status = getgrgid_r_impl (mygroups[i], &grpbuf,
     275  						    tmpbuf, tmplen, errnop))
     276  			 == NSS_STATUS_TRYAGAIN
     277  			 && *errnop == ERANGE)
     278                      {
     279  		      /* Check for overflow. */
     280  		      if (__glibc_unlikely (tmplen * 2 < tmplen))
     281  			{
     282  			  __set_errno (ENOMEM);
     283  			  status = NSS_STATUS_TRYAGAIN;
     284  			  goto done;
     285  			}
     286  		      /* Increase the size.  Make sure that we retry
     287  			 with a reasonable size.  */
     288  		      tmplen *= 2;
     289  		      if (tmplen < 1024)
     290  			tmplen = 1024;
     291  		      if (tmpbuf != buffer)
     292  			free (tmpbuf);
     293  		      tmpbuf = malloc (tmplen);
     294  		      if (__glibc_unlikely (tmpbuf == NULL))
     295  			{
     296  			  status = NSS_STATUS_TRYAGAIN;
     297  			  goto done;
     298  			}
     299                      }
     300  
     301  		  if (__builtin_expect  (status != NSS_STATUS_NOTFOUND, 1))
     302  		    {
     303  		      if (__builtin_expect  (status != NSS_STATUS_SUCCESS, 0))
     304  		        goto done;
     305  
     306  		      if (!in_blacklist (grpbuf.gr_name,
     307  					 strlen (grpbuf.gr_name), ent)
     308  			  && check_and_add_group (user, group, start, size,
     309  						  groupsp, limit, &grpbuf))
     310  			{
     311  			  if (setgrent_impl != NULL)
     312  			    {
     313  			      setgrent_impl (1);
     314  			      ent->need_endgrent = true;
     315  			    }
     316  			  ent->skip_initgroups_dyn = true;
     317  
     318  			  goto iter;
     319  			}
     320  		    }
     321  		}
     322  
     323  	      status = NSS_STATUS_NOTFOUND;
     324  
     325   done:
     326  	      if (tmpbuf != buffer)
     327  	        free (tmpbuf);
     328  	    }
     329  
     330  	  free (mygroups);
     331  
     332  	  return status;
     333  	}
     334  
     335        free (mygroups);
     336      }
     337  
     338    /* If we come here, the NSS module does not support initgroups_dyn
     339       or we were confronted with a split group.  In these cases we have
     340       to step through the whole list ourself.  */
     341   iter:
     342    do
     343      {
     344        if ((status = getgrent_r_impl (&grpbuf, buffer, buflen, errnop))
     345  	  != NSS_STATUS_SUCCESS)
     346  	break;
     347      }
     348    while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
     349  
     350    if (status == NSS_STATUS_SUCCESS)
     351      check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
     352  
     353    return status;
     354  }
     355  
     356  static enum nss_status
     357  internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
     358  		     gid_t group, long int *start, long int *size,
     359  		     gid_t **groupsp, long int limit, int *errnop)
     360  {
     361    struct parser_data *data = (void *) buffer;
     362    struct group grpbuf;
     363  
     364    if (!ent->files)
     365      return getgrent_next_nss (ent, buffer, buflen, user, group,
     366  			      start, size, groupsp, limit, errnop);
     367  
     368    while (1)
     369      {
     370        fpos_t pos;
     371        int parse_res = 0;
     372        char *p;
     373  
     374        do
     375  	{
     376  	  /* We need at least 3 characters for one line.  */
     377  	  if (__glibc_unlikely (buflen < 3))
     378  	    {
     379  	    erange:
     380  	      *errnop = ERANGE;
     381  	      return NSS_STATUS_TRYAGAIN;
     382  	    }
     383  
     384  	  fgetpos (ent->stream, &pos);
     385  	  buffer[buflen - 1] = '\xff';
     386  	  p = fgets_unlocked (buffer, buflen, ent->stream);
     387  	  if (p == NULL && feof_unlocked (ent->stream))
     388  	    return NSS_STATUS_NOTFOUND;
     389  
     390  	  if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
     391  	    {
     392  	    erange_reset:
     393  	      fsetpos (ent->stream, &pos);
     394  	      goto erange;
     395  	    }
     396  
     397  	  /* Terminate the line for any case.  */
     398  	  buffer[buflen - 1] = '\0';
     399  
     400  	  /* Skip leading blanks.  */
     401  	  while (isspace (*p))
     402  	    ++p;
     403  	}
     404        while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */
     405  	     /* Parse the line.  If it is invalid, loop to
     406  		get the next line of the file to parse.  */
     407  	     || !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
     408  						      errnop)));
     409  
     410        if (__glibc_unlikely (parse_res == -1))
     411  	/* The parser ran out of space.  */
     412  	goto erange_reset;
     413  
     414        if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
     415  	/* This is a real entry.  */
     416  	break;
     417  
     418        /* -group */
     419        if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
     420  	  && grpbuf.gr_name[1] != '@')
     421  	{
     422  	  blacklist_store_name (&grpbuf.gr_name[1], ent);
     423  	  continue;
     424  	}
     425  
     426        /* +group */
     427        if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
     428  	  && grpbuf.gr_name[1] != '@')
     429  	{
     430  	  if (in_blacklist (&grpbuf.gr_name[1],
     431  			    strlen (&grpbuf.gr_name[1]), ent))
     432  	    continue;
     433  	  /* Store the group in the blacklist for the "+" at the end of
     434  	     /etc/group */
     435  	  blacklist_store_name (&grpbuf.gr_name[1], ent);
     436  	  if (getgrnam_r_impl == NULL)
     437  	    return NSS_STATUS_UNAVAIL;
     438  	  else if (getgrnam_r_impl (&grpbuf.gr_name[1], &grpbuf, buffer,
     439  				    buflen, errnop) != NSS_STATUS_SUCCESS)
     440  	    continue;
     441  
     442  	  check_and_add_group (user, group, start, size, groupsp,
     443  			       limit, &grpbuf);
     444  
     445  	  return NSS_STATUS_SUCCESS;
     446  	}
     447  
     448        /* +:... */
     449        if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
     450  	{
     451  	  /* If the selected module does not support getgrent_r or
     452  	     initgroups_dyn, abort. We cannot find the needed group
     453  	     entries.  */
     454  	  if (initgroups_dyn_impl == NULL || getgrgid_r_impl == NULL)
     455  	    {
     456  	      if (setgrent_impl != NULL)
     457  		{
     458  		  setgrent_impl (1);
     459  		  ent->need_endgrent = true;
     460  		}
     461  	      ent->skip_initgroups_dyn = true;
     462  
     463  	      if (getgrent_r_impl == NULL)
     464  		return NSS_STATUS_UNAVAIL;
     465  	    }
     466  
     467  	  ent->files = false;
     468  
     469  	  return getgrent_next_nss (ent, buffer, buflen, user, group,
     470  				    start, size, groupsp, limit, errnop);
     471  	}
     472      }
     473  
     474    check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
     475  
     476    return NSS_STATUS_SUCCESS;
     477  }
     478  
     479  
     480  enum nss_status
     481  _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
     482  			    long int *size, gid_t **groupsp, long int limit,
     483  			    int *errnop)
     484  {
     485    enum nss_status status;
     486    ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
     487  
     488    status = internal_setgrent (&intern);
     489    if (status != NSS_STATUS_SUCCESS)
     490      return status;
     491  
     492    struct scratch_buffer tmpbuf;
     493    scratch_buffer_init (&tmpbuf);
     494  
     495    do
     496      {
     497        while ((status = internal_getgrent_r (&intern, tmpbuf.data, tmpbuf.length,
     498  					    user, group, start, size,
     499  					    groupsp, limit, errnop))
     500  	     == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
     501          if (!scratch_buffer_grow (&tmpbuf))
     502  	    goto done;
     503      }
     504    while (status == NSS_STATUS_SUCCESS);
     505  
     506    status = NSS_STATUS_SUCCESS;
     507  
     508   done:
     509    scratch_buffer_free (&tmpbuf);
     510  
     511    internal_endgrent_noerror (&intern);
     512  
     513    return status;
     514  }
     515  
     516  
     517  /* Support routines for remembering -@netgroup and -user entries.
     518     The names are stored in a single string with `|' as separator. */
     519  static void
     520  blacklist_store_name (const char *name, ent_t *ent)
     521  {
     522    int namelen = strlen (name);
     523    char *tmp;
     524  
     525    /* First call, setup cache.  */
     526    if (ent->blacklist.size == 0)
     527      {
     528        ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
     529        ent->blacklist.data = malloc (ent->blacklist.size);
     530        if (ent->blacklist.data == NULL)
     531  	return;
     532        ent->blacklist.data[0] = '|';
     533        ent->blacklist.data[1] = '\0';
     534        ent->blacklist.current = 1;
     535      }
     536    else
     537      {
     538        if (in_blacklist (name, namelen, ent))
     539  	return;			/* no duplicates */
     540  
     541        if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
     542  	{
     543  	  ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
     544  	  tmp = realloc (ent->blacklist.data, ent->blacklist.size);
     545  	  if (tmp == NULL)
     546  	    {
     547  	      free (ent->blacklist.data);
     548  	      ent->blacklist.size = 0;
     549  	      return;
     550  	    }
     551  	  ent->blacklist.data = tmp;
     552  	}
     553      }
     554  
     555    tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
     556    *tmp++ = '|';
     557    *tmp = '\0';
     558    ent->blacklist.current += namelen + 1;
     559  
     560    return;
     561  }
     562  
     563  /* Return whether ent->blacklist contains name.  */
     564  static bool
     565  in_blacklist (const char *name, int namelen, ent_t *ent)
     566  {
     567    char buf[namelen + 3];
     568    char *cp;
     569  
     570    if (ent->blacklist.data == NULL)
     571      return false;
     572  
     573    buf[0] = '|';
     574    cp = stpcpy (&buf[1], name);
     575    *cp++ = '|';
     576    *cp = '\0';
     577    return strstr (ent->blacklist.data, buf) != NULL;
     578  }