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