]> git.ipfire.org Git - thirdparty/glibc.git/blob - nscd/netgroupcache.c
Provide correct buffer length to netgroup queries in nscd (BZ #16695)
[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 (__glibc_unlikely (debug_level > 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 size_t group_len = strlen (key) + 1;
145 union
146 {
147 struct name_list elem;
148 char mem[sizeof (struct name_list) + group_len];
149 } first_needed;
150
151 if (netgroup_database == NULL
152 && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
153 {
154 /* No such service. */
155 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
156 &key_copy);
157 goto writeout;
158 }
159
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;
165
166 while (data.needed_groups != NULL)
167 {
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;
176
177 union
178 {
179 enum nss_status (*f) (const char *, struct __netgrent *);
180 void *ptr;
181 } setfct;
182
183 service_user *nip = netgroup_database;
184 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
185 while (!no_more)
186 {
187 enum nss_status status
188 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
189
190 if (status == NSS_STATUS_SUCCESS)
191 {
192 found = true;
193 union
194 {
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)
202 {
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)
212 {
213 if (data.type == triple_val)
214 {
215 const char *nhost = data.val.triple.host;
216 const char *nuser = data.val.triple.user;
217 const char *ndomain = data.val.triple.domain;
218
219 if (nhost == NULL || nuser == NULL || ndomain == NULL
220 || nhost > nuser || nuser > ndomain)
221 {
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;
229
230 size_t bufused
231 = (last == NULL
232 ? buffilled
233 : last + strlen (last) + 1 - buffer);
234
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;
240
241 if (buflen - req->key_len - bufused < needed)
242 {
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;
250
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;
261 }
262
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);
269 }
270
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;
277 }
278 else
279 {
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)
285 {
286 if (strcmp (runp->name, data.val.group) == 0)
287 break;
288
289 runp = runp->next;
290 if (runp == data.needed_groups)
291 {
292 runp = NULL;
293 break;
294 }
295 }
296
297 if (runp == NULL)
298 {
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;
305 }
306
307 if (runp == NULL)
308 {
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
317 {
318 newg->next = data.needed_groups->next;
319 data.needed_groups->next = newg;
320 data.needed_groups = newg;
321 }
322 }
323 }
324 }
325 else if (status == NSS_STATUS_UNAVAIL && e == ERANGE)
326 {
327 buflen *= 2;
328 buffer = xrealloc (buffer, buflen);
329 }
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 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;
365
366 dataset->resp.version = NSCD_VERSION;
367 dataset->resp.found = 1;
368 dataset->resp.nresults = nentries;
369 dataset->resp.result_len = buffilled - sizeof (*dataset);
370
371 assert (buflen - buffilled >= req->key_len);
372 key_copy = memcpy (buffer + buffilled, key, req->key_len);
373 buffilled += req->key_len;
374
375 /* Now we can determine whether on refill we have to create a new
376 record or not. */
377 if (he != NULL)
378 {
379 assert (fd == -1);
380
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)
385 {
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;
393
394 goto out;
395 }
396 }
397
398 {
399 struct dataset *newp
400 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
401 if (__glibc_likely (newp != NULL))
402 {
403 /* Adjust pointer into the memory block. */
404 key_copy = (char *) newp + (key_copy - buffer);
405
406 dataset = memcpy (newp, dataset, total + req->key_len);
407 cacheable = true;
408
409 if (he != NULL)
410 /* Mark the old record as obsolete. */
411 dh->usable = false;
412 }
413 }
414
415 if (he == NULL && fd != -1)
416 {
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)
423 {
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
439 }
440 else
441 #endif
442 {
443 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
444 use_write:
445 #endif
446 writeall (fd, &dataset->resp, dataset->head.recsize);
447 }
448 }
449
450 if (cacheable)
451 {
452 /* If necessary, we also propagate the data to disk. */
453 if (db->persistent)
454 {
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);
460 }
461
462 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
463 true, db, uid, he == NULL);
464
465 pthread_rwlock_unlock (&db->lock);
466
467 /* Mark the old entry as obsolete. */
468 if (dh != NULL)
469 dh->usable = false;
470 }
471
472 out:
473 free (buffer);
474
475 *resultp = dataset;
476
477 return timeout;
478 }
479
480
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)
485 {
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;
496
497 if (__glibc_unlikely (debug_level > 0))
498 {
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 ?: "");
505 }
506
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
514 {
515 request_header req_get =
516 {
517 .type = GETNETGRENT,
518 .key_len = group_len
519 };
520 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
521 &result);
522 }
523
524 struct indataset
525 {
526 struct datahead head;
527 innetgroup_response_header resp;
528 } *dataset
529 = (struct indataset *) mempool_alloc (db,
530 sizeof (*dataset) + req->key_len,
531 1);
532 struct indataset dataset_mem;
533 bool cacheable = true;
534 if (__glibc_unlikely (dataset == NULL))
535 {
536 cacheable = false;
537 dataset = &dataset_mem;
538 }
539
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;
547
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;
552
553 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
554
555 if (dataset->resp.found)
556 {
557 const char *triplets = (const char *) (&result->resp + 1);
558
559 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
560 {
561 bool success = true;
562
563 if (host != NULL)
564 success = strcmp (host, triplets) == 0;
565 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
566
567 if (success && user != NULL)
568 success = strcmp (user, triplets) == 0;
569 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
570
571 if (success && (domain == NULL || strcmp (domain, triplets) == 0))
572 {
573 dataset->resp.result = 1;
574 break;
575 }
576 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
577 }
578 }
579
580 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
581 {
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;
589 }
590
591 if (he == NULL)
592 {
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);
597
598 #ifdef HAVE_SENDFILE
599 if (__builtin_expect (db->mmap_used, 1) && cacheable)
600 {
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
617 }
618 else
619 #endif
620 {
621 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
622 use_write:
623 #endif
624 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
625 }
626 }
627
628 if (cacheable)
629 {
630 /* If necessary, we also propagate the data to disk. */
631 if (db->persistent)
632 {
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);
639 }
640
641 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
642 true, db, uid, he == NULL);
643
644 pthread_rwlock_unlock (&db->lock);
645
646 /* Mark the old entry as obsolete. */
647 if (dh != NULL)
648 dh->usable = false;
649 }
650
651 return timeout;
652 }
653
654
655 void
656 addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
657 void *key, uid_t uid)
658 {
659 struct dataset *ignore;
660
661 addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore);
662 }
663
664
665 time_t
666 readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
667 struct datahead *dh)
668 {
669 request_header req =
670 {
671 .type = GETNETGRENT,
672 .key_len = he->len
673 };
674 struct dataset *ignore;
675
676 return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh,
677 &ignore);
678 }
679
680
681 void
682 addinnetgr (struct database_dyn *db, int fd, request_header *req,
683 void *key, uid_t uid)
684 {
685 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
686 }
687
688
689 time_t
690 readdinnetgr (struct database_dyn *db, struct hashentry *he,
691 struct datahead *dh)
692 {
693 request_header req =
694 {
695 .type = INNETGR,
696 .key_len = he->len
697 };
698
699 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);
700 }