1/* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2018 Free Software Foundation, Inc.
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
17 along with this program; if not, see <http://www.gnu.org/licenses/>. */
18
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>
27
28#include "../inet/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/* 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. */
74static bool
75do_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)
78{
79 struct dataset *dataset;
80 ssize_t total;
81 time_t timeout;
82 bool cacheable = false;
83
84 total = sizeof (notfound);
85 timeout = time (NULL) + db->negtimeout;
86
87 if (fd != -1)
88 TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
89
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)
93 {
94 timeout = datahead_init_neg (&dataset->head,
95 sizeof (struct dataset) + req->key_len,
96 total, db->negtimeout);
97
98 /* This is the reply. */
99 memcpy (&dataset->resp, &notfound, total);
100
101 /* Copy the key data. */
102 memcpy (dataset->strdata, key, req->key_len);
103 *key_copy = dataset->strdata;
104
105 cacheable = true;
106 }
107 *timeoutp = timeout;
108 *totalp = total;
109 *datasetp = dataset;
110 return cacheable;
111}
112
113static time_t
114addgetnetgrentX (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)
118{
119 if (__glibc_unlikely (debug_level > 0))
120 {
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);
125 }
126
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;
133
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;
144
145 if (netgroup_database == NULL
146 && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
147 {
148 /* No such service. */
149 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
150 &key_copy);
151 goto writeout;
152 }
153
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;
160
161 while (data.needed_groups != NULL)
162 {
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;
171
172 union
173 {
174 enum nss_status (*f) (const char *, struct __netgrent *);
175 void *ptr;
176 } setfct;
177
178 service_user *nip = netgroup_database;
179 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
180 while (!no_more)
181 {
182 enum nss_status status
183 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
184
185 if (status == NSS_STATUS_SUCCESS)
186 {
187 found = true;
188 union
189 {
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)
197 {
198 int e;
199 status = getfct.f (&data, buffer + buffilled,
200 buflen - buffilled - req->key_len, &e);
201 if (status == NSS_STATUS_SUCCESS)
202 {
203 if (data.type == triple_val)
204 {
205 const char *nhost = data.val.triple.host;
206 const char *nuser = data.val.triple.user;
207 const char *ndomain = data.val.triple.domain;
208
209 size_t hostlen = strlen (nhost ?: "") + 1;
210 size_t userlen = strlen (nuser ?: "") + 1;
211 size_t domainlen = strlen (ndomain ?: "") + 1;
212
213 if (nhost == NULL || nuser == NULL || ndomain == NULL
214 || nhost > nuser || nuser > ndomain)
215 {
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;
223
224 size_t bufused
225 = (last == NULL
226 ? buffilled
227 : last + strlen (last) + 1 - buffer);
228
229 /* We have to make temporary copies. */
230 size_t needed = hostlen + userlen + domainlen;
231
232 if (buflen - req->key_len - bufused < needed)
233 {
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;
241
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;
252 }
253
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);
260 }
261
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;
271 }
272 else
273 {
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)
279 {
280 if (strcmp (runp->name, data.val.group) == 0)
281 break;
282
283 runp = runp->next;
284 if (runp == data.needed_groups)
285 {
286 runp = NULL;
287 break;
288 }
289 }
290
291 if (runp == NULL)
292 {
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;
299 }
300
301 if (runp == NULL)
302 {
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
311 {
312 newg->next = data.needed_groups->next;
313 data.needed_groups->next = newg;
314 data.needed_groups = newg;
315 }
316 }
317 }
318 }
319 else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE)
320 {
321 buflen *= 2;
322 buffer = xrealloc (buffer, buflen);
323 }
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;
331 }
332
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));
337
338 break;
339 }
340
341 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
342 status, 0);
343 }
344 }
345
346 /* No results. Return a failure and write out a notfound record in the
347 cache. */
348 if (!found)
349 {
350 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
351 &key_copy);
352 goto writeout;
353 }
354
355 total = buffilled;
356
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);
363
364 dataset->resp.version = NSCD_VERSION;
365 dataset->resp.found = 1;
366 dataset->resp.nresults = nentries;
367 dataset->resp.result_len = buffilled - sizeof (*dataset);
368
369 assert (buflen - buffilled >= req->key_len);
370 key_copy = memcpy (buffer + buffilled, key, req->key_len);
371 buffilled += req->key_len;
372
373 /* Now we can determine whether on refill we have to create a new
374 record or not. */
375 if (he != NULL)
376 {
377 assert (fd == -1);
378
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)
383 {
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;
391
392 goto out;
393 }
394 }
395
396 {
397 struct dataset *newp
398 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
399 if (__glibc_likely (newp != NULL))
400 {
401 /* Adjust pointer into the memory block. */
402 key_copy = (char *) newp + (key_copy - buffer);
403
404 dataset = memcpy (newp, dataset, total + req->key_len);
405 cacheable = true;
406
407 if (he != NULL)
408 /* Mark the old record as obsolete. */
409 dh->usable = false;
410 }
411 }
412
413 if (he == NULL && fd != -1)
414 {
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);
420 }
421
422 if (cacheable)
423 {
424 /* If necessary, we also propagate the data to disk. */
425 if (db->persistent)
426 {
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);
432 }
433
434 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
435 true, db, uid, he == NULL);
436
437 pthread_rwlock_unlock (&db->lock);
438
439 /* Mark the old entry as obsolete. */
440 if (dh != NULL)
441 dh->usable = false;
442 }
443
444 out:
445 *resultp = dataset;
446
447 return timeout;
448}
449
450
451static time_t
452addinnetgrX (struct database_dyn *db, int fd, request_header *req,
453 char *key, uid_t uid, struct hashentry *he,
454 struct datahead *dh)
455{
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;
466
467 if (__glibc_unlikely (debug_level > 0))
468 {
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 ?: "");
475 }
476
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)
483 {
484 timeout = result->head.timeout;
485 tofree = NULL;
486 }
487 else
488 {
489 request_header req_get =
490 {
491 .type = GETNETGRENT,
492 .key_len = group_len
493 };
494 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
495 &result, &tofree);
496 }
497
498 struct indataset
499 {
500 struct datahead head;
501 innetgroup_response_header resp;
502 } *dataset
503 = (struct indataset *) mempool_alloc (db,
504 sizeof (*dataset) + req->key_len,
505 1);
506 struct indataset dataset_mem;
507 bool cacheable = true;
508 if (__glibc_unlikely (dataset == NULL))
509 {
510 cacheable = false;
511 dataset = &dataset_mem;
512 }
513
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;
521
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;
526
527 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
528
529 if (dataset->resp.found)
530 {
531 const char *triplets = (const char *) (&result->resp + 1);
532
533 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
534 {
535 bool success = true;
536
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;
543
544 if (success && user != NULL && *triplets != '\0')
545 success = strcmp (user, triplets) == 0;
546 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
547
548 if (success && (domain == NULL || *triplets == '\0'
549 || strcmp (domain, triplets) == 0))
550 {
551 dataset->resp.result = 1;
552 break;
553 }
554 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
555 }
556 }
557
558 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
559 {
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;
569 }
570
571 if (he == NULL)
572 {
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);
577
578 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
579 }
580
581 if (cacheable)
582 {
583 /* If necessary, we also propagate the data to disk. */
584 if (db->persistent)
585 {
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);
592 }
593
594 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
595 true, db, uid, he == NULL);
596
597 pthread_rwlock_unlock (&db->lock);
598
599 /* Mark the old entry as obsolete. */
600 if (dh != NULL)
601 dh->usable = false;
602 }
603
604 out:
605 free (tofree);
606 return timeout;
607}
608
609
610static time_t
611addgetnetgrentX_ignore (struct database_dyn *db, int fd, request_header *req,
612 const char *key, uid_t uid, struct hashentry *he,
613 struct datahead *dh)
614{
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;
621}
622
623void
624addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
625 void *key, uid_t uid)
626{
627 addgetnetgrentX_ignore (db, fd, req, key, uid, NULL, NULL);
628}
629
630
631time_t
632readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
633 struct datahead *dh)
634{
635 request_header req =
636 {
637 .type = GETNETGRENT,
638 .key_len = he->len
639 };
640 return addgetnetgrentX_ignore
641 (db, -1, &req, db->data + he->key, he->owner, he, dh);
642}
643
644
645void
646addinnetgr (struct database_dyn *db, int fd, request_header *req,
647 void *key, uid_t uid)
648{
649 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
650}
651
652
653time_t
654readdinnetgr (struct database_dyn *db, struct hashentry *he,
655 struct datahead *dh)
656{
657 request_header req =
658 {
659 .type = INNETGR,
660 .key_len = he->len
661 };
662
663 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);
664}
665