1 /* Cache handling for passwd lookup.
2    Copyright (C) 1998-2022 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
cache_addpw(struct database_dyn * db,int fd,request_header * req,const void * key,struct passwd * pwd,uid_t owner,struct hashentry * const he,struct datahead * dh,int errval)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 (errno == EAGAIN || __builtin_expect (db->negtimeout == 0, 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 == (char *) rawmemchr (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
lookup(int type,union keytype key,struct passwd * resultbufp,char * buffer,size_t buflen,struct passwd ** pwd)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
addpwbyX(struct database_dyn * db,int fd,request_header * req,union keytype key,const char * keystr,uid_t c_uid,struct hashentry * he,struct datahead * dh)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
addpwbyname(struct database_dyn * db,int fd,request_header * req,void * key,uid_t c_uid)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
readdpwbyname(struct database_dyn * db,struct hashentry * he,struct datahead * dh)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
addpwbyuid(struct database_dyn * db,int fd,request_header * req,void * key,uid_t c_uid)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
readdpwbyuid(struct database_dyn * db,struct hashentry * he,struct datahead * dh)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 }
501