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

source code of glibc/nscd/netgroupcache.c