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