1/* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2016 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{
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 service_user *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 (key) + 1;
140 struct name_list *first_needed
141 = alloca (sizeof (struct name_list) + group_len);
142
143 if (netgroup_database == NULL
144 && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
145 {
146 /* No such service. */
147 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
148 &key_copy);
149 goto writeout;
150 }
151
152 memset (&data, '\0', sizeof (data));
153 buffer = xmalloc (buflen);
154 first_needed->next = first_needed;
155 memcpy (first_needed->name, key, group_len);
156 data.needed_groups = first_needed;
157
158 while (data.needed_groups != NULL)
159 {
160 /* Add the next group to the list of those which are known. */
161 struct name_list *this_group = data.needed_groups->next;
162 if (this_group == data.needed_groups)
163 data.needed_groups = NULL;
164 else
165 data.needed_groups->next = this_group->next;
166 this_group->next = data.known_groups;
167 data.known_groups = this_group;
168
169 union
170 {
171 enum nss_status (*f) (const char *, struct __netgrent *);
172 void *ptr;
173 } setfct;
174
175 service_user *nip = netgroup_database;
176 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
177 while (!no_more)
178 {
179 enum nss_status status
180 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
181
182 if (status == NSS_STATUS_SUCCESS)
183 {
184 found = true;
185 union
186 {
187 enum nss_status (*f) (struct __netgrent *, char *, size_t,
188 int *);
189 void *ptr;
190 } getfct;
191 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
192 if (getfct.f != NULL)
193 while (1)
194 {
195 int e;
196 status = getfct.f (&data, buffer + buffilled,
197 buflen - buffilled - req->key_len, &e);
198 if (status == NSS_STATUS_SUCCESS)
199 {
200 if (data.type == triple_val)
201 {
202 const char *nhost = data.val.triple.host;
203 const char *nuser = data.val.triple.user;
204 const char *ndomain = data.val.triple.domain;
205
206 size_t hostlen = strlen (nhost ?: "") + 1;
207 size_t userlen = strlen (nuser ?: "") + 1;
208 size_t domainlen = strlen (ndomain ?: "") + 1;
209
210 if (nhost == NULL || nuser == NULL || ndomain == NULL
211 || nhost > nuser || nuser > ndomain)
212 {
213 const char *last = nhost;
214 if (last == NULL
215 || (nuser != NULL && nuser > last))
216 last = nuser;
217 if (last == NULL
218 || (ndomain != NULL && ndomain > last))
219 last = ndomain;
220
221 size_t bufused
222 = (last == NULL
223 ? buffilled
224 : last + strlen (last) + 1 - buffer);
225
226 /* We have to make temporary copies. */
227 size_t needed = hostlen + userlen + domainlen;
228
229 if (buflen - req->key_len - bufused < needed)
230 {
231 buflen += MAX (buflen, 2 * needed);
232 /* Save offset in the old buffer. We don't
233 bother with the NULL check here since
234 we'll do that later anyway. */
235 size_t nhostdiff = nhost - buffer;
236 size_t nuserdiff = nuser - buffer;
237 size_t ndomaindiff = ndomain - buffer;
238
239 char *newbuf = xrealloc (buffer, buflen);
240 /* Fix up the triplet pointers into the new
241 buffer. */
242 nhost = (nhost ? newbuf + nhostdiff
243 : NULL);
244 nuser = (nuser ? newbuf + nuserdiff
245 : NULL);
246 ndomain = (ndomain ? newbuf + ndomaindiff
247 : NULL);
248 buffer = newbuf;
249 }
250
251 nhost = memcpy (buffer + bufused,
252 nhost ?: "", hostlen);
253 nuser = memcpy ((char *) nhost + hostlen,
254 nuser ?: "", userlen);
255 ndomain = memcpy ((char *) nuser + userlen,
256 ndomain ?: "", domainlen);
257 }
258
259 char *wp = buffer + buffilled;
260 wp = memmove (wp, nhost ?: "", hostlen);
261 wp += hostlen;
262 wp = memmove (wp, nuser ?: "", userlen);
263 wp += userlen;
264 wp = memmove (wp, ndomain ?: "", domainlen);
265 wp += domainlen;
266 buffilled = wp - buffer;
267 ++nentries;
268 }
269 else
270 {
271 /* Check that the group has not been
272 requested before. */
273 struct name_list *runp = data.needed_groups;
274 if (runp != NULL)
275 while (1)
276 {
277 if (strcmp (runp->name, data.val.group) == 0)
278 break;
279
280 runp = runp->next;
281 if (runp == data.needed_groups)
282 {
283 runp = NULL;
284 break;
285 }
286 }
287
288 if (runp == NULL)
289 {
290 runp = data.known_groups;
291 while (runp != NULL)
292 if (strcmp (runp->name, data.val.group) == 0)
293 break;
294 else
295 runp = runp->next;
296 }
297
298 if (runp == NULL)
299 {
300 /* A new group is requested. */
301 size_t namelen = strlen (data.val.group) + 1;
302 struct name_list *newg = alloca (sizeof (*newg)
303 + namelen);
304 memcpy (newg->name, data.val.group, namelen);
305 if (data.needed_groups == NULL)
306 data.needed_groups = newg->next = newg;
307 else
308 {
309 newg->next = data.needed_groups->next;
310 data.needed_groups->next = newg;
311 data.needed_groups = newg;
312 }
313 }
314 }
315 }
316 else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE)
317 {
318 buflen *= 2;
319 buffer = xrealloc (buffer, buflen);
320 }
321 else if (status == NSS_STATUS_RETURN
322 || status == NSS_STATUS_NOTFOUND
323 || status == NSS_STATUS_UNAVAIL)
324 /* This was either the last one for this group or the
325 group was empty or the NSS module had an internal
326 failure. Look at next group if available. */
327 break;
328 }
329
330 enum nss_status (*endfct) (struct __netgrent *);
331 endfct = __nss_lookup_function (nip, "endnetgrent");
332 if (endfct != NULL)
333 (void) DL_CALL_FCT (*endfct, (&data));
334
335 break;
336 }
337
338 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
339 status, 0);
340 }
341 }
342
343 /* No results. Return a failure and write out a notfound record in the
344 cache. */
345 if (!found)
346 {
347 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
348 &key_copy);
349 goto writeout;
350 }
351
352 total = buffilled;
353
354 /* Fill in the dataset. */
355 dataset = (struct dataset *) buffer;
356 timeout = datahead_init_pos (&dataset->head, total + req->key_len,
357 total - offsetof (struct dataset, resp),
358 he == NULL ? 0 : dh->nreloads + 1,
359 db->postimeout);
360
361 dataset->resp.version = NSCD_VERSION;
362 dataset->resp.found = 1;
363 dataset->resp.nresults = nentries;
364 dataset->resp.result_len = buffilled - sizeof (*dataset);
365
366 assert (buflen - buffilled >= req->key_len);
367 key_copy = memcpy (buffer + buffilled, key, req->key_len);
368 buffilled += req->key_len;
369
370 /* Now we can determine whether on refill we have to create a new
371 record or not. */
372 if (he != NULL)
373 {
374 assert (fd == -1);
375
376 if (dataset->head.allocsize == dh->allocsize
377 && dataset->head.recsize == dh->recsize
378 && memcmp (&dataset->resp, dh->data,
379 dh->allocsize - offsetof (struct dataset, resp)) == 0)
380 {
381 /* The data has not changed. We will just bump the timeout
382 value. Note that the new record has been allocated on
383 the stack and need not be freed. */
384 dh->timeout = dataset->head.timeout;
385 dh->ttl = dataset->head.ttl;
386 ++dh->nreloads;
387 dataset = (struct dataset *) dh;
388
389 goto out;
390 }
391 }
392
393 {
394 struct dataset *newp
395 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
396 if (__glibc_likely (newp != NULL))
397 {
398 /* Adjust pointer into the memory block. */
399 key_copy = (char *) newp + (key_copy - buffer);
400
401 dataset = memcpy (newp, dataset, total + req->key_len);
402 cacheable = true;
403
404 if (he != NULL)
405 /* Mark the old record as obsolete. */
406 dh->usable = false;
407 }
408 }
409
410 if (he == NULL && fd != -1)
411 {
412 /* We write the dataset before inserting it to the database
413 since while inserting this thread might block and so would
414 unnecessarily let the receiver wait. */
415 writeout:
416#ifdef HAVE_SENDFILE
417 if (__builtin_expect (db->mmap_used, 1) && cacheable)
418 {
419 assert (db->wr_fd != -1);
420 assert ((char *) &dataset->resp > (char *) db->data);
421 assert ((char *) dataset - (char *) db->head + total
422 <= (sizeof (struct database_pers_head)
423 + db->head->module * sizeof (ref_t)
424 + db->head->data_size));
425# ifndef __ASSUME_SENDFILE
426 ssize_t written =
427# endif
428 sendfileall (fd, db->wr_fd, (char *) &dataset->resp
429 - (char *) db->head, dataset->head.recsize);
430# ifndef __ASSUME_SENDFILE
431 if (written == -1 && errno == ENOSYS)
432 goto use_write;
433# endif
434 }
435 else
436#endif
437 {
438#if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
439 use_write:
440#endif
441 writeall (fd, &dataset->resp, dataset->head.recsize);
442 }
443 }
444
445 if (cacheable)
446 {
447 /* If necessary, we also propagate the data to disk. */
448 if (db->persistent)
449 {
450 // XXX async OK?
451 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
452 msync ((void *) pval,
453 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
454 MS_ASYNC);
455 }
456
457 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
458 true, db, uid, he == NULL);
459
460 pthread_rwlock_unlock (&db->lock);
461
462 /* Mark the old entry as obsolete. */
463 if (dh != NULL)
464 dh->usable = false;
465 }
466
467 out:
468 free (buffer);
469
470 *resultp = dataset;
471
472 return timeout;
473}
474
475
476static time_t
477addinnetgrX (struct database_dyn *db, int fd, request_header *req,
478 char *key, uid_t uid, struct hashentry *he,
479 struct datahead *dh)
480{
481 const char *group = key;
482 key = (char *) rawmemchr (key, '\0') + 1;
483 size_t group_len = key - group - 1;
484 const char *host = *key++ ? key : NULL;
485 if (host != NULL)
486 key = (char *) rawmemchr (key, '\0') + 1;
487 const char *user = *key++ ? key : NULL;
488 if (user != NULL)
489 key = (char *) rawmemchr (key, '\0') + 1;
490 const char *domain = *key++ ? key : NULL;
491
492 if (__glibc_unlikely (debug_level > 0))
493 {
494 if (he == NULL)
495 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
496 group, host ?: "", user ?: "", domain ?: "");
497 else
498 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
499 group, host ?: "", user ?: "", domain ?: "");
500 }
501
502 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
503 group, group_len,
504 db, uid);
505 time_t timeout;
506 if (result != NULL)
507 timeout = result->head.timeout;
508 else
509 {
510 request_header req_get =
511 {
512 .type = GETNETGRENT,
513 .key_len = group_len
514 };
515 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
516 &result);
517 }
518
519 struct indataset
520 {
521 struct datahead head;
522 innetgroup_response_header resp;
523 } *dataset
524 = (struct indataset *) mempool_alloc (db,
525 sizeof (*dataset) + req->key_len,
526 1);
527 struct indataset dataset_mem;
528 bool cacheable = true;
529 if (__glibc_unlikely (dataset == NULL))
530 {
531 cacheable = false;
532 dataset = &dataset_mem;
533 }
534
535 datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len,
536 sizeof (innetgroup_response_header),
537 he == NULL ? 0 : dh->nreloads + 1, result->head.ttl);
538 /* Set the notfound status and timeout based on the result from
539 getnetgrent. */
540 dataset->head.notfound = result->head.notfound;
541 dataset->head.timeout = timeout;
542
543 dataset->resp.version = NSCD_VERSION;
544 dataset->resp.found = result->resp.found;
545 /* Until we find a matching entry the result is 0. */
546 dataset->resp.result = 0;
547
548 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
549
550 if (dataset->resp.found)
551 {
552 const char *triplets = (const char *) (&result->resp + 1);
553
554 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
555 {
556 bool success = true;
557
558 /* For the host, user and domain in each triplet, we assume success
559 if the value is blank because that is how the wildcard entry to
560 match anything is stored in the netgroup cache. */
561 if (host != NULL && *triplets != '\0')
562 success = strcmp (host, triplets) == 0;
563 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
564
565 if (success && user != NULL && *triplets != '\0')
566 success = strcmp (user, triplets) == 0;
567 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
568
569 if (success && (domain == NULL || *triplets == '\0'
570 || strcmp (domain, triplets) == 0))
571 {
572 dataset->resp.result = 1;
573 break;
574 }
575 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
576 }
577 }
578
579 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
580 {
581 /* The data has not changed. We will just bump the timeout
582 value. Note that the new record has been allocated on
583 the stack and need not be freed. */
584 dh->timeout = timeout;
585 dh->ttl = dataset->head.ttl;
586 ++dh->nreloads;
587 return timeout;
588 }
589
590 if (he == NULL)
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#ifdef HAVE_SENDFILE
598 if (__builtin_expect (db->mmap_used, 1) && cacheable)
599 {
600 assert (db->wr_fd != -1);
601 assert ((char *) &dataset->resp > (char *) db->data);
602 assert ((char *) dataset - (char *) db->head + sizeof (*dataset)
603 <= (sizeof (struct database_pers_head)
604 + db->head->module * sizeof (ref_t)
605 + db->head->data_size));
606# ifndef __ASSUME_SENDFILE
607 ssize_t written =
608# endif
609 sendfileall (fd, db->wr_fd,
610 (char *) &dataset->resp - (char *) db->head,
611 sizeof (innetgroup_response_header));
612# ifndef __ASSUME_SENDFILE
613 if (written == -1 && errno == ENOSYS)
614 goto use_write;
615# endif
616 }
617 else
618#endif
619 {
620#if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
621 use_write:
622#endif
623 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
624 }
625 }
626
627 if (cacheable)
628 {
629 /* If necessary, we also propagate the data to disk. */
630 if (db->persistent)
631 {
632 // XXX async OK?
633 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
634 msync ((void *) pval,
635 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
636 + req->key_len,
637 MS_ASYNC);
638 }
639
640 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
641 true, db, uid, he == NULL);
642
643 pthread_rwlock_unlock (&db->lock);
644
645 /* Mark the old entry as obsolete. */
646 if (dh != NULL)
647 dh->usable = false;
648 }
649
650 return timeout;
651}
652
653
654void
655addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
656 void *key, uid_t uid)
657{
658 struct dataset *ignore;
659
660 addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
661}
662
663
664time_t
665readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
666 struct datahead *dh)
667{
668 request_header req =
669 {
670 .type = GETNETGRENT,
671 .key_len = he->len
672 };
673 struct dataset *ignore;
674
675 return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
676 &ignore);
677}
678
679
680void
681addinnetgr (struct database_dyn *db, int fd, request_header *req,
682 void *key, uid_t uid)
683{
684 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
685}
686
687
688time_t
689readdinnetgr (struct database_dyn *db, struct hashentry *he,
690 struct datahead *dh)
691{
692 request_header req =
693 {
694 .type = INNETGR,
695 .key_len = he->len
696 };
697
698 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);
699}
700