(root)/
glibc-2.38/
nscd/
pwdcache.c
       1  /* Cache handling for passwd lookup.
       2     Copyright (C) 1998-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 <error.h>
      21  #include <libintl.h>
      22  #include <pwd.h>
      23  #include <stdbool.h>
      24  #include <stddef.h>
      25  #include <stdio.h>
      26  #include <stdlib.h>
      27  #include <string.h>
      28  #include <time.h>
      29  #include <unistd.h>
      30  #include <sys/mman.h>
      31  #include <sys/socket.h>
      32  #include <stackinfo.h>
      33  #include <scratch_buffer.h>
      34  
      35  #include "nscd.h"
      36  #include "dbg_log.h"
      37  
      38  /* This is the standard reply in case the service is disabled.  */
      39  static const pw_response_header disabled =
      40  {
      41    .version = NSCD_VERSION,
      42    .found = -1,
      43    .pw_name_len = 0,
      44    .pw_passwd_len = 0,
      45    .pw_uid = -1,
      46    .pw_gid = -1,
      47    .pw_gecos_len = 0,
      48    .pw_dir_len = 0,
      49    .pw_shell_len = 0
      50  };
      51  
      52  /* This is the struct describing how to write this record.  */
      53  const struct iovec pwd_iov_disabled =
      54  {
      55    .iov_base = (void *) &disabled,
      56    .iov_len = sizeof (disabled)
      57  };
      58  
      59  
      60  /* This is the standard reply in case we haven't found the dataset.  */
      61  static const pw_response_header notfound =
      62  {
      63    .version = NSCD_VERSION,
      64    .found = 0,
      65    .pw_name_len = 0,
      66    .pw_passwd_len = 0,
      67    .pw_uid = -1,
      68    .pw_gid = -1,
      69    .pw_gecos_len = 0,
      70    .pw_dir_len = 0,
      71    .pw_shell_len = 0
      72  };
      73  
      74  
      75  static time_t
      76  cache_addpw (struct database_dyn *db, int fd, request_header *req,
      77  	     const void *key, struct passwd *pwd, uid_t owner,
      78  	     struct hashentry *const he, struct datahead *dh, int errval)
      79  {
      80    bool all_written = true;
      81    ssize_t total;
      82    time_t t = time (NULL);
      83  
      84    /* We allocate all data in one memory block: the iov vector,
      85       the response header and the dataset itself.  */
      86    struct dataset
      87    {
      88      struct datahead head;
      89      pw_response_header resp;
      90      char strdata[0];
      91    } *dataset;
      92  
      93    assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
      94  
      95    time_t timeout = MAX_TIMEOUT_VALUE;
      96    if (pwd == NULL)
      97      {
      98        if (he != NULL && errval == EAGAIN)
      99  	{
     100  	  /* If we have an old record available but cannot find one
     101  	     now because the service is not available we keep the old
     102  	     record and make sure it does not get removed.  */
     103  	  if (reload_count != UINT_MAX && dh->nreloads == reload_count)
     104  	    /* Do not reset the value if we never not reload the record.  */
     105  	    dh->nreloads = reload_count - 1;
     106  
     107  	  /* Reload with the same time-to-live value.  */
     108  	  timeout = dh->timeout = t + db->postimeout;
     109  
     110  	  total = 0;
     111  	}
     112        else
     113  	{
     114  	  /* We have no data.  This means we send the standard reply for this
     115  	     case.  */
     116  	  total = sizeof (notfound);
     117  
     118  	  if (fd != -1
     119  	      && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
     120  					   MSG_NOSIGNAL)) != total)
     121  	    all_written = false;
     122  
     123  	  /* If we have a transient error or cannot permanently store
     124  	     the result, so be it.  */
     125  	  if (errval == EAGAIN || __glibc_unlikely (db->negtimeout == 0))
     126  	    {
     127  	      /* Mark the old entry as obsolete.  */
     128  	      if (dh != NULL)
     129  		dh->usable = false;
     130  	    }
     131  	  else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
     132  						  + req->key_len), 1)) != NULL)
     133  	    {
     134  	      timeout = datahead_init_neg (&dataset->head,
     135  					   (sizeof (struct dataset)
     136  					    + req->key_len), total,
     137  					   db->negtimeout);
     138  
     139  	      /* This is the reply.  */
     140  	      memcpy (&dataset->resp, &notfound, total);
     141  
     142  	      /* Copy the key data.  */
     143  	      char *key_copy = memcpy (dataset->strdata, key, req->key_len);
     144  
     145  	      /* If necessary, we also propagate the data to disk.  */
     146  	      if (db->persistent)
     147  		{
     148  		  // XXX async OK?
     149  		  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
     150  		  msync ((void *) pval,
     151  			 ((uintptr_t) dataset & pagesize_m1)
     152  			 + sizeof (struct dataset) + req->key_len, MS_ASYNC);
     153  		}
     154  
     155  	      (void) cache_add (req->type, key_copy, req->key_len,
     156  				&dataset->head, true, db, owner, he == NULL);
     157  
     158  	      pthread_rwlock_unlock (&db->lock);
     159  
     160  	      /* Mark the old entry as obsolete.  */
     161  	      if (dh != NULL)
     162  		dh->usable = false;
     163  	    }
     164  	}
     165      }
     166    else
     167      {
     168        /* Determine the I/O structure.  */
     169        size_t pw_name_len = strlen (pwd->pw_name) + 1;
     170        size_t pw_passwd_len = strlen (pwd->pw_passwd) + 1;
     171        size_t pw_gecos_len = strlen (pwd->pw_gecos) + 1;
     172        size_t pw_dir_len = strlen (pwd->pw_dir) + 1;
     173        size_t pw_shell_len = strlen (pwd->pw_shell) + 1;
     174        char *cp;
     175        const size_t key_len = strlen (key);
     176        const size_t buf_len = 3 * sizeof (pwd->pw_uid) + key_len + 1;
     177        char *buf = alloca (buf_len);
     178        ssize_t n;
     179  
     180        /* We need this to insert the `byuid' entry.  */
     181        int key_offset;
     182        n = snprintf (buf, buf_len, "%d%c%n%s", pwd->pw_uid, '\0',
     183  		    &key_offset, (char *) key) + 1;
     184  
     185        total = (offsetof (struct dataset, strdata)
     186  	       + pw_name_len + pw_passwd_len
     187  	       + pw_gecos_len + pw_dir_len + pw_shell_len);
     188  
     189        /* If we refill the cache, first assume the reconrd did not
     190  	 change.  Allocate memory on the cache since it is likely
     191  	 discarded anyway.  If it turns out to be necessary to have a
     192  	 new record we can still allocate real memory.  */
     193        bool alloca_used = false;
     194        dataset = NULL;
     195  
     196        if (he == NULL)
     197  	{
     198  	  /* Prevent an INVALIDATE request from pruning the data between
     199  	     the two calls to cache_add.  */
     200  	  if (db->propagate)
     201  	    pthread_mutex_lock (&db->prune_run_lock);
     202  	  dataset = (struct dataset *) mempool_alloc (db, total + n, 1);
     203  	}
     204  
     205        if (dataset == NULL)
     206  	{
     207  	  if (he == NULL && db->propagate)
     208  	    pthread_mutex_unlock (&db->prune_run_lock);
     209  
     210  	  /* We cannot permanently add the result in the moment.  But
     211  	     we can provide the result as is.  Store the data in some
     212  	     temporary memory.  */
     213  	  dataset = (struct dataset *) alloca (total + n);
     214  
     215  	  /* We cannot add this record to the permanent database.  */
     216  	  alloca_used = true;
     217  	}
     218  
     219        timeout = datahead_init_pos (&dataset->head, total + n,
     220  				   total - offsetof (struct dataset, resp),
     221  				   he == NULL ? 0 : dh->nreloads + 1,
     222  				   db->postimeout);
     223  
     224        dataset->resp.version = NSCD_VERSION;
     225        dataset->resp.found = 1;
     226        dataset->resp.pw_name_len = pw_name_len;
     227        dataset->resp.pw_passwd_len = pw_passwd_len;
     228        dataset->resp.pw_uid = pwd->pw_uid;
     229        dataset->resp.pw_gid = pwd->pw_gid;
     230        dataset->resp.pw_gecos_len = pw_gecos_len;
     231        dataset->resp.pw_dir_len = pw_dir_len;
     232        dataset->resp.pw_shell_len = pw_shell_len;
     233  
     234        cp = dataset->strdata;
     235  
     236        /* Copy the strings over into the buffer.  */
     237        cp = mempcpy (cp, pwd->pw_name, pw_name_len);
     238        cp = mempcpy (cp, pwd->pw_passwd, pw_passwd_len);
     239        cp = mempcpy (cp, pwd->pw_gecos, pw_gecos_len);
     240        cp = mempcpy (cp, pwd->pw_dir, pw_dir_len);
     241        cp = mempcpy (cp, pwd->pw_shell, pw_shell_len);
     242  
     243        /* Finally the stringified UID value.  */
     244        memcpy (cp, buf, n);
     245        char *key_copy = cp + key_offset;
     246        assert (key_copy == strchr (cp, '\0') + 1);
     247  
     248        assert (cp == dataset->strdata + total - offsetof (struct dataset,
     249  							 strdata));
     250  
     251        /* Now we can determine whether on refill we have to create a new
     252  	 record or not.  */
     253        if (he != NULL)
     254  	{
     255  	  assert (fd == -1);
     256  
     257  	  if (dataset->head.allocsize == dh->allocsize
     258  	      && dataset->head.recsize == dh->recsize
     259  	      && memcmp (&dataset->resp, dh->data,
     260  			 dh->allocsize - offsetof (struct dataset, resp)) == 0)
     261  	    {
     262  	      /* The data has not changed.  We will just bump the
     263  		 timeout value.  Note that the new record has been
     264  		 allocated on the stack and need not be freed.  */
     265  	      dh->timeout = dataset->head.timeout;
     266  	      ++dh->nreloads;
     267  	    }
     268  	  else
     269  	    {
     270  	      /* We have to create a new record.  Just allocate
     271  		 appropriate memory and copy it.  */
     272  	      struct dataset *newp
     273  		= (struct dataset *) mempool_alloc (db, total + n, 1);
     274  	      if (newp != NULL)
     275  		{
     276  		  /* Adjust pointer into the memory block.  */
     277  		  cp = (char *) newp + (cp - (char *) dataset);
     278  		  key_copy = (char *) newp + (key_copy - (char *) dataset);
     279  
     280  		  dataset = memcpy (newp, dataset, total + n);
     281  		  alloca_used = false;
     282  		}
     283  
     284  	      /* Mark the old record as obsolete.  */
     285  	      dh->usable = false;
     286  	    }
     287  	}
     288        else
     289  	{
     290  	  /* We write the dataset before inserting it to the database
     291  	     since while inserting this thread might block and so would
     292  	     unnecessarily let the receiver wait.  */
     293  	  assert (fd != -1);
     294  
     295  	  if (writeall (fd, &dataset->resp, dataset->head.recsize)
     296  	      != dataset->head.recsize)
     297  	    all_written = false;
     298  	}
     299  
     300  
     301        /* Add the record to the database.  But only if it has not been
     302  	 stored on the stack.  */
     303        if (! alloca_used)
     304  	{
     305  	  /* If necessary, we also propagate the data to disk.  */
     306  	  if (db->persistent)
     307  	    {
     308  	      // XXX async OK?
     309  	      uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
     310  	      msync ((void *) pval,
     311  		     ((uintptr_t) dataset & pagesize_m1) + total + n,
     312  		     MS_ASYNC);
     313  	    }
     314  
     315  	  /* NB: in the following code we always must add the entry
     316  	     marked with FIRST first.  Otherwise we end up with
     317  	     dangling "pointers" in case a latter hash entry cannot be
     318  	     added.  */
     319  	  bool first = true;
     320  
     321  	  /* If the request was by UID, add that entry first.  */
     322  	  if (req->type == GETPWBYUID)
     323  	    {
     324  	      if (cache_add (GETPWBYUID, cp, key_offset, &dataset->head, true,
     325  			     db, owner, he == NULL) < 0)
     326  		goto out;
     327  
     328  	      first = false;
     329  	    }
     330  	  /* If the key is different from the name add a separate entry.  */
     331  	  else if (strcmp (key_copy, dataset->strdata) != 0)
     332  	    {
     333  	      if (cache_add (GETPWBYNAME, key_copy, key_len + 1,
     334  			     &dataset->head, true, db, owner, he == NULL) < 0)
     335  		goto out;
     336  
     337  	      first = false;
     338  	    }
     339  
     340  	  /* We have to add the value for both, byname and byuid.  */
     341  	  if ((req->type == GETPWBYNAME || db->propagate)
     342  	      && __builtin_expect (cache_add (GETPWBYNAME, dataset->strdata,
     343  					      pw_name_len, &dataset->head,
     344  					      first, db, owner, he == NULL)
     345  				   == 0, 1))
     346  	    {
     347  	      if (req->type == GETPWBYNAME && db->propagate)
     348  		(void) cache_add (GETPWBYUID, cp, key_offset, &dataset->head,
     349  				  false, db, owner, false);
     350  	    }
     351  
     352  	out:
     353  	  pthread_rwlock_unlock (&db->lock);
     354  	  if (he == NULL && db->propagate)
     355  	    pthread_mutex_unlock (&db->prune_run_lock);
     356  	}
     357      }
     358  
     359    if (__builtin_expect (!all_written, 0) && debug_level > 0)
     360      {
     361        char buf[256];
     362        dbg_log (_("short write in %s: %s"),  __FUNCTION__,
     363  	       strerror_r (errno, buf, sizeof (buf)));
     364      }
     365  
     366    return timeout;
     367  }
     368  
     369  
     370  union keytype
     371  {
     372    void *v;
     373    uid_t u;
     374  };
     375  
     376  
     377  static int
     378  lookup (int type, union keytype key, struct passwd *resultbufp, char *buffer,
     379  	size_t buflen, struct passwd **pwd)
     380  {
     381    if (type == GETPWBYNAME)
     382      return __getpwnam_r (key.v, resultbufp, buffer, buflen, pwd);
     383    else
     384      return __getpwuid_r (key.u, resultbufp, buffer, buflen, pwd);
     385  }
     386  
     387  
     388  static time_t
     389  addpwbyX (struct database_dyn *db, int fd, request_header *req,
     390  	  union keytype key, const char *keystr, uid_t c_uid,
     391  	  struct hashentry *he, struct datahead *dh)
     392  {
     393    /* Search for the entry matching the key.  Please note that we don't
     394       look again in the table whether the dataset is now available.  We
     395       simply insert it.  It does not matter if it is in there twice.  The
     396       pruning function only will look at the timestamp.  */
     397    struct passwd resultbuf;
     398    struct passwd *pwd;
     399    int errval = 0;
     400    struct scratch_buffer tmpbuf;
     401    scratch_buffer_init (&tmpbuf);
     402  
     403    if (__glibc_unlikely (debug_level > 0))
     404      {
     405        if (he == NULL)
     406  	dbg_log (_("Haven't found \"%s\" in user database cache!"), keystr);
     407        else
     408  	dbg_log (_("Reloading \"%s\" in user database cache!"), keystr);
     409      }
     410  
     411    while (lookup (req->type, key, &resultbuf,
     412  		 tmpbuf.data, tmpbuf.length, &pwd) != 0
     413  	 && (errval = errno) == ERANGE)
     414      if (!scratch_buffer_grow (&tmpbuf))
     415        {
     416  	/* We ran out of memory.  We cannot do anything but sending a
     417  	   negative response.  In reality this should never
     418  	   happen.  */
     419  	pwd = NULL;
     420  	/* We set the error to indicate this is (possibly) a temporary
     421  	   error and that it does not mean the entry is not available
     422  	   at all.  */
     423  	errval = EAGAIN;
     424  	break;
     425        }
     426  
     427    /* Add the entry to the cache.  */
     428    time_t timeout = cache_addpw (db, fd, req, keystr, pwd, c_uid, he, dh,
     429  				errval);
     430    scratch_buffer_free (&tmpbuf);
     431    return timeout;
     432  }
     433  
     434  
     435  void
     436  addpwbyname (struct database_dyn *db, int fd, request_header *req,
     437  	     void *key, uid_t c_uid)
     438  {
     439    union keytype u = { .v = key };
     440  
     441    addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
     442  }
     443  
     444  
     445  time_t
     446  readdpwbyname (struct database_dyn *db, struct hashentry *he,
     447  	       struct datahead *dh)
     448  {
     449    request_header req =
     450      {
     451        .type = GETPWBYNAME,
     452        .key_len = he->len
     453      };
     454    union keytype u = { .v = db->data + he->key };
     455  
     456    return addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
     457  }
     458  
     459  
     460  void
     461  addpwbyuid (struct database_dyn *db, int fd, request_header *req,
     462  	    void *key, uid_t c_uid)
     463  {
     464    char *ep;
     465    uid_t uid = strtoul ((char *) key, &ep, 10);
     466  
     467    if (*(char *) key == '\0' || *ep != '\0')  /* invalid numeric uid */
     468      {
     469        if (debug_level > 0)
     470  	dbg_log (_("Invalid numeric uid \"%s\"!"), (char *) key);
     471  
     472        errno = EINVAL;
     473        return;
     474      }
     475  
     476    union keytype u = { .u = uid };
     477  
     478    addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
     479  }
     480  
     481  
     482  time_t
     483  readdpwbyuid (struct database_dyn *db, struct hashentry *he,
     484  	      struct datahead *dh)
     485  {
     486    char *ep;
     487    uid_t uid = strtoul (db->data + he->key, &ep, 10);
     488  
     489    /* Since the key has been added before it must be OK.  */
     490    assert (*(db->data + he->key) != '\0' && *ep == '\0');
     491  
     492    request_header req =
     493      {
     494        .type = GETPWBYUID,
     495        .key_len = he->len
     496      };
     497    union keytype u = { .u = uid };
     498  
     499    return addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
     500  }