Add NEWS entry for Update to Unicode 13.0.0 [BZ #25819]
[glibc.git] / nscd / netgroupcache.c
blob88c69d1e9c1d183eccb8e443e43b3f42add0602d
1 /* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2020 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@gmail.com>, 2011.
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.
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.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, see <https://www.gnu.org/licenses/>. */
19 #include <alloca.h>
20 #include <assert.h>
21 #include <errno.h>
22 #include <libintl.h>
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <sys/mman.h>
28 #include "../inet/netgroup.h"
29 #include "nscd.h"
30 #include "dbg_log.h"
32 #include <kernel-features.h>
35 /* This is the standard reply in case the service is disabled. */
36 static const netgroup_response_header disabled =
38 .version = NSCD_VERSION,
39 .found = -1,
40 .nresults = 0,
41 .result_len = 0
44 /* This is the struct describing how to write this record. */
45 const struct iovec netgroup_iov_disabled =
47 .iov_base = (void *) &disabled,
48 .iov_len = sizeof (disabled)
52 /* This is the standard reply in case we haven't found the dataset. */
53 static const netgroup_response_header notfound =
55 .version = NSCD_VERSION,
56 .found = 0,
57 .nresults = 0,
58 .result_len = 0
62 struct dataset
64 struct datahead head;
65 netgroup_response_header resp;
66 char strdata[0];
69 /* Sends a notfound message and prepares a notfound dataset to write to the
70 cache. Returns true if there was enough memory to allocate the dataset and
71 returns the dataset in DATASETP, total bytes to write in TOTALP and the
72 timeout in TIMEOUTP. KEY_COPY is set to point to the copy of the key in the
73 dataset. */
74 static bool
75 do_notfound (struct database_dyn *db, int fd, request_header *req,
76 const char *key, struct dataset **datasetp, ssize_t *totalp,
77 time_t *timeoutp, char **key_copy)
79 struct dataset *dataset;
80 ssize_t total;
81 time_t timeout;
82 bool cacheable = false;
84 total = sizeof (notfound);
85 timeout = time (NULL) + db->negtimeout;
87 if (fd != -1)
88 TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
90 dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
91 /* If we cannot permanently store the result, so be it. */
92 if (dataset != NULL)
94 timeout = datahead_init_neg (&dataset->head,
95 sizeof (struct dataset) + req->key_len,
96 total, db->negtimeout);
98 /* This is the reply. */
99 memcpy (&dataset->resp, &notfound, total);
101 /* Copy the key data. */
102 memcpy (dataset->strdata, key, req->key_len);
103 *key_copy = dataset->strdata;
105 cacheable = true;
107 *timeoutp = timeout;
108 *totalp = total;
109 *datasetp = dataset;
110 return cacheable;
113 static time_t
114 addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
115 const char *key, uid_t uid, struct hashentry *he,
116 struct datahead *dh, struct dataset **resultp,
117 void **tofreep)
119 if (__glibc_unlikely (debug_level > 0))
121 if (he == NULL)
122 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
123 else
124 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
127 static service_user *netgroup_database;
128 time_t timeout;
129 struct dataset *dataset;
130 bool cacheable = false;
131 ssize_t total;
132 bool found = false;
134 char *key_copy = NULL;
135 struct __netgrent data;
136 size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
137 size_t buffilled = sizeof (*dataset);
138 char *buffer = NULL;
139 size_t nentries = 0;
140 size_t group_len = strlen (key) + 1;
141 struct name_list *first_needed
142 = alloca (sizeof (struct name_list) + group_len);
143 *tofreep = NULL;
145 if (netgroup_database == NULL
146 && __nss_database_lookup2 ("netgroup", NULL, NULL, &netgroup_database))
148 /* No such service. */
149 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
150 &key_copy);
151 goto writeout;
154 memset (&data, '\0', sizeof (data));
155 buffer = xmalloc (buflen);
156 *tofreep = buffer;
157 first_needed->next = first_needed;
158 memcpy (first_needed->name, key, group_len);
159 data.needed_groups = first_needed;
161 while (data.needed_groups != NULL)
163 /* Add the next group to the list of those which are known. */
164 struct name_list *this_group = data.needed_groups->next;
165 if (this_group == data.needed_groups)
166 data.needed_groups = NULL;
167 else
168 data.needed_groups->next = this_group->next;
169 this_group->next = data.known_groups;
170 data.known_groups = this_group;
172 union
174 enum nss_status (*f) (const char *, struct __netgrent *);
175 void *ptr;
176 } setfct;
178 service_user *nip = netgroup_database;
179 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
180 while (!no_more)
182 enum nss_status status
183 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
185 if (status == NSS_STATUS_SUCCESS)
187 found = true;
188 union
190 enum nss_status (*f) (struct __netgrent *, char *, size_t,
191 int *);
192 void *ptr;
193 } getfct;
194 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
195 if (getfct.f != NULL)
196 while (1)
198 int e;
199 status = getfct.f (&data, buffer + buffilled,
200 buflen - buffilled - req->key_len, &e);
201 if (status == NSS_STATUS_SUCCESS)
203 if (data.type == triple_val)
205 const char *nhost = data.val.triple.host;
206 const char *nuser = data.val.triple.user;
207 const char *ndomain = data.val.triple.domain;
209 size_t hostlen = strlen (nhost ?: "") + 1;
210 size_t userlen = strlen (nuser ?: "") + 1;
211 size_t domainlen = strlen (ndomain ?: "") + 1;
213 if (nhost == NULL || nuser == NULL || ndomain == NULL
214 || nhost > nuser || nuser > ndomain)
216 const char *last = nhost;
217 if (last == NULL
218 || (nuser != NULL && nuser > last))
219 last = nuser;
220 if (last == NULL
221 || (ndomain != NULL && ndomain > last))
222 last = ndomain;
224 size_t bufused
225 = (last == NULL
226 ? buffilled
227 : last + strlen (last) + 1 - buffer);
229 /* We have to make temporary copies. */
230 size_t needed = hostlen + userlen + domainlen;
232 if (buflen - req->key_len - bufused < needed)
234 buflen += MAX (buflen, 2 * needed);
235 /* Save offset in the old buffer. We don't
236 bother with the NULL check here since
237 we'll do that later anyway. */
238 size_t nhostdiff = nhost - buffer;
239 size_t nuserdiff = nuser - buffer;
240 size_t ndomaindiff = ndomain - buffer;
242 char *newbuf = xrealloc (buffer, buflen);
243 /* Fix up the triplet pointers into the new
244 buffer. */
245 nhost = (nhost ? newbuf + nhostdiff
246 : NULL);
247 nuser = (nuser ? newbuf + nuserdiff
248 : NULL);
249 ndomain = (ndomain ? newbuf + ndomaindiff
250 : NULL);
251 buffer = newbuf;
254 nhost = memcpy (buffer + bufused,
255 nhost ?: "", hostlen);
256 nuser = memcpy ((char *) nhost + hostlen,
257 nuser ?: "", userlen);
258 ndomain = memcpy ((char *) nuser + userlen,
259 ndomain ?: "", domainlen);
262 char *wp = buffer + buffilled;
263 wp = memmove (wp, nhost ?: "", hostlen);
264 wp += hostlen;
265 wp = memmove (wp, nuser ?: "", userlen);
266 wp += userlen;
267 wp = memmove (wp, ndomain ?: "", domainlen);
268 wp += domainlen;
269 buffilled = wp - buffer;
270 ++nentries;
272 else
274 /* Check that the group has not been
275 requested before. */
276 struct name_list *runp = data.needed_groups;
277 if (runp != NULL)
278 while (1)
280 if (strcmp (runp->name, data.val.group) == 0)
281 break;
283 runp = runp->next;
284 if (runp == data.needed_groups)
286 runp = NULL;
287 break;
291 if (runp == NULL)
293 runp = data.known_groups;
294 while (runp != NULL)
295 if (strcmp (runp->name, data.val.group) == 0)
296 break;
297 else
298 runp = runp->next;
301 if (runp == NULL)
303 /* A new group is requested. */
304 size_t namelen = strlen (data.val.group) + 1;
305 struct name_list *newg = alloca (sizeof (*newg)
306 + namelen);
307 memcpy (newg->name, data.val.group, namelen);
308 if (data.needed_groups == NULL)
309 data.needed_groups = newg->next = newg;
310 else
312 newg->next = data.needed_groups->next;
313 data.needed_groups->next = newg;
314 data.needed_groups = newg;
319 else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE)
321 buflen *= 2;
322 buffer = xrealloc (buffer, buflen);
324 else if (status == NSS_STATUS_RETURN
325 || status == NSS_STATUS_NOTFOUND
326 || status == NSS_STATUS_UNAVAIL)
327 /* This was either the last one for this group or the
328 group was empty or the NSS module had an internal
329 failure. Look at next group if available. */
330 break;
333 enum nss_status (*endfct) (struct __netgrent *);
334 endfct = __nss_lookup_function (nip, "endnetgrent");
335 if (endfct != NULL)
336 (void) DL_CALL_FCT (*endfct, (&data));
338 break;
341 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
342 status, 0);
346 /* No results. Return a failure and write out a notfound record in the
347 cache. */
348 if (!found)
350 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
351 &key_copy);
352 goto writeout;
355 total = buffilled;
357 /* Fill in the dataset. */
358 dataset = (struct dataset *) buffer;
359 timeout = datahead_init_pos (&dataset->head, total + req->key_len,
360 total - offsetof (struct dataset, resp),
361 he == NULL ? 0 : dh->nreloads + 1,
362 db->postimeout);
364 dataset->resp.version = NSCD_VERSION;
365 dataset->resp.found = 1;
366 dataset->resp.nresults = nentries;
367 dataset->resp.result_len = buffilled - sizeof (*dataset);
369 assert (buflen - buffilled >= req->key_len);
370 key_copy = memcpy (buffer + buffilled, key, req->key_len);
371 buffilled += req->key_len;
373 /* Now we can determine whether on refill we have to create a new
374 record or not. */
375 if (he != NULL)
377 assert (fd == -1);
379 if (dataset->head.allocsize == dh->allocsize
380 && dataset->head.recsize == dh->recsize
381 && memcmp (&dataset->resp, dh->data,
382 dh->allocsize - offsetof (struct dataset, resp)) == 0)
384 /* The data has not changed. We will just bump the timeout
385 value. Note that the new record has been allocated on
386 the stack and need not be freed. */
387 dh->timeout = dataset->head.timeout;
388 dh->ttl = dataset->head.ttl;
389 ++dh->nreloads;
390 dataset = (struct dataset *) dh;
392 goto out;
397 struct dataset *newp
398 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
399 if (__glibc_likely (newp != NULL))
401 /* Adjust pointer into the memory block. */
402 key_copy = (char *) newp + (key_copy - buffer);
404 dataset = memcpy (newp, dataset, total + req->key_len);
405 cacheable = true;
407 if (he != NULL)
408 /* Mark the old record as obsolete. */
409 dh->usable = false;
413 if (he == NULL && fd != -1)
415 /* We write the dataset before inserting it to the database
416 since while inserting this thread might block and so would
417 unnecessarily let the receiver wait. */
418 writeout:
419 writeall (fd, &dataset->resp, dataset->head.recsize);
422 if (cacheable)
424 /* If necessary, we also propagate the data to disk. */
425 if (db->persistent)
427 // XXX async OK?
428 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
429 msync ((void *) pval,
430 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
431 MS_ASYNC);
434 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
435 true, db, uid, he == NULL);
437 pthread_rwlock_unlock (&db->lock);
439 /* Mark the old entry as obsolete. */
440 if (dh != NULL)
441 dh->usable = false;
444 out:
445 *resultp = dataset;
447 return timeout;
451 static time_t
452 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
453 char *key, uid_t uid, struct hashentry *he,
454 struct datahead *dh)
456 const char *group = key;
457 key = (char *) rawmemchr (key, '\0') + 1;
458 size_t group_len = key - group;
459 const char *host = *key++ ? key : NULL;
460 if (host != NULL)
461 key = (char *) rawmemchr (key, '\0') + 1;
462 const char *user = *key++ ? key : NULL;
463 if (user != NULL)
464 key = (char *) rawmemchr (key, '\0') + 1;
465 const char *domain = *key++ ? key : NULL;
467 if (__glibc_unlikely (debug_level > 0))
469 if (he == NULL)
470 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
471 group, host ?: "", user ?: "", domain ?: "");
472 else
473 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
474 group, host ?: "", user ?: "", domain ?: "");
477 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
478 group, group_len,
479 db, uid);
480 time_t timeout;
481 void *tofree;
482 if (result != NULL)
484 timeout = result->head.timeout;
485 tofree = NULL;
487 else
489 request_header req_get =
491 .type = GETNETGRENT,
492 .key_len = group_len
494 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
495 &result, &tofree);
498 struct indataset
500 struct datahead head;
501 innetgroup_response_header resp;
502 } *dataset
503 = (struct indataset *) mempool_alloc (db,
504 sizeof (*dataset) + req->key_len,
506 struct indataset dataset_mem;
507 bool cacheable = true;
508 if (__glibc_unlikely (dataset == NULL))
510 cacheable = false;
511 dataset = &dataset_mem;
514 datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len,
515 sizeof (innetgroup_response_header),
516 he == NULL ? 0 : dh->nreloads + 1, result->head.ttl);
517 /* Set the notfound status and timeout based on the result from
518 getnetgrent. */
519 dataset->head.notfound = result->head.notfound;
520 dataset->head.timeout = timeout;
522 dataset->resp.version = NSCD_VERSION;
523 dataset->resp.found = result->resp.found;
524 /* Until we find a matching entry the result is 0. */
525 dataset->resp.result = 0;
527 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
529 if (dataset->resp.found)
531 const char *triplets = (const char *) (&result->resp + 1);
533 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
535 bool success = true;
537 /* For the host, user and domain in each triplet, we assume success
538 if the value is blank because that is how the wildcard entry to
539 match anything is stored in the netgroup cache. */
540 if (host != NULL && *triplets != '\0')
541 success = strcmp (host, triplets) == 0;
542 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
544 if (success && user != NULL && *triplets != '\0')
545 success = strcmp (user, triplets) == 0;
546 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
548 if (success && (domain == NULL || *triplets == '\0'
549 || strcmp (domain, triplets) == 0))
551 dataset->resp.result = 1;
552 break;
554 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
558 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
560 /* The data has not changed. We will just bump the timeout
561 value. Note that the new record has been allocated on
562 the stack and need not be freed. */
563 dh->timeout = timeout;
564 dh->ttl = dataset->head.ttl;
565 ++dh->nreloads;
566 if (cacheable)
567 pthread_rwlock_unlock (&db->lock);
568 goto out;
571 if (he == NULL)
573 /* We write the dataset before inserting it to the database
574 since while inserting this thread might block and so would
575 unnecessarily let the receiver wait. */
576 assert (fd != -1);
578 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
581 if (cacheable)
583 /* If necessary, we also propagate the data to disk. */
584 if (db->persistent)
586 // XXX async OK?
587 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
588 msync ((void *) pval,
589 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
590 + req->key_len,
591 MS_ASYNC);
594 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
595 true, db, uid, he == NULL);
597 pthread_rwlock_unlock (&db->lock);
599 /* Mark the old entry as obsolete. */
600 if (dh != NULL)
601 dh->usable = false;
604 out:
605 free (tofree);
606 return timeout;
610 static time_t
611 addgetnetgrentX_ignore (struct database_dyn *db, int fd, request_header *req,
612 const char *key, uid_t uid, struct hashentry *he,
613 struct datahead *dh)
615 struct dataset *ignore;
616 void *tofree;
617 time_t timeout = addgetnetgrentX (db, fd, req, key, uid, he, dh,
618 &ignore, &tofree);
619 free (tofree);
620 return timeout;
623 void
624 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
625 void *key, uid_t uid)
627 addgetnetgrentX_ignore (db, fd, req, key, uid, NULL, NULL);
631 time_t
632 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
633 struct datahead *dh)
635 request_header req =
637 .type = GETNETGRENT,
638 .key_len = he->len
640 return addgetnetgrentX_ignore
641 (db, -1, &req, db->data + he->key, he->owner, he, dh);
645 void
646 addinnetgr (struct database_dyn *db, int fd, request_header *req,
647 void *key, uid_t uid)
649 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
653 time_t
654 readdinnetgr (struct database_dyn *db, struct hashentry *he,
655 struct datahead *dh)
657 request_header req =
659 .type = INNETGR,
660 .key_len = he->len
663 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);