1 /* Cache handling for netgroup lookup.
2    Copyright (C) 2011-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 <alloca.h>
19 #include <assert.h>
20 #include <errno.h>
21 #include <libintl.h>
22 #include <stdbool.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <sys/mman.h>
26 
27 #include "../inet/netgroup.h"
28 #include "nscd.h"
29 #include "dbg_log.h"
30 
31 #include <kernel-features.h>
32 
33 
34 /* This is the standard reply in case the service is disabled.  */
35 static const netgroup_response_header disabled =
36 {
37   .version = NSCD_VERSION,
38   .found = -1,
39   .nresults = 0,
40   .result_len = 0
41 };
42 
43 /* This is the struct describing how to write this record.  */
44 const struct iovec netgroup_iov_disabled =
45 {
46   .iov_base = (void *) &disabled,
47   .iov_len = sizeof (disabled)
48 };
49 
50 
51 /* This is the standard reply in case we haven't found the dataset.  */
52 static const netgroup_response_header notfound =
53 {
54   .version = NSCD_VERSION,
55   .found = 0,
56   .nresults = 0,
57   .result_len = 0
58 };
59 
60 
61 struct dataset
62 {
63   struct datahead head;
64   netgroup_response_header resp;
65   char strdata[0];
66 };
67 
68 /* Sends a notfound message and prepares a notfound dataset to write to the
69    cache.  Returns true if there was enough memory to allocate the dataset and
70    returns the dataset in DATASETP, total bytes to write in TOTALP and the
71    timeout in TIMEOUTP.  KEY_COPY is set to point to the copy of the key in the
72    dataset. */
73 static bool
do_notfound(struct database_dyn * db,int fd,request_header * req,const char * key,struct dataset ** datasetp,ssize_t * totalp,time_t * timeoutp,char ** key_copy)74 do_notfound (struct database_dyn *db, int fd, request_header *req,
75 	       const char *key, struct dataset **datasetp, ssize_t *totalp,
76 	       time_t *timeoutp, char **key_copy)
77 {
78   struct dataset *dataset;
79   ssize_t total;
80   time_t timeout;
81   bool cacheable = false;
82 
83   total = sizeof (notfound);
84   timeout = time (NULL) + db->negtimeout;
85 
86   if (fd != -1)
87     TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
88 
89   dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
90   /* If we cannot permanently store the result, so be it.  */
91   if (dataset != NULL)
92     {
93       timeout = datahead_init_neg (&dataset->head,
94 				   sizeof (struct dataset) + req->key_len,
95 				   total, db->negtimeout);
96 
97       /* This is the reply.  */
98       memcpy (&dataset->resp, &notfound, total);
99 
100       /* Copy the key data.  */
101       memcpy (dataset->strdata, key, req->key_len);
102       *key_copy = dataset->strdata;
103 
104       cacheable = true;
105     }
106   *timeoutp = timeout;
107   *totalp = total;
108   *datasetp = dataset;
109   return cacheable;
110 }
111 
112 static time_t
addgetnetgrentX(struct database_dyn * db,int fd,request_header * req,const char * key,uid_t uid,struct hashentry * he,struct datahead * dh,struct dataset ** resultp,void ** tofreep)113 addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
114 		 const char *key, uid_t uid, struct hashentry *he,
115 		 struct datahead *dh, struct dataset **resultp,
116 		 void **tofreep)
117 {
118   if (__glibc_unlikely (debug_level > 0))
119     {
120       if (he == NULL)
121 	dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
122       else
123 	dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
124     }
125 
126   static nss_action_list netgroup_database;
127   time_t timeout;
128   struct dataset *dataset;
129   bool cacheable = false;
130   ssize_t total;
131   bool found = false;
132 
133   char *key_copy = NULL;
134   struct __netgrent data;
135   size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
136   size_t buffilled = sizeof (*dataset);
137   char *buffer = NULL;
138   size_t nentries = 0;
139   size_t group_len = strlen (key) + 1;
140   struct name_list *first_needed
141     = alloca (sizeof (struct name_list) + group_len);
142   *tofreep = NULL;
143 
144   if (netgroup_database == NULL
145       && !__nss_database_get (nss_database_netgroup, &netgroup_database))
146     {
147       /* No such service.  */
148       cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
149 			       &key_copy);
150       goto writeout;
151     }
152 
153   memset (&data, '\0', sizeof (data));
154   buffer = xmalloc (buflen);
155   *tofreep = buffer;
156   first_needed->next = first_needed;
157   memcpy (first_needed->name, key, group_len);
158   data.needed_groups = first_needed;
159 
160   while (data.needed_groups != NULL)
161     {
162       /* Add the next group to the list of those which are known.  */
163       struct name_list *this_group = data.needed_groups->next;
164       if (this_group == data.needed_groups)
165 	data.needed_groups = NULL;
166       else
167 	data.needed_groups->next = this_group->next;
168       this_group->next = data.known_groups;
169       data.known_groups = this_group;
170 
171       union
172       {
173 	enum nss_status (*f) (const char *, struct __netgrent *);
174 	void *ptr;
175       } setfct;
176 
177       nss_action_list nip = netgroup_database;
178       int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
179       while (!no_more)
180 	{
181 	  enum nss_status status
182 	    = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
183 
184 	  if (status == NSS_STATUS_SUCCESS)
185 	    {
186 	      found = true;
187 	      union
188 	      {
189 		enum nss_status (*f) (struct __netgrent *, char *, size_t,
190 				      int *);
191 		void *ptr;
192 	      } getfct;
193 	      getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
194 	      if (getfct.f != NULL)
195 		while (1)
196 		  {
197 		    int e;
198 		    status = getfct.f (&data, buffer + buffilled,
199 				       buflen - buffilled - req->key_len, &e);
200 		    if (status == NSS_STATUS_SUCCESS)
201 		      {
202 			if (data.type == triple_val)
203 			  {
204 			    const char *nhost = data.val.triple.host;
205 			    const char *nuser = data.val.triple.user;
206 			    const char *ndomain = data.val.triple.domain;
207 
208 			    size_t hostlen = strlen (nhost ?: "") + 1;
209 			    size_t userlen = strlen (nuser ?: "") + 1;
210 			    size_t domainlen = strlen (ndomain ?: "") + 1;
211 
212 			    if (nhost == NULL || nuser == NULL || ndomain == NULL
213 				|| nhost > nuser || nuser > ndomain)
214 			      {
215 				const char *last = nhost;
216 				if (last == NULL
217 				    || (nuser != NULL && nuser > last))
218 				  last = nuser;
219 				if (last == NULL
220 				    || (ndomain != NULL && ndomain > last))
221 				  last = ndomain;
222 
223 				size_t bufused
224 				  = (last == NULL
225 				     ? buffilled
226 				     : last + strlen (last) + 1 - buffer);
227 
228 				/* We have to make temporary copies.  */
229 				size_t needed = hostlen + userlen + domainlen;
230 
231 				if (buflen - req->key_len - bufused < needed)
232 				  {
233 				    buflen += MAX (buflen, 2 * needed);
234 				    /* Save offset in the old buffer.  We don't
235 				       bother with the NULL check here since
236 				       we'll do that later anyway.  */
237 				    size_t nhostdiff = nhost - buffer;
238 				    size_t nuserdiff = nuser - buffer;
239 				    size_t ndomaindiff = ndomain - buffer;
240 
241 				    char *newbuf = xrealloc (buffer, buflen);
242 				    /* Fix up the triplet pointers into the new
243 				       buffer.  */
244 				    nhost = (nhost ? newbuf + nhostdiff
245 					     : NULL);
246 				    nuser = (nuser ? newbuf + nuserdiff
247 					     : NULL);
248 				    ndomain = (ndomain ? newbuf + ndomaindiff
249 					       : NULL);
250 				    *tofreep = buffer = newbuf;
251 				  }
252 
253 				nhost = memcpy (buffer + bufused,
254 						nhost ?: "", hostlen);
255 				nuser = memcpy ((char *) nhost + hostlen,
256 						nuser ?: "", userlen);
257 				ndomain = memcpy ((char *) nuser + userlen,
258 						  ndomain ?: "", domainlen);
259 			      }
260 
261 			    char *wp = buffer + buffilled;
262 			    wp = memmove (wp, nhost ?: "", hostlen);
263 			    wp += hostlen;
264 			    wp = memmove (wp, nuser ?: "", userlen);
265 			    wp += userlen;
266 			    wp = memmove (wp, ndomain ?: "", domainlen);
267 			    wp += domainlen;
268 			    buffilled = wp - buffer;
269 			    ++nentries;
270 			  }
271 			else
272 			  {
273 			    /* Check that the group has not been
274 			       requested before.  */
275 			    struct name_list *runp = data.needed_groups;
276 			    if (runp != NULL)
277 			      while (1)
278 				{
279 				  if (strcmp (runp->name, data.val.group) == 0)
280 				    break;
281 
282 				  runp = runp->next;
283 				  if (runp == data.needed_groups)
284 				    {
285 				      runp = NULL;
286 				      break;
287 				    }
288 				}
289 
290 			    if (runp == NULL)
291 			      {
292 				runp = data.known_groups;
293 				while (runp != NULL)
294 				  if (strcmp (runp->name, data.val.group) == 0)
295 				    break;
296 				  else
297 				    runp = runp->next;
298 				}
299 
300 			    if (runp == NULL)
301 			      {
302 				/* A new group is requested.  */
303 				size_t namelen = strlen (data.val.group) + 1;
304 				struct name_list *newg = alloca (sizeof (*newg)
305 								 + namelen);
306 				memcpy (newg->name, data.val.group, namelen);
307 				if (data.needed_groups == NULL)
308 				  data.needed_groups = newg->next = newg;
309 				else
310 				  {
311 				    newg->next = data.needed_groups->next;
312 				    data.needed_groups->next = newg;
313 				    data.needed_groups = newg;
314 				  }
315 			      }
316 			  }
317 		      }
318 		    else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE)
319 		      {
320 			buflen *= 2;
321 			*tofreep = buffer = xrealloc (buffer, buflen);
322 		      }
323 		    else if (status == NSS_STATUS_RETURN
324 			     || status == NSS_STATUS_NOTFOUND
325 			     || status == NSS_STATUS_UNAVAIL)
326 		      /* This was either the last one for this group or the
327 			 group was empty or the NSS module had an internal
328 			 failure.  Look at next group if available.  */
329 		      break;
330 		  }
331 
332 	      enum nss_status (*endfct) (struct __netgrent *);
333 	      endfct = __nss_lookup_function (nip, "endnetgrent");
334 	      if (endfct != NULL)
335 		(void) DL_CALL_FCT (*endfct, (&data));
336 
337 	      break;
338 	    }
339 
340 	  no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
341 				 status, 0);
342 	}
343     }
344 
345   /* No results.  Return a failure and write out a notfound record in the
346      cache.  */
347   if (!found)
348     {
349       cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
350 			       &key_copy);
351       goto writeout;
352     }
353 
354   total = buffilled;
355 
356   /* Fill in the dataset.  */
357   dataset = (struct dataset *) buffer;
358   timeout = datahead_init_pos (&dataset->head, total + req->key_len,
359 			       total - offsetof (struct dataset, resp),
360 			       he == NULL ? 0 : dh->nreloads + 1,
361 			       db->postimeout);
362 
363   dataset->resp.version = NSCD_VERSION;
364   dataset->resp.found = 1;
365   dataset->resp.nresults = nentries;
366   dataset->resp.result_len = buffilled - sizeof (*dataset);
367 
368   assert (buflen - buffilled >= req->key_len);
369   key_copy = memcpy (buffer + buffilled, key, req->key_len);
370   buffilled += req->key_len;
371 
372   /* Now we can determine whether on refill we have to create a new
373      record or not.  */
374   if (he != NULL)
375     {
376       assert (fd == -1);
377 
378       if (dataset->head.allocsize == dh->allocsize
379 	  && dataset->head.recsize == dh->recsize
380 	  && memcmp (&dataset->resp, dh->data,
381 		     dh->allocsize - offsetof (struct dataset, resp)) == 0)
382 	{
383 	  /* The data has not changed.  We will just bump the timeout
384 	     value.  Note that the new record has been allocated on
385 	     the stack and need not be freed.  */
386 	  dh->timeout = dataset->head.timeout;
387 	  dh->ttl = dataset->head.ttl;
388 	  ++dh->nreloads;
389 	  dataset = (struct dataset *) dh;
390 
391 	  goto out;
392 	}
393     }
394 
395   {
396     struct dataset *newp
397       = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
398     if (__glibc_likely (newp != NULL))
399       {
400 	/* Adjust pointer into the memory block.  */
401 	key_copy = (char *) newp + (key_copy - buffer);
402 
403 	dataset = memcpy (newp, dataset, total + req->key_len);
404 	cacheable = true;
405 
406 	if (he != NULL)
407 	  /* Mark the old record as obsolete.  */
408 	  dh->usable = false;
409       }
410   }
411 
412   if (he == NULL && fd != -1)
413     {
414       /* We write the dataset before inserting it to the database
415 	 since while inserting this thread might block and so would
416 	 unnecessarily let the receiver wait.  */
417     writeout:
418       writeall (fd, &dataset->resp, dataset->head.recsize);
419     }
420 
421   if (cacheable)
422     {
423       /* If necessary, we also propagate the data to disk.  */
424       if (db->persistent)
425 	{
426 	  // XXX async OK?
427 	  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
428 	  msync ((void *) pval,
429 		 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
430 		 MS_ASYNC);
431 	}
432 
433       (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
434 			true, db, uid, he == NULL);
435 
436       pthread_rwlock_unlock (&db->lock);
437 
438       /* Mark the old entry as obsolete.  */
439       if (dh != NULL)
440 	dh->usable = false;
441     }
442 
443  out:
444   *resultp = dataset;
445 
446   return timeout;
447 }
448 
449 
450 static time_t
addinnetgrX(struct database_dyn * db,int fd,request_header * req,char * key,uid_t uid,struct hashentry * he,struct datahead * dh)451 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
452 	     char *key, uid_t uid, struct hashentry *he,
453 	     struct datahead *dh)
454 {
455   const char *group = key;
456   key = (char *) rawmemchr (key, '\0') + 1;
457   size_t group_len = key - group;
458   const char *host = *key++ ? key : NULL;
459   if (host != NULL)
460     key = (char *) rawmemchr (key, '\0') + 1;
461   const char *user = *key++ ? key : NULL;
462   if (user != NULL)
463     key = (char *) rawmemchr (key, '\0') + 1;
464   const char *domain = *key++ ? key : NULL;
465 
466   if (__glibc_unlikely (debug_level > 0))
467     {
468       if (he == NULL)
469 	dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
470 		 group, host ?: "", user ?: "", domain ?: "");
471       else
472 	dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
473 		 group, host ?: "", user ?: "", domain ?: "");
474     }
475 
476   struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
477 							    group, group_len,
478 							    db, uid);
479   time_t timeout;
480   void *tofree;
481   if (result != NULL)
482     {
483       timeout = result->head.timeout;
484       tofree = NULL;
485     }
486   else
487     {
488       request_header req_get =
489 	{
490 	  .type = GETNETGRENT,
491 	  .key_len = group_len
492 	};
493       timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
494 				 &result, &tofree);
495     }
496 
497   struct indataset
498   {
499     struct datahead head;
500     innetgroup_response_header resp;
501   } *dataset
502       = (struct indataset *) mempool_alloc (db,
503 					    sizeof (*dataset) + req->key_len,
504 					    1);
505   struct indataset dataset_mem;
506   bool cacheable = true;
507   if (__glibc_unlikely (dataset == NULL))
508     {
509       cacheable = false;
510       dataset = &dataset_mem;
511     }
512 
513   datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len,
514 		     sizeof (innetgroup_response_header),
515 		     he == NULL ? 0 : dh->nreloads + 1, result->head.ttl);
516   /* Set the notfound status and timeout based on the result from
517      getnetgrent.  */
518   dataset->head.notfound = result->head.notfound;
519   dataset->head.timeout = timeout;
520 
521   dataset->resp.version = NSCD_VERSION;
522   dataset->resp.found = result->resp.found;
523   /* Until we find a matching entry the result is 0.  */
524   dataset->resp.result = 0;
525 
526   char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
527 
528   if (dataset->resp.found)
529     {
530       const char *triplets = (const char *) (&result->resp + 1);
531 
532       for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
533 	{
534 	  bool success = true;
535 
536 	  /* For the host, user and domain in each triplet, we assume success
537 	     if the value is blank because that is how the wildcard entry to
538 	     match anything is stored in the netgroup cache.  */
539 	  if (host != NULL && *triplets != '\0')
540 	    success = strcmp (host, triplets) == 0;
541 	  triplets = (const char *) rawmemchr (triplets, '\0') + 1;
542 
543 	  if (success && user != NULL && *triplets != '\0')
544 	    success = strcmp (user, triplets) == 0;
545 	  triplets = (const char *) rawmemchr (triplets, '\0') + 1;
546 
547 	  if (success && (domain == NULL || *triplets == '\0'
548 			  || strcmp (domain, triplets) == 0))
549 	    {
550 	      dataset->resp.result = 1;
551 	      break;
552 	    }
553 	  triplets = (const char *) rawmemchr (triplets, '\0') + 1;
554 	}
555     }
556 
557   if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
558     {
559       /* The data has not changed.  We will just bump the timeout
560 	 value.  Note that the new record has been allocated on
561 	 the stack and need not be freed.  */
562       dh->timeout = timeout;
563       dh->ttl = dataset->head.ttl;
564       ++dh->nreloads;
565       if (cacheable)
566         pthread_rwlock_unlock (&db->lock);
567       goto out;
568     }
569 
570   if (he == NULL)
571     {
572       /* We write the dataset before inserting it to the database
573 	 since while inserting this thread might block and so would
574 	 unnecessarily let the receiver wait.  */
575       assert (fd != -1);
576 
577       writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
578     }
579 
580   if (cacheable)
581     {
582       /* If necessary, we also propagate the data to disk.  */
583       if (db->persistent)
584 	{
585 	  // XXX async OK?
586 	  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
587 	  msync ((void *) pval,
588 		 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
589 		 + req->key_len,
590 		 MS_ASYNC);
591 	}
592 
593       (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
594 			true, db, uid, he == NULL);
595 
596       pthread_rwlock_unlock (&db->lock);
597 
598       /* Mark the old entry as obsolete.  */
599       if (dh != NULL)
600 	dh->usable = false;
601     }
602 
603  out:
604   free (tofree);
605   return timeout;
606 }
607 
608 
609 static time_t
addgetnetgrentX_ignore(struct database_dyn * db,int fd,request_header * req,const char * key,uid_t uid,struct hashentry * he,struct datahead * dh)610 addgetnetgrentX_ignore (struct database_dyn *db, int fd, request_header *req,
611 			const char *key, uid_t uid, struct hashentry *he,
612 			struct datahead *dh)
613 {
614   struct dataset *ignore;
615   void *tofree;
616   time_t timeout = addgetnetgrentX (db, fd, req, key, uid, he, dh,
617 				    &ignore, &tofree);
618   free (tofree);
619   return timeout;
620 }
621 
622 void
addgetnetgrent(struct database_dyn * db,int fd,request_header * req,void * key,uid_t uid)623 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
624 		void *key, uid_t uid)
625 {
626   addgetnetgrentX_ignore (db, fd, req, key, uid, NULL, NULL);
627 }
628 
629 
630 time_t
readdgetnetgrent(struct database_dyn * db,struct hashentry * he,struct datahead * dh)631 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
632 		  struct datahead *dh)
633 {
634   request_header req =
635     {
636       .type = GETNETGRENT,
637       .key_len = he->len
638     };
639   return addgetnetgrentX_ignore
640     (db, -1, &req, db->data + he->key, he->owner, he, dh);
641 }
642 
643 
644 void
addinnetgr(struct database_dyn * db,int fd,request_header * req,void * key,uid_t uid)645 addinnetgr (struct database_dyn *db, int fd, request_header *req,
646 	    void *key, uid_t uid)
647 {
648   addinnetgrX (db, fd, req, key, uid, NULL, NULL);
649 }
650 
651 
652 time_t
readdinnetgr(struct database_dyn * db,struct hashentry * he,struct datahead * dh)653 readdinnetgr (struct database_dyn *db, struct hashentry *he,
654 	      struct datahead *dh)
655 {
656   request_header req =
657     {
658       .type = INNETGR,
659       .key_len = he->len
660     };
661 
662   return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);
663 }
664