]> git.ipfire.org Git - thirdparty/glibc.git/blame - nscd/netgroupcache.c
Remove duplicate initialization of field in nscd.
[thirdparty/glibc.git] / nscd / netgroupcache.c
CommitLineData
684ae515 1/* Cache handling for netgroup lookup.
04277e02 2 Copyright (C) 2011-2019 Free Software Foundation, Inc.
684ae515
UD
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
59ba27a6 17 along with this program; if not, see <http://www.gnu.org/licenses/>. */
684ae515
UD
18
19#include <alloca.h>
20#include <assert.h>
21#include <errno.h>
22#include <libintl.h>
23#include <stdbool.h>
c6dfed24 24#include <stdlib.h>
684ae515
UD
25#include <unistd.h>
26#include <sys/mman.h>
27
28#include "../inet/netgroup.h"
29#include "nscd.h"
30#include "dbg_log.h"
37233df9
TS
31
32#include <kernel-features.h>
684ae515
UD
33
34
35/* This is the standard reply in case the service is disabled. */
36static const netgroup_response_header disabled =
37{
38 .version = NSCD_VERSION,
39 .found = -1,
40 .nresults = 0,
41 .result_len = 0
42};
43
44/* This is the struct describing how to write this record. */
45const struct iovec netgroup_iov_disabled =
46{
47 .iov_base = (void *) &disabled,
48 .iov_len = sizeof (disabled)
49};
50
51
52/* This is the standard reply in case we haven't found the dataset. */
53static const netgroup_response_header notfound =
54{
55 .version = NSCD_VERSION,
56 .found = 0,
57 .nresults = 0,
58 .result_len = 0
59};
60
61
62struct dataset
63{
64 struct datahead head;
65 netgroup_response_header resp;
66 char strdata[0];
67};
68
9a3c6a6f
SP
69/* Sends a notfound message and prepares a notfound dataset to write to the
70 cache. Returns true if there was enough memory to allocate the dataset and
71 returns the dataset in DATASETP, total bytes to write in TOTALP and the
72 timeout in TIMEOUTP. KEY_COPY is set to point to the copy of the key in the
73 dataset. */
74static bool
75do_notfound (struct database_dyn *db, int fd, request_header *req,
76 const char *key, struct dataset **datasetp, ssize_t *totalp,
77 time_t *timeoutp, char **key_copy)
78{
79 struct dataset *dataset;
80 ssize_t total;
81 time_t timeout;
82 bool cacheable = false;
83
84 total = sizeof (notfound);
85 timeout = time (NULL) + db->negtimeout;
86
87 if (fd != -1)
88 TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
89
90 dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
91 /* If we cannot permanently store the result, so be it. */
92 if (dataset != NULL)
93 {
1cdeb237
SP
94 timeout = datahead_init_neg (&dataset->head,
95 sizeof (struct dataset) + req->key_len,
96 total, db->negtimeout);
9a3c6a6f
SP
97
98 /* This is the reply. */
99 memcpy (&dataset->resp, &notfound, total);
100
101 /* Copy the key data. */
102 memcpy (dataset->strdata, key, req->key_len);
103 *key_copy = dataset->strdata;
104
105 cacheable = true;
106 }
107 *timeoutp = timeout;
108 *totalp = total;
109 *datasetp = dataset;
110 return cacheable;
111}
684ae515
UD
112
113static time_t
114addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
115 const char *key, uid_t uid, struct hashentry *he,
745664bd
FW
116 struct datahead *dh, struct dataset **resultp,
117 void **tofreep)
684ae515 118{
a1ffb40e 119 if (__glibc_unlikely (debug_level > 0))
684ae515
UD
120 {
121 if (he == NULL)
122 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
123 else
124 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
125 }
126
127 static service_user *netgroup_database;
128 time_t timeout;
129 struct dataset *dataset;
130 bool cacheable = false;
131 ssize_t total;
9a3c6a6f 132 bool found = false;
684ae515
UD
133
134 char *key_copy = NULL;
135 struct __netgrent data;
136 size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
137 size_t buffilled = sizeof (*dataset);
138 char *buffer = NULL;
139 size_t nentries = 0;
684ae515 140 size_t group_len = strlen (key) + 1;
c6dfed24
RM
141 struct name_list *first_needed
142 = alloca (sizeof (struct name_list) + group_len);
745664bd 143 *tofreep = NULL;
684ae515
UD
144
145 if (netgroup_database == NULL
146 && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database))
147 {
148 /* No such service. */
9a3c6a6f
SP
149 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
150 &key_copy);
684ae515
UD
151 goto writeout;
152 }
153
154 memset (&data, '\0', sizeof (data));
980cb518 155 buffer = xmalloc (buflen);
745664bd 156 *tofreep = buffer;
c6dfed24
RM
157 first_needed->next = first_needed;
158 memcpy (first_needed->name, key, group_len);
159 data.needed_groups = first_needed;
684ae515
UD
160
161 while (data.needed_groups != NULL)
162 {
163 /* Add the next group to the list of those which are known. */
164 struct name_list *this_group = data.needed_groups->next;
165 if (this_group == data.needed_groups)
166 data.needed_groups = NULL;
167 else
168 data.needed_groups->next = this_group->next;
169 this_group->next = data.known_groups;
170 data.known_groups = this_group;
171
172 union
173 {
174 enum nss_status (*f) (const char *, struct __netgrent *);
175 void *ptr;
176 } setfct;
177
178 service_user *nip = netgroup_database;
179 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
180 while (!no_more)
181 {
182 enum nss_status status
183 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
184
185 if (status == NSS_STATUS_SUCCESS)
186 {
9a3c6a6f 187 found = true;
684ae515
UD
188 union
189 {
190 enum nss_status (*f) (struct __netgrent *, char *, size_t,
191 int *);
192 void *ptr;
193 } getfct;
194 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
195 if (getfct.f != NULL)
196 while (1)
197 {
198 int e;
199 status = getfct.f (&data, buffer + buffilled,
c44496df 200 buflen - buffilled - req->key_len, &e);
684ae515
UD
201 if (status == NSS_STATUS_SUCCESS)
202 {
203 if (data.type == triple_val)
204 {
205 const char *nhost = data.val.triple.host;
206 const char *nuser = data.val.triple.user;
207 const char *ndomain = data.val.triple.domain;
208
ea7d8b95
SP
209 size_t hostlen = strlen (nhost ?: "") + 1;
210 size_t userlen = strlen (nuser ?: "") + 1;
211 size_t domainlen = strlen (ndomain ?: "") + 1;
212
50fd745b
AS
213 if (nhost == NULL || nuser == NULL || ndomain == NULL
214 || nhost > nuser || nuser > ndomain)
684ae515 215 {
50fd745b
AS
216 const char *last = nhost;
217 if (last == NULL
218 || (nuser != NULL && nuser > last))
219 last = nuser;
220 if (last == NULL
221 || (ndomain != NULL && ndomain > last))
222 last = ndomain;
223
224 size_t bufused
225 = (last == NULL
226 ? buffilled
227 : last + strlen (last) + 1 - buffer);
684ae515
UD
228
229 /* We have to make temporary copies. */
684ae515
UD
230 size_t needed = hostlen + userlen + domainlen;
231
232 if (buflen - req->key_len - bufused < needed)
233 {
980cb518 234 buflen += MAX (buflen, 2 * needed);
af37a8a3
SP
235 /* Save offset in the old buffer. We don't
236 bother with the NULL check here since
237 we'll do that later anyway. */
238 size_t nhostdiff = nhost - buffer;
239 size_t nuserdiff = nuser - buffer;
240 size_t ndomaindiff = ndomain - buffer;
241
5d41dadf 242 char *newbuf = xrealloc (buffer, buflen);
af37a8a3 243 /* Fix up the triplet pointers into the new
5d41dadf 244 buffer. */
af37a8a3 245 nhost = (nhost ? newbuf + nhostdiff
5d41dadf 246 : NULL);
af37a8a3 247 nuser = (nuser ? newbuf + nuserdiff
5d41dadf 248 : NULL);
af37a8a3 249 ndomain = (ndomain ? newbuf + ndomaindiff
5d41dadf
SP
250 : NULL);
251 buffer = newbuf;
684ae515
UD
252 }
253
254 nhost = memcpy (buffer + bufused,
50fd745b 255 nhost ?: "", hostlen);
684ae515 256 nuser = memcpy ((char *) nhost + hostlen,
50fd745b 257 nuser ?: "", userlen);
684ae515 258 ndomain = memcpy ((char *) nuser + userlen,
50fd745b 259 ndomain ?: "", domainlen);
684ae515
UD
260 }
261
262 char *wp = buffer + buffilled;
ea7d8b95
SP
263 wp = memmove (wp, nhost ?: "", hostlen);
264 wp += hostlen;
265 wp = memmove (wp, nuser ?: "", userlen);
266 wp += userlen;
267 wp = memmove (wp, ndomain ?: "", domainlen);
268 wp += domainlen;
684ae515
UD
269 buffilled = wp - buffer;
270 ++nentries;
271 }
272 else
273 {
274 /* Check that the group has not been
275 requested before. */
276 struct name_list *runp = data.needed_groups;
277 if (runp != NULL)
278 while (1)
279 {
280 if (strcmp (runp->name, data.val.group) == 0)
281 break;
282
283 runp = runp->next;
284 if (runp == data.needed_groups)
285 {
286 runp = NULL;
287 break;
288 }
289 }
290
291 if (runp == NULL)
292 {
293 runp = data.known_groups;
294 while (runp != NULL)
295 if (strcmp (runp->name, data.val.group) == 0)
296 break;
297 else
298 runp = runp->next;
299 }
300
301 if (runp == NULL)
302 {
303 /* A new group is requested. */
304 size_t namelen = strlen (data.val.group) + 1;
305 struct name_list *newg = alloca (sizeof (*newg)
306 + namelen);
307 memcpy (newg->name, data.val.group, namelen);
308 if (data.needed_groups == NULL)
309 data.needed_groups = newg->next = newg;
310 else
311 {
312 newg->next = data.needed_groups->next;
313 data.needed_groups->next = newg;
314 data.needed_groups = newg;
315 }
316 }
317 }
318 }
c3ec475c 319 else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE)
684ae515 320 {
980cb518
SP
321 buflen *= 2;
322 buffer = xrealloc (buffer, buflen);
684ae515 323 }
c3ec475c
SP
324 else if (status == NSS_STATUS_RETURN
325 || status == NSS_STATUS_NOTFOUND
326 || status == NSS_STATUS_UNAVAIL)
327 /* This was either the last one for this group or the
328 group was empty or the NSS module had an internal
329 failure. Look at next group if available. */
330 break;
684ae515
UD
331 }
332
333 enum nss_status (*endfct) (struct __netgrent *);
334 endfct = __nss_lookup_function (nip, "endnetgrent");
335 if (endfct != NULL)
336 (void) DL_CALL_FCT (*endfct, (&data));
337
338 break;
339 }
340
341 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
342 status, 0);
343 }
344 }
345
9a3c6a6f
SP
346 /* No results. Return a failure and write out a notfound record in the
347 cache. */
348 if (!found)
349 {
350 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
351 &key_copy);
352 goto writeout;
353 }
354
684ae515
UD
355 total = buffilled;
356
357 /* Fill in the dataset. */
358 dataset = (struct dataset *) buffer;
1cdeb237
SP
359 timeout = datahead_init_pos (&dataset->head, total + req->key_len,
360 total - offsetof (struct dataset, resp),
361 he == NULL ? 0 : dh->nreloads + 1,
362 db->postimeout);
684ae515
UD
363
364 dataset->resp.version = NSCD_VERSION;
365 dataset->resp.found = 1;
366 dataset->resp.nresults = nentries;
367 dataset->resp.result_len = buffilled - sizeof (*dataset);
368
369 assert (buflen - buffilled >= req->key_len);
370 key_copy = memcpy (buffer + buffilled, key, req->key_len);
371 buffilled += req->key_len;
372
373 /* Now we can determine whether on refill we have to create a new
374 record or not. */
375 if (he != NULL)
376 {
377 assert (fd == -1);
378
379 if (dataset->head.allocsize == dh->allocsize
380 && dataset->head.recsize == dh->recsize
381 && memcmp (&dataset->resp, dh->data,
382 dh->allocsize - offsetof (struct dataset, resp)) == 0)
383 {
384 /* The data has not changed. We will just bump the timeout
385 value. Note that the new record has been allocated on
386 the stack and need not be freed. */
387 dh->timeout = dataset->head.timeout;
388 dh->ttl = dataset->head.ttl;
389 ++dh->nreloads;
390 dataset = (struct dataset *) dh;
391
392 goto out;
393 }
394 }
395
396 {
397 struct dataset *newp
398 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
a1ffb40e 399 if (__glibc_likely (newp != NULL))
684ae515
UD
400 {
401 /* Adjust pointer into the memory block. */
402 key_copy = (char *) newp + (key_copy - buffer);
403
404 dataset = memcpy (newp, dataset, total + req->key_len);
405 cacheable = true;
406
407 if (he != NULL)
408 /* Mark the old record as obsolete. */
409 dh->usable = false;
410 }
411 }
412
413 if (he == NULL && fd != -1)
414 {
415 /* We write the dataset before inserting it to the database
416 since while inserting this thread might block and so would
417 unnecessarily let the receiver wait. */
418 writeout:
8c78faa9 419 writeall (fd, &dataset->resp, dataset->head.recsize);
684ae515
UD
420 }
421
422 if (cacheable)
423 {
424 /* If necessary, we also propagate the data to disk. */
425 if (db->persistent)
426 {
427 // XXX async OK?
428 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
429 msync ((void *) pval,
430 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
431 MS_ASYNC);
432 }
433
434 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
435 true, db, uid, he == NULL);
436
437 pthread_rwlock_unlock (&db->lock);
438
439 /* Mark the old entry as obsolete. */
440 if (dh != NULL)
441 dh->usable = false;
442 }
443
444 out:
684ae515
UD
445 *resultp = dataset;
446
447 return timeout;
448}
449
450
451static time_t
452addinnetgrX (struct database_dyn *db, int fd, request_header *req,
453 char *key, uid_t uid, struct hashentry *he,
454 struct datahead *dh)
455{
456 const char *group = key;
457 key = (char *) rawmemchr (key, '\0') + 1;
1c81d55f 458 size_t group_len = key - group;
684ae515
UD
459 const char *host = *key++ ? key : NULL;
460 if (host != NULL)
461 key = (char *) rawmemchr (key, '\0') + 1;
462 const char *user = *key++ ? key : NULL;
463 if (user != NULL)
464 key = (char *) rawmemchr (key, '\0') + 1;
465 const char *domain = *key++ ? key : NULL;
466
a1ffb40e 467 if (__glibc_unlikely (debug_level > 0))
684ae515
UD
468 {
469 if (he == NULL)
470 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
471 group, host ?: "", user ?: "", domain ?: "");
472 else
473 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
474 group, host ?: "", user ?: "", domain ?: "");
475 }
476
477 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
478 group, group_len,
479 db, uid);
480 time_t timeout;
745664bd 481 void *tofree;
684ae515 482 if (result != NULL)
745664bd
FW
483 {
484 timeout = result->head.timeout;
485 tofree = NULL;
486 }
684ae515
UD
487 else
488 {
489 request_header req_get =
490 {
491 .type = GETNETGRENT,
492 .key_len = group_len
493 };
494 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
745664bd 495 &result, &tofree);
684ae515
UD
496 }
497
498 struct indataset
499 {
500 struct datahead head;
501 innetgroup_response_header resp;
502 } *dataset
503 = (struct indataset *) mempool_alloc (db,
504 sizeof (*dataset) + req->key_len,
505 1);
506 struct indataset dataset_mem;
507 bool cacheable = true;
a1ffb40e 508 if (__glibc_unlikely (dataset == NULL))
684ae515
UD
509 {
510 cacheable = false;
511 dataset = &dataset_mem;
512 }
513
1cdeb237
SP
514 datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len,
515 sizeof (innetgroup_response_header),
516 he == NULL ? 0 : dh->nreloads + 1, result->head.ttl);
517 /* Set the notfound status and timeout based on the result from
518 getnetgrent. */
684ae515 519 dataset->head.notfound = result->head.notfound;
684ae515
UD
520 dataset->head.timeout = timeout;
521
522 dataset->resp.version = NSCD_VERSION;
523 dataset->resp.found = result->resp.found;
524 /* Until we find a matching entry the result is 0. */
525 dataset->resp.result = 0;
526
527 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
528
529 if (dataset->resp.found)
530 {
531 const char *triplets = (const char *) (&result->resp + 1);
532
533 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
534 {
535 bool success = true;
536
fbd6b5a4
SP
537 /* For the host, user and domain in each triplet, we assume success
538 if the value is blank because that is how the wildcard entry to
539 match anything is stored in the netgroup cache. */
540 if (host != NULL && *triplets != '\0')
684ae515
UD
541 success = strcmp (host, triplets) == 0;
542 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
543
fbd6b5a4 544 if (success && user != NULL && *triplets != '\0')
684ae515
UD
545 success = strcmp (user, triplets) == 0;
546 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
547
fbd6b5a4
SP
548 if (success && (domain == NULL || *triplets == '\0'
549 || strcmp (domain, triplets) == 0))
684ae515
UD
550 {
551 dataset->resp.result = 1;
552 break;
553 }
554 triplets = (const char *) rawmemchr (triplets, '\0') + 1;
555 }
556 }
557
558 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
559 {
560 /* The data has not changed. We will just bump the timeout
561 value. Note that the new record has been allocated on
562 the stack and need not be freed. */
563 dh->timeout = timeout;
564 dh->ttl = dataset->head.ttl;
565 ++dh->nreloads;
3de93d19
DD
566 if (cacheable)
567 pthread_rwlock_unlock (&db->lock);
745664bd 568 goto out;
684ae515
UD
569 }
570
571 if (he == NULL)
572 {
573 /* We write the dataset before inserting it to the database
574 since while inserting this thread might block and so would
575 unnecessarily let the receiver wait. */
8c78faa9 576 assert (fd != -1);
684ae515 577
8c78faa9 578 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
684ae515
UD
579 }
580
581 if (cacheable)
582 {
583 /* If necessary, we also propagate the data to disk. */
584 if (db->persistent)
585 {
586 // XXX async OK?
587 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
588 msync ((void *) pval,
589 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
590 + req->key_len,
591 MS_ASYNC);
592 }
593
594 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
595 true, db, uid, he == NULL);
596
597 pthread_rwlock_unlock (&db->lock);
598
599 /* Mark the old entry as obsolete. */
600 if (dh != NULL)
601 dh->usable = false;
602 }
603
745664bd
FW
604 out:
605 free (tofree);
684ae515
UD
606 return timeout;
607}
608
609
745664bd
FW
610static time_t
611addgetnetgrentX_ignore (struct database_dyn *db, int fd, request_header *req,
612 const char *key, uid_t uid, struct hashentry *he,
613 struct datahead *dh)
614{
615 struct dataset *ignore;
616 void *tofree;
617 time_t timeout = addgetnetgrentX (db, fd, req, key, uid, he, dh,
618 &ignore, &tofree);
619 free (tofree);
620 return timeout;
621}
622
684ae515
UD
623void
624addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
625 void *key, uid_t uid)
626{
745664bd 627 addgetnetgrentX_ignore (db, fd, req, key, uid, NULL, NULL);
684ae515
UD
628}
629
630
631time_t
632readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
633 struct datahead *dh)
634{
635 request_header req =
636 {
637 .type = GETNETGRENT,
638 .key_len = he->len
639 };
745664bd
FW
640 return addgetnetgrentX_ignore
641 (db, -1, &req, db->data + he->key, he->owner, he, dh);
684ae515
UD
642}
643
644
645void
646addinnetgr (struct database_dyn *db, int fd, request_header *req,
647 void *key, uid_t uid)
648{
649 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
650}
651
652
653time_t
654readdinnetgr (struct database_dyn *db, struct hashentry *he,
655 struct datahead *dh)
656{
657 request_header req =
658 {
659 .type = INNETGR,
660 .key_len = he->len
661 };
662
663 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);
664}