(root)/
glibc-2.38/
nscd/
initgrcache.c
       1  /* Cache handling for host lookup.
       2     Copyright (C) 2004-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     This program is free software; you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published
       7     by the Free Software Foundation; version 2 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program; if not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <assert.h>
      19  #include <errno.h>
      20  #include <grp.h>
      21  #include <libintl.h>
      22  #include <string.h>
      23  #include <time.h>
      24  #include <unistd.h>
      25  #include <sys/mman.h>
      26  #include <scratch_buffer.h>
      27  #include <config.h>
      28  
      29  #include "dbg_log.h"
      30  #include "nscd.h"
      31  
      32  #include "../nss/nsswitch.h"
      33  
      34  /* Type of the lookup function.  */
      35  typedef enum nss_status (*initgroups_dyn_function) (const char *, gid_t,
      36  						    long int *, long int *,
      37  						    gid_t **, long int, int *);
      38  
      39  
      40  static const initgr_response_header notfound =
      41  {
      42    .version = NSCD_VERSION,
      43    .found = 0,
      44    .ngrps = 0
      45  };
      46  
      47  
      48  #include "../grp/compat-initgroups.c"
      49  
      50  
      51  static time_t
      52  addinitgroupsX (struct database_dyn *db, int fd, request_header *req,
      53  		void *key, uid_t uid, struct hashentry *const he,
      54  		struct datahead *dh)
      55  {
      56    /* Search for the entry matching the key.  Please note that we don't
      57       look again in the table whether the dataset is now available.  We
      58       simply insert it.  It does not matter if it is in there twice.  The
      59       pruning function only will look at the timestamp.  */
      60  
      61  
      62    /* We allocate all data in one memory block: the iov vector,
      63       the response header and the dataset itself.  */
      64    struct dataset
      65    {
      66      struct datahead head;
      67      initgr_response_header resp;
      68      char strdata[0];
      69    } *dataset = NULL;
      70  
      71    if (__glibc_unlikely (debug_level > 0))
      72      {
      73        if (he == NULL)
      74  	dbg_log (_("Haven't found \"%s\" in group cache!"), (char *) key);
      75        else
      76  	dbg_log (_("Reloading \"%s\" in group cache!"), (char *) key);
      77      }
      78  
      79    static nss_action_list group_database;
      80    nss_action_list nip;
      81    int no_more;
      82  
      83    if (group_database == NULL)
      84      no_more = !__nss_database_get (nss_database_group, &group_database);
      85    else
      86      no_more = 0;
      87    nip = group_database;
      88  
      89   /* We always use sysconf even if NGROUPS_MAX is defined.  That way, the
      90       limit can be raised in the kernel configuration without having to
      91       recompile libc.  */
      92    long int limit = __sysconf (_SC_NGROUPS_MAX);
      93  
      94    long int size;
      95    if (limit > 0)
      96      /* We limit the size of the initially allocated array.  */
      97      size = MIN (limit, 64);
      98    else
      99      /* No fixed limit on groups.  Pick a starting buffer size.  */
     100      size = 16;
     101  
     102    long int start = 0;
     103    bool all_tryagain = true;
     104    bool any_success = false;
     105  
     106    /* This is temporary memory, we need not (and must not) call
     107       mempool_alloc.  */
     108    // XXX This really should use alloca.  need to change the backends.
     109    gid_t *groups = (gid_t *) malloc (size * sizeof (gid_t));
     110    if (__glibc_unlikely (groups == NULL))
     111      /* No more memory.  */
     112      goto out;
     113  
     114    /* Nothing added yet.  */
     115    while (! no_more)
     116      {
     117        long int prev_start = start;
     118        enum nss_status status;
     119        initgroups_dyn_function fct;
     120        fct = __nss_lookup_function (nip, "initgroups_dyn");
     121  
     122        if (fct == NULL)
     123  	{
     124  	  status = compat_call (nip, key, -1, &start, &size, &groups,
     125  				limit, &errno);
     126  
     127  	  if (nss_next_action (nip, NSS_STATUS_UNAVAIL) != NSS_ACTION_CONTINUE)
     128  	    break;
     129  	}
     130        else
     131  	status = DL_CALL_FCT (fct, (key, -1, &start, &size, &groups,
     132  				    limit, &errno));
     133  
     134        /* Remove duplicates.  */
     135        long int cnt = prev_start;
     136        while (cnt < start)
     137  	{
     138  	  long int inner;
     139  	  for (inner = 0; inner < prev_start; ++inner)
     140  	    if (groups[inner] == groups[cnt])
     141  	      break;
     142  
     143  	  if (inner < prev_start)
     144  	    groups[cnt] = groups[--start];
     145  	  else
     146  	    ++cnt;
     147  	}
     148  
     149        if (status != NSS_STATUS_TRYAGAIN)
     150  	all_tryagain = false;
     151  
     152        /* This is really only for debugging.  */
     153        if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_RETURN)
     154  	__libc_fatal ("Illegal status in internal_getgrouplist.\n");
     155  
     156        any_success |= status == NSS_STATUS_SUCCESS;
     157  
     158        if (status != NSS_STATUS_SUCCESS
     159  	  && nss_next_action (nip, status) == NSS_ACTION_RETURN)
     160  	 break;
     161  
     162        if (nip[1].module == NULL)
     163  	no_more = -1;
     164        else
     165  	++nip;
     166      }
     167  
     168    bool all_written;
     169    ssize_t total;
     170    time_t timeout;
     171   out:
     172    all_written = true;
     173    timeout = MAX_TIMEOUT_VALUE;
     174    if (!any_success)
     175      {
     176        /* Nothing found.  Create a negative result record.  */
     177        total = sizeof (notfound);
     178  
     179        if (he != NULL && all_tryagain)
     180  	{
     181  	  /* If we have an old record available but cannot find one now
     182  	     because the service is not available we keep the old record
     183  	     and make sure it does not get removed.  */
     184  	  if (reload_count != UINT_MAX && dh->nreloads == reload_count)
     185  	    /* Do not reset the value if we never not reload the record.  */
     186  	    dh->nreloads = reload_count - 1;
     187  
     188  	  /* Reload with the same time-to-live value.  */
     189  	  timeout = dh->timeout = time (NULL) + db->postimeout;
     190  	}
     191        else
     192  	{
     193  	  /* We have no data.  This means we send the standard reply for this
     194  	     case.  */
     195  	  if (fd != -1
     196  	      && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
     197  					   MSG_NOSIGNAL)) != total)
     198  	    all_written = false;
     199  
     200  	  /* If we have a transient error or cannot permanently store
     201  	     the result, so be it.  */
     202  	  if (all_tryagain || __builtin_expect (db->negtimeout == 0, 0))
     203  	    {
     204  	      /* Mark the old entry as obsolete.  */
     205  	      if (dh != NULL)
     206  		dh->usable = false;
     207  	    }
     208  	  else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
     209  						  + req->key_len), 1)) != NULL)
     210  	    {
     211  	      timeout = datahead_init_neg (&dataset->head,
     212  					   (sizeof (struct dataset)
     213  					    + req->key_len), total,
     214  					   db->negtimeout);
     215  
     216  	      /* This is the reply.  */
     217  	      memcpy (&dataset->resp, &notfound, total);
     218  
     219  	      /* Copy the key data.  */
     220  	      char *key_copy = memcpy (dataset->strdata, key, req->key_len);
     221  
     222  	      /* If necessary, we also propagate the data to disk.  */
     223  	      if (db->persistent)
     224  		{
     225  		  // XXX async OK?
     226  		  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
     227  		  msync ((void *) pval,
     228  			 ((uintptr_t) dataset & pagesize_m1)
     229  			 + sizeof (struct dataset) + req->key_len, MS_ASYNC);
     230  		}
     231  
     232  	      (void) cache_add (req->type, key_copy, req->key_len,
     233  				&dataset->head, true, db, uid, he == NULL);
     234  
     235  	      pthread_rwlock_unlock (&db->lock);
     236  
     237  	      /* Mark the old entry as obsolete.  */
     238  	      if (dh != NULL)
     239  		dh->usable = false;
     240  	    }
     241  	}
     242      }
     243    else
     244      {
     245  
     246        total = offsetof (struct dataset, strdata) + start * sizeof (int32_t);
     247  
     248        /* If we refill the cache, first assume the reconrd did not
     249  	 change.  Allocate memory on the cache since it is likely
     250  	 discarded anyway.  If it turns out to be necessary to have a
     251  	 new record we can still allocate real memory.  */
     252        bool alloca_used = false;
     253        dataset = NULL;
     254  
     255        if (he == NULL)
     256  	dataset = (struct dataset *) mempool_alloc (db, total + req->key_len,
     257  						    1);
     258  
     259        if (dataset == NULL)
     260  	{
     261  	  /* We cannot permanently add the result in the moment.  But
     262  	     we can provide the result as is.  Store the data in some
     263  	     temporary memory.  */
     264  	  dataset = (struct dataset *) alloca (total + req->key_len);
     265  
     266  	  /* We cannot add this record to the permanent database.  */
     267  	  alloca_used = true;
     268  	}
     269  
     270        timeout = datahead_init_pos (&dataset->head, total + req->key_len,
     271  				   total - offsetof (struct dataset, resp),
     272  				   he == NULL ? 0 : dh->nreloads + 1,
     273  				   db->postimeout);
     274  
     275        dataset->resp.version = NSCD_VERSION;
     276        dataset->resp.found = 1;
     277        dataset->resp.ngrps = start;
     278  
     279        char *cp = dataset->strdata;
     280  
     281        /* Copy the GID values.  If the size of the types match this is
     282  	 very simple.  */
     283        if (sizeof (gid_t) == sizeof (int32_t))
     284  	cp = mempcpy (cp, groups, start * sizeof (gid_t));
     285        else
     286  	{
     287  	  gid_t *gcp = (gid_t *) cp;
     288  
     289  	  for (int i = 0; i < start; ++i)
     290  	    *gcp++ = groups[i];
     291  
     292  	  cp = (char *) gcp;
     293  	}
     294  
     295        /* Finally the user name.  */
     296        memcpy (cp, key, req->key_len);
     297  
     298        assert (cp == dataset->strdata + total - offsetof (struct dataset,
     299  							 strdata));
     300  
     301        /* Now we can determine whether on refill we have to create a new
     302  	 record or not.  */
     303        if (he != NULL)
     304  	{
     305  	  assert (fd == -1);
     306  
     307  	  if (total + req->key_len == dh->allocsize
     308  	      && total - offsetof (struct dataset, resp) == dh->recsize
     309  	      && memcmp (&dataset->resp, dh->data,
     310  			 dh->allocsize - offsetof (struct dataset, resp)) == 0)
     311  	    {
     312  	      /* The data has not changed.  We will just bump the
     313  		 timeout value.  Note that the new record has been
     314  		 allocated on the stack and need not be freed.  */
     315  	      dh->timeout = dataset->head.timeout;
     316  	      ++dh->nreloads;
     317  	    }
     318  	  else
     319  	    {
     320  	      /* We have to create a new record.  Just allocate
     321  		 appropriate memory and copy it.  */
     322  	      struct dataset *newp
     323  		= (struct dataset *) mempool_alloc (db, total + req->key_len,
     324  						    1);
     325  	      if (newp != NULL)
     326  		{
     327  		  /* Adjust pointer into the memory block.  */
     328  		  cp = (char *) newp + (cp - (char *) dataset);
     329  
     330  		  dataset = memcpy (newp, dataset, total + req->key_len);
     331  		  alloca_used = false;
     332  		}
     333  
     334  	      /* Mark the old record as obsolete.  */
     335  	      dh->usable = false;
     336  	    }
     337  	}
     338        else
     339  	{
     340  	  /* We write the dataset before inserting it to the database
     341  	     since while inserting this thread might block and so would
     342  	     unnecessarily let the receiver wait.  */
     343  	  assert (fd != -1);
     344  
     345  	  if (writeall (fd, &dataset->resp, dataset->head.recsize)
     346  	      != dataset->head.recsize)
     347  	    all_written = false;
     348  	}
     349  
     350  
     351        /* Add the record to the database.  But only if it has not been
     352  	 stored on the stack.  */
     353        if (! alloca_used)
     354  	{
     355  	  /* If necessary, we also propagate the data to disk.  */
     356  	  if (db->persistent)
     357  	    {
     358  	      // XXX async OK?
     359  	      uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
     360  	      msync ((void *) pval,
     361  		     ((uintptr_t) dataset & pagesize_m1) + total
     362  		     + req->key_len, MS_ASYNC);
     363  	    }
     364  
     365  	  (void) cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
     366  			    db, uid, he == NULL);
     367  
     368  	  pthread_rwlock_unlock (&db->lock);
     369  	}
     370      }
     371  
     372    free (groups);
     373  
     374    if (__builtin_expect (!all_written, 0) && debug_level > 0)
     375      {
     376        char buf[256];
     377        dbg_log (_("short write in %s: %s"), __FUNCTION__,
     378  	       strerror_r (errno, buf, sizeof (buf)));
     379      }
     380  
     381    return timeout;
     382  }
     383  
     384  
     385  void
     386  addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
     387  	       uid_t uid)
     388  {
     389    addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
     390  }
     391  
     392  
     393  time_t
     394  readdinitgroups (struct database_dyn *db, struct hashentry *he,
     395  		 struct datahead *dh)
     396  {
     397    request_header req =
     398      {
     399        .type = INITGROUPS,
     400        .key_len = he->len
     401      };
     402  
     403    return addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
     404  }