Fix error_tail overflow in allocation calculation.
[glibc.git] / nscd / netgroupcache.c
bloba607dda0a57897338d1b677fd4986276966aa324
1 /* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2013 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 <http://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 <unistd.h>
25 #include <sys/mman.h>
27 #include "../inet/netgroup.h"
28 #include "nscd.h"
29 #include "dbg_log.h"
31 #include <kernel-features.h>
34 /* This is the standard reply in case the service is disabled. */
35 static const netgroup_response_header disabled =
37 .version = NSCD_VERSION,
38 .found = -1,
39 .nresults = 0,
40 .result_len = 0
43 /* This is the struct describing how to write this record. */
44 const struct iovec netgroup_iov_disabled =
46 .iov_base = (void *) &disabled,
47 .iov_len = sizeof (disabled)
51 /* This is the standard reply in case we haven't found the dataset. */
52 static const netgroup_response_header notfound =
54 .version = NSCD_VERSION,
55 .found = 0,
56 .nresults = 0,
57 .result_len = 0
61 struct dataset
63 struct datahead head;
64 netgroup_response_header resp;
65 char strdata[0];
69 static time_t
70 addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
71 const char *key, uid_t uid, struct hashentry *he,
72 struct datahead *dh, struct dataset **resultp)
74 if (__builtin_expect (debug_level > 0, 0))
76 if (he == NULL)
77 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
78 else
79 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
82 static service_user *netgroup_database;
83 time_t timeout;
84 struct dataset *dataset;
85 bool cacheable = false;
86 ssize_t total;
88 char *key_copy = NULL;
89 struct __netgrent data;
90 size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
91 size_t buffilled = sizeof (*dataset);
92 char *buffer = NULL;
93 size_t nentries = 0;
94 bool use_malloc = false;
95 size_t group_len = strlen (key) + 1;
96 union
98 struct name_list elem;
99 char mem[sizeof (struct name_list) + group_len];
100 } first_needed;
102 if (netgroup_database == NULL
103 && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
105 /* No such service. */
106 total = sizeof (notfound);
107 timeout = time (NULL) + db->negtimeout;
109 if (fd != -1)
110 TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
112 dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
113 /* If we cannot permanently store the result, so be it. */
114 if (dataset != NULL)
116 dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
117 dataset->head.recsize = total;
118 dataset->head.notfound = true;
119 dataset->head.nreloads = 0;
120 dataset->head.usable = true;
122 /* Compute the timeout time. */
123 timeout = dataset->head.timeout = time (NULL) + db->negtimeout;
124 dataset->head.ttl = db->negtimeout;
126 /* This is the reply. */
127 memcpy (&dataset->resp, &notfound, total);
129 /* Copy the key data. */
130 memcpy (dataset->strdata, key, req->key_len);
132 cacheable = true;
135 goto writeout;
138 memset (&data, '\0', sizeof (data));
139 buffer = alloca (buflen);
140 first_needed.elem.next = &first_needed.elem;
141 memcpy (first_needed.elem.name, key, group_len);
142 data.needed_groups = &first_needed.elem;
144 while (data.needed_groups != NULL)
146 /* Add the next group to the list of those which are known. */
147 struct name_list *this_group = data.needed_groups->next;
148 if (this_group == data.needed_groups)
149 data.needed_groups = NULL;
150 else
151 data.needed_groups->next = this_group->next;
152 this_group->next = data.known_groups;
153 data.known_groups = this_group;
155 union
157 enum nss_status (*f) (const char *, struct __netgrent *);
158 void *ptr;
159 } setfct;
161 service_user *nip = netgroup_database;
162 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
163 while (!no_more)
165 enum nss_status status
166 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
168 if (status == NSS_STATUS_SUCCESS)
170 union
172 enum nss_status (*f) (struct __netgrent *, char *, size_t,
173 int *);
174 void *ptr;
175 } getfct;
176 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
177 if (getfct.f != NULL)
178 while (1)
180 int e;
181 status = getfct.f (&data, buffer + buffilled,
182 buflen - buffilled, &e);
183 if (status == NSS_STATUS_RETURN)
184 /* This was the last one for this group. Look
185 at next group if available. */
186 break;
187 if (status == NSS_STATUS_SUCCESS)
189 if (data.type == triple_val)
191 const char *nhost = data.val.triple.host;
192 const char *nuser = data.val.triple.user;
193 const char *ndomain = data.val.triple.domain;
195 if (nhost == NULL || nuser == NULL || ndomain == NULL
196 || nhost > nuser || nuser > ndomain)
198 const char *last = nhost;
199 if (last == NULL
200 || (nuser != NULL && nuser > last))
201 last = nuser;
202 if (last == NULL
203 || (ndomain != NULL && ndomain > last))
204 last = ndomain;
206 size_t bufused
207 = (last == NULL
208 ? buffilled
209 : last + strlen (last) + 1 - buffer);
211 /* We have to make temporary copies. */
212 size_t hostlen = strlen (nhost ?: "") + 1;
213 size_t userlen = strlen (nuser ?: "") + 1;
214 size_t domainlen = strlen (ndomain ?: "") + 1;
215 size_t needed = hostlen + userlen + domainlen;
217 if (buflen - req->key_len - bufused < needed)
219 size_t newsize = MAX (2 * buflen,
220 buflen + 2 * needed);
221 if (use_malloc || newsize > 1024 * 1024)
223 buflen = newsize;
224 char *newbuf = xrealloc (use_malloc
225 ? buffer
226 : NULL,
227 buflen);
229 buffer = newbuf;
230 use_malloc = true;
232 else
233 extend_alloca (buffer, buflen, newsize);
236 nhost = memcpy (buffer + bufused,
237 nhost ?: "", hostlen);
238 nuser = memcpy ((char *) nhost + hostlen,
239 nuser ?: "", userlen);
240 ndomain = memcpy ((char *) nuser + userlen,
241 ndomain ?: "", domainlen);
244 char *wp = buffer + buffilled;
245 wp = stpcpy (wp, nhost) + 1;
246 wp = stpcpy (wp, nuser) + 1;
247 wp = stpcpy (wp, ndomain) + 1;
248 buffilled = wp - buffer;
249 ++nentries;
251 else
253 /* Check that the group has not been
254 requested before. */
255 struct name_list *runp = data.needed_groups;
256 if (runp != NULL)
257 while (1)
259 if (strcmp (runp->name, data.val.group) == 0)
260 break;
262 runp = runp->next;
263 if (runp == data.needed_groups)
265 runp = NULL;
266 break;
270 if (runp == NULL)
272 runp = data.known_groups;
273 while (runp != NULL)
274 if (strcmp (runp->name, data.val.group) == 0)
275 break;
276 else
277 runp = runp->next;
280 if (runp == NULL)
282 /* A new group is requested. */
283 size_t namelen = strlen (data.val.group) + 1;
284 struct name_list *newg = alloca (sizeof (*newg)
285 + namelen);
286 memcpy (newg->name, data.val.group, namelen);
287 if (data.needed_groups == NULL)
288 data.needed_groups = newg->next = newg;
289 else
291 newg->next = data.needed_groups->next;
292 data.needed_groups->next = newg;
293 data.needed_groups = newg;
298 else if (status == NSS_STATUS_UNAVAIL && e == ERANGE)
300 size_t newsize = 2 * buflen;
301 if (use_malloc || newsize > 1024 * 1024)
303 buflen = newsize;
304 char *newbuf = xrealloc (use_malloc
305 ? buffer : NULL, buflen);
307 buffer = newbuf;
308 use_malloc = true;
310 else
311 extend_alloca (buffer, buflen, newsize);
315 enum nss_status (*endfct) (struct __netgrent *);
316 endfct = __nss_lookup_function (nip, "endnetgrent");
317 if (endfct != NULL)
318 (void) DL_CALL_FCT (*endfct, (&data));
320 break;
323 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
324 status, 0);
328 total = buffilled;
330 /* Fill in the dataset. */
331 dataset = (struct dataset *) buffer;
332 dataset->head.allocsize = total + req->key_len;
333 dataset->head.recsize = total - offsetof (struct dataset, resp);
334 dataset->head.notfound = false;
335 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
336 dataset->head.usable = true;
337 dataset->head.ttl = db->postimeout;
338 timeout = dataset->head.timeout = time (NULL) + dataset->head.ttl;
340 dataset->resp.version = NSCD_VERSION;
341 dataset->resp.found = 1;
342 dataset->resp.nresults = nentries;
343 dataset->resp.result_len = buffilled - sizeof (*dataset);
345 assert (buflen - buffilled >= req->key_len);
346 key_copy = memcpy (buffer + buffilled, key, req->key_len);
347 buffilled += req->key_len;
349 /* Now we can determine whether on refill we have to create a new
350 record or not. */
351 if (he != NULL)
353 assert (fd == -1);
355 if (dataset->head.allocsize == dh->allocsize
356 && dataset->head.recsize == dh->recsize
357 && memcmp (&dataset->resp, dh->data,
358 dh->allocsize - offsetof (struct dataset, resp)) == 0)
360 /* The data has not changed. We will just bump the timeout
361 value. Note that the new record has been allocated on
362 the stack and need not be freed. */
363 dh->timeout = dataset->head.timeout;
364 dh->ttl = dataset->head.ttl;
365 ++dh->nreloads;
366 dataset = (struct dataset *) dh;
368 goto out;
373 struct dataset *newp
374 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
375 if (__builtin_expect (newp != NULL, 1))
377 /* Adjust pointer into the memory block. */
378 key_copy = (char *) newp + (key_copy - buffer);
380 dataset = memcpy (newp, dataset, total + req->key_len);
381 cacheable = true;
383 if (he != NULL)
384 /* Mark the old record as obsolete. */
385 dh->usable = false;
389 if (he == NULL && fd != -1)
391 /* We write the dataset before inserting it to the database
392 since while inserting this thread might block and so would
393 unnecessarily let the receiver wait. */
394 writeout:
395 #ifdef HAVE_SENDFILE
396 if (__builtin_expect (db->mmap_used, 1) && cacheable)
398 assert (db->wr_fd != -1);
399 assert ((char *) &dataset->resp > (char *) db->data);
400 assert ((char *) dataset - (char *) db->head + total
401 <= (sizeof (struct database_pers_head)
402 + db->head->module * sizeof (ref_t)
403 + db->head->data_size));
404 # ifndef __ASSUME_SENDFILE
405 ssize_t written =
406 # endif
407 sendfileall (fd, db->wr_fd, (char *) &dataset->resp
408 - (char *) db->head, dataset->head.recsize);
409 # ifndef __ASSUME_SENDFILE
410 if (written == -1 && errno == ENOSYS)
411 goto use_write;
412 # endif
414 else
415 #endif
417 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
418 use_write:
419 #endif
420 writeall (fd, &dataset->resp, dataset->head.recsize);
424 if (cacheable)
426 /* If necessary, we also propagate the data to disk. */
427 if (db->persistent)
429 // XXX async OK?
430 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
431 msync ((void *) pval,
432 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
433 MS_ASYNC);
436 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
437 true, db, uid, he == NULL);
439 pthread_rwlock_unlock (&db->lock);
441 /* Mark the old entry as obsolete. */
442 if (dh != NULL)
443 dh->usable = false;
446 out:
447 if (use_malloc)
448 free (buffer);
450 *resultp = dataset;
452 return timeout;
456 static time_t
457 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
458 char *key, uid_t uid, struct hashentry *he,
459 struct datahead *dh)
461 const char *group = key;
462 key = (char *) rawmemchr (key, '\0') + 1;
463 size_t group_len = key - group - 1;
464 const char *host = *key++ ? key : NULL;
465 if (host != NULL)
466 key = (char *) rawmemchr (key, '\0') + 1;
467 const char *user = *key++ ? key : NULL;
468 if (user != NULL)
469 key = (char *) rawmemchr (key, '\0') + 1;
470 const char *domain = *key++ ? key : NULL;
472 if (__builtin_expect (debug_level > 0, 0))
474 if (he == NULL)
475 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
476 group, host ?: "", user ?: "", domain ?: "");
477 else
478 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
479 group, host ?: "", user ?: "", domain ?: "");
482 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
483 group, group_len,
484 db, uid);
485 time_t timeout;
486 if (result != NULL)
487 timeout = result->head.timeout;
488 else
490 request_header req_get =
492 .type = GETNETGRENT,
493 .key_len = group_len
495 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
496 &result);
499 struct indataset
501 struct datahead head;
502 innetgroup_response_header resp;
503 } *dataset
504 = (struct indataset *) mempool_alloc (db,
505 sizeof (*dataset) + req->key_len,
507 struct indataset dataset_mem;
508 bool cacheable = true;
509 if (__builtin_expect (dataset == NULL, 0))
511 cacheable = false;
512 dataset = &dataset_mem;
515 dataset->head.allocsize = sizeof (*dataset) + req->key_len;
516 dataset->head.recsize = sizeof (innetgroup_response_header);
517 dataset->head.notfound = result->head.notfound;
518 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
519 dataset->head.usable = true;
520 dataset->head.ttl = result->head.ttl;
521 dataset->head.timeout = timeout;
523 dataset->resp.version = NSCD_VERSION;
524 dataset->resp.found = result->resp.found;
525 /* Until we find a matching entry the result is 0. */
526 dataset->resp.result = 0;
528 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
530 if (dataset->resp.found)
532 const char *triplets = (const char *) (&result->resp + 1);
534 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
536 bool success = true;
538 if (host != NULL)
539 success = strcmp (host, triplets) == 0;
540 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
542 if (success && user != NULL)
543 success = strcmp (user, triplets) == 0;
544 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
546 if (success && (domain == NULL || strcmp (domain, triplets) == 0))
548 dataset->resp.result = 1;
549 break;
551 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
555 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
557 /* The data has not changed. We will just bump the timeout
558 value. Note that the new record has been allocated on
559 the stack and need not be freed. */
560 dh->timeout = timeout;
561 dh->ttl = dataset->head.ttl;
562 ++dh->nreloads;
563 return timeout;
566 if (he == NULL)
568 /* We write the dataset before inserting it to the database
569 since while inserting this thread might block and so would
570 unnecessarily let the receiver wait. */
571 assert (fd != -1);
573 #ifdef HAVE_SENDFILE
574 if (__builtin_expect (db->mmap_used, 1) && cacheable)
576 assert (db->wr_fd != -1);
577 assert ((char *) &dataset->resp > (char *) db->data);
578 assert ((char *) dataset - (char *) db->head + sizeof (*dataset)
579 <= (sizeof (struct database_pers_head)
580 + db->head->module * sizeof (ref_t)
581 + db->head->data_size));
582 # ifndef __ASSUME_SENDFILE
583 ssize_t written =
584 # endif
585 sendfileall (fd, db->wr_fd,
586 (char *) &dataset->resp - (char *) db->head,
587 sizeof (innetgroup_response_header));
588 # ifndef __ASSUME_SENDFILE
589 if (written == -1 && errno == ENOSYS)
590 goto use_write;
591 # endif
593 else
594 #endif
596 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
597 use_write:
598 #endif
599 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
603 if (cacheable)
605 /* If necessary, we also propagate the data to disk. */
606 if (db->persistent)
608 // XXX async OK?
609 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
610 msync ((void *) pval,
611 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
612 + req->key_len,
613 MS_ASYNC);
616 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
617 true, db, uid, he == NULL);
619 pthread_rwlock_unlock (&db->lock);
621 /* Mark the old entry as obsolete. */
622 if (dh != NULL)
623 dh->usable = false;
626 return timeout;
630 void
631 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
632 void *key, uid_t uid)
634 struct dataset *ignore;
636 addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
640 time_t
641 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
642 struct datahead *dh)
644 request_header req =
646 .type = GETNETGRENT,
647 .key_len = he->len
649 struct dataset *ignore;
651 return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
652 &ignore);
656 void
657 addinnetgr (struct database_dyn *db, int fd, request_header *req,
658 void *key, uid_t uid)
660 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
664 time_t
665 readdinnetgr (struct database_dyn *db, struct hashentry *he,
666 struct datahead *dh)
668 request_header req =
670 .type = INNETGR,
671 .key_len = he->len
674 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);