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. */
35static 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. */
44const 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. */
52static const netgroup_response_header notfound =
53{
54 .version = NSCD_VERSION,
55 .found = 0,
56 .nresults = 0,
57 .result_len = 0
58};
59
60
61struct 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. */
73static bool
74do_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, len: sizeof (struct dataset) + req->key_len, data_alloc: 1);
90 /* If we cannot permanently store the result, so be it. */
91 if (dataset != NULL)
92 {
93 timeout = datahead_init_neg (head: &dataset->head,
94 allocsize: sizeof (struct dataset) + req->key_len,
95 recsize: total, ttl: db->negtimeout);
96
97 /* This is the reply. */
98 memcpy (dest: &dataset->resp, src: &notfound, len: total);
99
100 /* Copy the key data. */
101 memcpy (dest: dataset->strdata, src: key, len: 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
112static time_t
113addgetnetgrentX (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 (s: 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 (db: nss_database_netgroup, actions: &netgroup_database))
146 {
147 /* No such service. */
148 cacheable = do_notfound (db, fd, req, key, datasetp: &dataset, totalp: &total, timeoutp: &timeout,
149 key_copy: &key_copy);
150 goto writeout;
151 }
152
153 memset (dest: &data, ch: '\0', len: sizeof (data));
154 buffer = xmalloc (n: buflen);
155 *tofreep = buffer;
156 first_needed->next = first_needed;
157 memcpy (dest: first_needed->name, src: key, len: 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 (ni: &nip, fct_name: "setnetgrent", NULL, fctp: &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 (ni: nip, fct_name: "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 (s: nhost ?: "") + 1;
209 size_t userlen = strlen (s: nuser ?: "") + 1;
210 size_t domainlen = strlen (s: 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 (s: 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 (o: buffer, n: 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 (dest: buffer + bufused,
254 src: nhost ?: "", len: hostlen);
255 nuser = memcpy (dest: (char *) nhost + hostlen,
256 src: nuser ?: "", len: userlen);
257 ndomain = memcpy (dest: (char *) nuser + userlen,
258 src: ndomain ?: "", len: domainlen);
259 }
260
261 char *wp = buffer + buffilled;
262 wp = memmove (dest: wp, src: nhost ?: "", len: hostlen);
263 wp += hostlen;
264 wp = memmove (dest: wp, src: nuser ?: "", len: userlen);
265 wp += userlen;
266 wp = memmove (dest: wp, src: ndomain ?: "", len: 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 (s1: runp->name, s2: 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 (s1: runp->name, s2: 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 (s: data.val.group) + 1;
304 struct name_list *newg = alloca (sizeof (*newg)
305 + namelen);
306 memcpy (dest: newg->name, src: data.val.group, len: 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 (o: buffer, n: 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 (ni: nip, fct_name: "endnetgrent");
334 if (endfct != NULL)
335 (void) DL_CALL_FCT (*endfct, (&data));
336
337 break;
338 }
339
340 no_more = __nss_next2 (ni: &nip, fct_name: "setnetgrent", NULL, fctp: &setfct.ptr,
341 status, all_values: 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, datasetp: &dataset, totalp: &total, timeoutp: &timeout,
350 key_copy: &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 (head: &dataset->head, allocsize: total + req->key_len,
359 recsize: total - offsetof (struct dataset, resp),
360 nreloads: he == NULL ? 0 : dh->nreloads + 1,
361 ttl: 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 (dest: buffer + buffilled, src: key, len: 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 (s1: &dataset->resp, s2: dh->data,
381 n: 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, len: total + req->key_len, data_alloc: 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 (dest: newp, src: dataset, len: 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, buf: &dataset->resp, len: 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 (addr: (void *) pval,
429 len: ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
430 MS_ASYNC);
431 }
432
433 (void) cache_add (type: req->type, key: key_copy, len: req->key_len, packet: &dataset->head,
434 true, table: db, owner: uid, prune_wakeup: he == NULL);
435
436 pthread_rwlock_unlock (rwlock: &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
450static time_t
451addinnetgrX (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 (s: key, c: '\0') + 1;
457 size_t group_len = key - group;
458 const char *host = *key++ ? key : NULL;
459 if (host != NULL)
460 key = (char *) rawmemchr (s: key, c: '\0') + 1;
461 const char *user = *key++ ? key : NULL;
462 if (user != NULL)
463 key = (char *) rawmemchr (s: key, c: '\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 key: group, len: group_len,
478 table: db, owner: 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, fd: -1, req: &req_get, key: group, uid, NULL, NULL,
494 resultp: &result, tofreep: &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 len: sizeof (*dataset) + req->key_len,
504 data_alloc: 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 (head: &dataset->head, allocsize: sizeof (*dataset) + req->key_len,
514 recsize: sizeof (innetgroup_response_header),
515 nreloads: he == NULL ? 0 : dh->nreloads + 1, ttl: 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 (dest: (char *) (dataset + 1), src: group, len: 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 (s1: host, s2: triplets) == 0;
541 triplets = (const char *) rawmemchr (s: triplets, c: '\0') + 1;
542
543 if (success && user != NULL && *triplets != '\0')
544 success = strcmp (s1: user, s2: triplets) == 0;
545 triplets = (const char *) rawmemchr (s: triplets, c: '\0') + 1;
546
547 if (success && (domain == NULL || *triplets == '\0'
548 || strcmp (s1: domain, s2: triplets) == 0))
549 {
550 dataset->resp.result = 1;
551 break;
552 }
553 triplets = (const char *) rawmemchr (s: triplets, c: '\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 (rwlock: &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, buf: &dataset->resp, len: 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 (addr: (void *) pval,
588 len: ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
589 + req->key_len,
590 MS_ASYNC);
591 }
592
593 (void) cache_add (type: req->type, key: key_copy, len: req->key_len, packet: &dataset->head,
594 true, table: db, owner: uid, prune_wakeup: he == NULL);
595
596 pthread_rwlock_unlock (rwlock: &db->lock);
597
598 /* Mark the old entry as obsolete. */
599 if (dh != NULL)
600 dh->usable = false;
601 }
602
603 out:
604 free (ptr: tofree);
605 return timeout;
606}
607
608
609static time_t
610addgetnetgrentX_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 resultp: &ignore, tofreep: &tofree);
618 free (ptr: tofree);
619 return timeout;
620}
621
622void
623addgetnetgrent (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
630time_t
631readdgetnetgrent (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, fd: -1, req: &req, key: db->data + he->key, uid: he->owner, he, dh);
641}
642
643
644void
645addinnetgr (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
652time_t
653readdinnetgr (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, fd: -1, req: &req, key: db->data + he->key, uid: he->owner, he, dh);
663}
664

source code of glibc/nscd/netgroupcache.c