Provide correct buffer length to netgroup queries in nscd (BZ #16695)
[glibc.git] / nscd / netgroupcache.c
blob5ba1e1f27752961895a1cad5ecbd493b39ed8fc3
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.
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];
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)
78 struct dataset *dataset;
79 ssize_t total;
80 time_t timeout;
81 bool cacheable = false;
83 total = sizeof (notfound);
84 timeout = time (NULL) + db->negtimeout;
86 if (fd != -1)
87 TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
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)
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;
99 /* Compute the timeout time. */
100 timeout = dataset->head.timeout = time (NULL) + db->negtimeout;
101 dataset->head.ttl = db->negtimeout;
103 /* This is the reply. */
104 memcpy (&dataset->resp, &notfound, total);
106 /* Copy the key data. */
107 memcpy (dataset->strdata, key, req->key_len);
108 *key_copy = dataset->strdata;
110 cacheable = true;
112 *timeoutp = timeout;
113 *totalp = total;
114 *datasetp = dataset;
115 return cacheable;
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)
123 if (__glibc_unlikely (debug_level > 0))
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);
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;
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 size_t group_len = strlen (key) + 1;
145 union
147 struct name_list elem;
148 char mem[sizeof (struct name_list) + group_len];
149 } first_needed;
151 if (netgroup_database == NULL
152 && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
154 /* No such service. */
155 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
156 &key_copy);
157 goto writeout;
160 memset (&data, '\0', sizeof (data));
161 buffer = xmalloc (buflen);
162 first_needed.elem.next = &first_needed.elem;
163 memcpy (first_needed.elem.name, key, group_len);
164 data.needed_groups = &first_needed.elem;
166 while (data.needed_groups != NULL)
168 /* Add the next group to the list of those which are known. */
169 struct name_list *this_group = data.needed_groups->next;
170 if (this_group == data.needed_groups)
171 data.needed_groups = NULL;
172 else
173 data.needed_groups->next = this_group->next;
174 this_group->next = data.known_groups;
175 data.known_groups = this_group;
177 union
179 enum nss_status (*f) (const char *, struct __netgrent *);
180 void *ptr;
181 } setfct;
183 service_user *nip = netgroup_database;
184 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
185 while (!no_more)
187 enum nss_status status
188 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
190 if (status == NSS_STATUS_SUCCESS)
192 found = true;
193 union
195 enum nss_status (*f) (struct __netgrent *, char *, size_t,
196 int *);
197 void *ptr;
198 } getfct;
199 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
200 if (getfct.f != NULL)
201 while (1)
203 int e;
204 status = getfct.f (&data, buffer + buffilled,
205 buflen - buffilled - req->key_len, &e);
206 if (status == NSS_STATUS_RETURN
207 || status == NSS_STATUS_NOTFOUND)
208 /* This was either the last one for this group or the
209 group was empty. Look at next group if available. */
210 break;
211 if (status == NSS_STATUS_SUCCESS)
213 if (data.type == triple_val)
215 const char *nhost = data.val.triple.host;
216 const char *nuser = data.val.triple.user;
217 const char *ndomain = data.val.triple.domain;
219 if (nhost == NULL || nuser == NULL || ndomain == NULL
220 || nhost > nuser || nuser > ndomain)
222 const char *last = nhost;
223 if (last == NULL
224 || (nuser != NULL && nuser > last))
225 last = nuser;
226 if (last == NULL
227 || (ndomain != NULL && ndomain > last))
228 last = ndomain;
230 size_t bufused
231 = (last == NULL
232 ? buffilled
233 : last + strlen (last) + 1 - buffer);
235 /* We have to make temporary copies. */
236 size_t hostlen = strlen (nhost ?: "") + 1;
237 size_t userlen = strlen (nuser ?: "") + 1;
238 size_t domainlen = strlen (ndomain ?: "") + 1;
239 size_t needed = hostlen + userlen + domainlen;
241 if (buflen - req->key_len - bufused < needed)
243 buflen += MAX (buflen, 2 * needed);
244 /* Save offset in the old buffer. We don't
245 bother with the NULL check here since
246 we'll do that later anyway. */
247 size_t nhostdiff = nhost - buffer;
248 size_t nuserdiff = nuser - buffer;
249 size_t ndomaindiff = ndomain - buffer;
251 char *newbuf = xrealloc (buffer, buflen);
252 /* Fix up the triplet pointers into the new
253 buffer. */
254 nhost = (nhost ? newbuf + nhostdiff
255 : NULL);
256 nuser = (nuser ? newbuf + nuserdiff
257 : NULL);
258 ndomain = (ndomain ? newbuf + ndomaindiff
259 : NULL);
260 buffer = newbuf;
263 nhost = memcpy (buffer + bufused,
264 nhost ?: "", hostlen);
265 nuser = memcpy ((char *) nhost + hostlen,
266 nuser ?: "", userlen);
267 ndomain = memcpy ((char *) nuser + userlen,
268 ndomain ?: "", domainlen);
271 char *wp = buffer + buffilled;
272 wp = stpcpy (wp, nhost) + 1;
273 wp = stpcpy (wp, nuser) + 1;
274 wp = stpcpy (wp, ndomain) + 1;
275 buffilled = wp - buffer;
276 ++nentries;
278 else
280 /* Check that the group has not been
281 requested before. */
282 struct name_list *runp = data.needed_groups;
283 if (runp != NULL)
284 while (1)
286 if (strcmp (runp->name, data.val.group) == 0)
287 break;
289 runp = runp->next;
290 if (runp == data.needed_groups)
292 runp = NULL;
293 break;
297 if (runp == NULL)
299 runp = data.known_groups;
300 while (runp != NULL)
301 if (strcmp (runp->name, data.val.group) == 0)
302 break;
303 else
304 runp = runp->next;
307 if (runp == NULL)
309 /* A new group is requested. */
310 size_t namelen = strlen (data.val.group) + 1;
311 struct name_list *newg = alloca (sizeof (*newg)
312 + namelen);
313 memcpy (newg->name, data.val.group, namelen);
314 if (data.needed_groups == NULL)
315 data.needed_groups = newg->next = newg;
316 else
318 newg->next = data.needed_groups->next;
319 data.needed_groups->next = newg;
320 data.needed_groups = newg;
325 else if (status == NSS_STATUS_UNAVAIL && e == ERANGE)
327 buflen *= 2;
328 buffer = xrealloc (buffer, buflen);
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));
337 break;
340 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
341 status, 0);
345 /* No results. Return a failure and write out a notfound record in the
346 cache. */
347 if (!found)
349 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
350 &key_copy);
351 goto writeout;
354 total = buffilled;
356 /* Fill in the dataset. */
357 dataset = (struct dataset *) buffer;
358 dataset->head.allocsize = total + req->key_len;
359 dataset->head.recsize = total - offsetof (struct dataset, resp);
360 dataset->head.notfound = false;
361 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
362 dataset->head.usable = true;
363 dataset->head.ttl = db->postimeout;
364 timeout = dataset->head.timeout = time (NULL) + dataset->head.ttl;
366 dataset->resp.version = NSCD_VERSION;
367 dataset->resp.found = 1;
368 dataset->resp.nresults = nentries;
369 dataset->resp.result_len = buffilled - sizeof (*dataset);
371 assert (buflen - buffilled >= req->key_len);
372 key_copy = memcpy (buffer + buffilled, key, req->key_len);
373 buffilled += req->key_len;
375 /* Now we can determine whether on refill we have to create a new
376 record or not. */
377 if (he != NULL)
379 assert (fd == -1);
381 if (dataset->head.allocsize == dh->allocsize
382 && dataset->head.recsize == dh->recsize
383 && memcmp (&dataset->resp, dh->data,
384 dh->allocsize - offsetof (struct dataset, resp)) == 0)
386 /* The data has not changed. We will just bump the timeout
387 value. Note that the new record has been allocated on
388 the stack and need not be freed. */
389 dh->timeout = dataset->head.timeout;
390 dh->ttl = dataset->head.ttl;
391 ++dh->nreloads;
392 dataset = (struct dataset *) dh;
394 goto out;
399 struct dataset *newp
400 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
401 if (__glibc_likely (newp != NULL))
403 /* Adjust pointer into the memory block. */
404 key_copy = (char *) newp + (key_copy - buffer);
406 dataset = memcpy (newp, dataset, total + req->key_len);
407 cacheable = true;
409 if (he != NULL)
410 /* Mark the old record as obsolete. */
411 dh->usable = false;
415 if (he == NULL && fd != -1)
417 /* We write the dataset before inserting it to the database
418 since while inserting this thread might block and so would
419 unnecessarily let the receiver wait. */
420 writeout:
421 #ifdef HAVE_SENDFILE
422 if (__builtin_expect (db->mmap_used, 1) && cacheable)
424 assert (db->wr_fd != -1);
425 assert ((char *) &dataset->resp > (char *) db->data);
426 assert ((char *) dataset - (char *) db->head + total
427 <= (sizeof (struct database_pers_head)
428 + db->head->module * sizeof (ref_t)
429 + db->head->data_size));
430 # ifndef __ASSUME_SENDFILE
431 ssize_t written =
432 # endif
433 sendfileall (fd, db->wr_fd, (char *) &dataset->resp
434 - (char *) db->head, dataset->head.recsize);
435 # ifndef __ASSUME_SENDFILE
436 if (written == -1 && errno == ENOSYS)
437 goto use_write;
438 # endif
440 else
441 #endif
443 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
444 use_write:
445 #endif
446 writeall (fd, &dataset->resp, dataset->head.recsize);
450 if (cacheable)
452 /* If necessary, we also propagate the data to disk. */
453 if (db->persistent)
455 // XXX async OK?
456 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
457 msync ((void *) pval,
458 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
459 MS_ASYNC);
462 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
463 true, db, uid, he == NULL);
465 pthread_rwlock_unlock (&db->lock);
467 /* Mark the old entry as obsolete. */
468 if (dh != NULL)
469 dh->usable = false;
472 out:
473 free (buffer);
475 *resultp = dataset;
477 return timeout;
481 static time_t
482 addinnetgrX (struct database_dyn *db, int fd, request_header *req,
483 char *key, uid_t uid, struct hashentry *he,
484 struct datahead *dh)
486 const char *group = key;
487 key = (char *) rawmemchr (key, '\0') + 1;
488 size_t group_len = key - group - 1;
489 const char *host = *key++ ? key : NULL;
490 if (host != NULL)
491 key = (char *) rawmemchr (key, '\0') + 1;
492 const char *user = *key++ ? key : NULL;
493 if (user != NULL)
494 key = (char *) rawmemchr (key, '\0') + 1;
495 const char *domain = *key++ ? key : NULL;
497 if (__glibc_unlikely (debug_level > 0))
499 if (he == NULL)
500 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
501 group, host ?: "", user ?: "", domain ?: "");
502 else
503 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
504 group, host ?: "", user ?: "", domain ?: "");
507 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
508 group, group_len,
509 db, uid);
510 time_t timeout;
511 if (result != NULL)
512 timeout = result->head.timeout;
513 else
515 request_header req_get =
517 .type = GETNETGRENT,
518 .key_len = group_len
520 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
521 &result);
524 struct indataset
526 struct datahead head;
527 innetgroup_response_header resp;
528 } *dataset
529 = (struct indataset *) mempool_alloc (db,
530 sizeof (*dataset) + req->key_len,
532 struct indataset dataset_mem;
533 bool cacheable = true;
534 if (__glibc_unlikely (dataset == NULL))
536 cacheable = false;
537 dataset = &dataset_mem;
540 dataset->head.allocsize = sizeof (*dataset) + req->key_len;
541 dataset->head.recsize = sizeof (innetgroup_response_header);
542 dataset->head.notfound = result->head.notfound;
543 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
544 dataset->head.usable = true;
545 dataset->head.ttl = result->head.ttl;
546 dataset->head.timeout = timeout;
548 dataset->resp.version = NSCD_VERSION;
549 dataset->resp.found = result->resp.found;
550 /* Until we find a matching entry the result is 0. */
551 dataset->resp.result = 0;
553 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
555 if (dataset->resp.found)
557 const char *triplets = (const char *) (&result->resp + 1);
559 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
561 bool success = true;
563 if (host != NULL)
564 success = strcmp (host, triplets) == 0;
565 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
567 if (success && user != NULL)
568 success = strcmp (user, triplets) == 0;
569 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
571 if (success && (domain == NULL || strcmp (domain, triplets) == 0))
573 dataset->resp.result = 1;
574 break;
576 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
580 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
582 /* The data has not changed. We will just bump the timeout
583 value. Note that the new record has been allocated on
584 the stack and need not be freed. */
585 dh->timeout = timeout;
586 dh->ttl = dataset->head.ttl;
587 ++dh->nreloads;
588 return timeout;
591 if (he == NULL)
593 /* We write the dataset before inserting it to the database
594 since while inserting this thread might block and so would
595 unnecessarily let the receiver wait. */
596 assert (fd != -1);
598 #ifdef HAVE_SENDFILE
599 if (__builtin_expect (db->mmap_used, 1) && cacheable)
601 assert (db->wr_fd != -1);
602 assert ((char *) &dataset->resp > (char *) db->data);
603 assert ((char *) dataset - (char *) db->head + sizeof (*dataset)
604 <= (sizeof (struct database_pers_head)
605 + db->head->module * sizeof (ref_t)
606 + db->head->data_size));
607 # ifndef __ASSUME_SENDFILE
608 ssize_t written =
609 # endif
610 sendfileall (fd, db->wr_fd,
611 (char *) &dataset->resp - (char *) db->head,
612 sizeof (innetgroup_response_header));
613 # ifndef __ASSUME_SENDFILE
614 if (written == -1 && errno == ENOSYS)
615 goto use_write;
616 # endif
618 else
619 #endif
621 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
622 use_write:
623 #endif
624 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
628 if (cacheable)
630 /* If necessary, we also propagate the data to disk. */
631 if (db->persistent)
633 // XXX async OK?
634 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
635 msync ((void *) pval,
636 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
637 + req->key_len,
638 MS_ASYNC);
641 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
642 true, db, uid, he == NULL);
644 pthread_rwlock_unlock (&db->lock);
646 /* Mark the old entry as obsolete. */
647 if (dh != NULL)
648 dh->usable = false;
651 return timeout;
655 void
656 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
657 void *key, uid_t uid)
659 struct dataset *ignore;
661 addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
665 time_t
666 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
667 struct datahead *dh)
669 request_header req =
671 .type = GETNETGRENT,
672 .key_len = he->len
674 struct dataset *ignore;
676 return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
677 &ignore);
681 void
682 addinnetgr (struct database_dyn *db, int fd, request_header *req,
683 void *key, uid_t uid)
685 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
689 time_t
690 readdinnetgr (struct database_dyn *db, struct hashentry *he,
691 struct datahead *dh)
693 request_header req =
695 .type = INNETGR,
696 .key_len = he->len
699 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);