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