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/>. */
27 #include "../inet/netgroup.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
,
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
,
64 netgroup_response_header resp
;
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
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
;
81 bool cacheable
= false;
83 total
= sizeof (notfound
);
84 timeout
= time (NULL
) + db
->negtimeout
;
87 TEMP_FAILURE_RETRY (send (fd
, ¬found
, 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. */
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
, ¬found
, total
);
106 /* Copy the key data. */
107 memcpy (dataset
->strdata
, key
, req
->key_len
);
108 *key_copy
= dataset
->strdata
;
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))
126 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key
);
128 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key
);
131 static service_user
*netgroup_database
;
133 struct dataset
*dataset
;
134 bool cacheable
= 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
);
144 size_t group_len
= strlen (key
) + 1;
147 struct name_list elem
;
148 char mem
[sizeof (struct name_list
) + group_len
];
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
,
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
;
173 data
.needed_groups
->next
= this_group
->next
;
174 this_group
->next
= data
.known_groups
;
175 data
.known_groups
= this_group
;
179 enum nss_status (*f
) (const char *, struct __netgrent
*);
183 service_user
*nip
= netgroup_database
;
184 int no_more
= __nss_lookup (&nip
, "setnetgrent", NULL
, &setfct
.ptr
);
187 enum nss_status status
188 = DL_CALL_FCT (*setfct
.f
, (data
.known_groups
->name
, &data
));
190 if (status
== NSS_STATUS_SUCCESS
)
195 enum nss_status (*f
) (struct __netgrent
*, char *, size_t,
199 getfct
.ptr
= __nss_lookup_function (nip
, "getnetgrent_r");
200 if (getfct
.f
!= NULL
)
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. */
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
;
224 || (nuser
!= NULL
&& nuser
> last
))
227 || (ndomain
!= NULL
&& ndomain
> last
))
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
254 nhost
= (nhost
? newbuf
+ nhostdiff
256 nuser
= (nuser
? newbuf
+ nuserdiff
258 ndomain
= (ndomain
? newbuf
+ ndomaindiff
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
;
280 /* Check that the group has not been
282 struct name_list
*runp
= data
.needed_groups
;
286 if (strcmp (runp
->name
, data
.val
.group
) == 0)
290 if (runp
== data
.needed_groups
)
299 runp
= data
.known_groups
;
301 if (strcmp (runp
->name
, data
.val
.group
) == 0)
309 /* A new group is requested. */
310 size_t namelen
= strlen (data
.val
.group
) + 1;
311 struct name_list
*newg
= alloca (sizeof (*newg
)
313 memcpy (newg
->name
, data
.val
.group
, namelen
);
314 if (data
.needed_groups
== NULL
)
315 data
.needed_groups
= newg
->next
= newg
;
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
)
328 buffer
= xrealloc (buffer
, buflen
);
332 enum nss_status (*endfct
) (struct __netgrent
*);
333 endfct
= __nss_lookup_function (nip
, "endnetgrent");
335 (void) DL_CALL_FCT (*endfct
, (&data
));
340 no_more
= __nss_next2 (&nip
, "setnetgrent", NULL
, &setfct
.ptr
,
345 /* No results. Return a failure and write out a notfound record in the
349 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
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
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
;
392 dataset
= (struct dataset
*) dh
;
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
);
410 /* Mark the old record as obsolete. */
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. */
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
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
)
443 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
446 writeall (fd
, &dataset
->resp
, dataset
->head
.recsize
);
452 /* If necessary, we also propagate the data to disk. */
456 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
457 msync ((void *) pval
,
458 ((uintptr_t) dataset
& pagesize_m1
) + total
+ req
->key_len
,
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. */
482 addinnetgrX (struct database_dyn
*db
, int fd
, request_header
*req
,
483 char *key
, uid_t uid
, struct hashentry
*he
,
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
;
491 key
= (char *) rawmemchr (key
, '\0') + 1;
492 const char *user
= *key
++ ? key
: NULL
;
494 key
= (char *) rawmemchr (key
, '\0') + 1;
495 const char *domain
= *key
++ ? key
: NULL
;
497 if (__glibc_unlikely (debug_level
> 0))
500 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
501 group
, host
?: "", user
?: "", domain
?: "");
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
,
512 timeout
= result
->head
.timeout
;
515 request_header req_get
=
520 timeout
= addgetnetgrentX (db
, -1, &req_get
, group
, uid
, NULL
, NULL
,
526 struct datahead head
;
527 innetgroup_response_header resp
;
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
))
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
)
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;
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
;
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. */
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
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
)
621 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
624 writeall (fd
, &dataset
->resp
, sizeof (innetgroup_response_header
));
630 /* If necessary, we also propagate the data to disk. */
634 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
635 msync ((void *) pval
,
636 ((uintptr_t) dataset
& pagesize_m1
) + sizeof (*dataset
)
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. */
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
);
666 readdgetnetgrent (struct database_dyn
*db
, struct hashentry
*he
,
674 struct dataset
*ignore
;
676 return addgetnetgrentX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
,
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
);
690 readdinnetgr (struct database_dyn
*db
, struct hashentry
*he
,
699 return addinnetgrX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
);