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 size_t hostlen
= strlen (nhost
?: "") + 1;
220 size_t userlen
= strlen (nuser
?: "") + 1;
221 size_t domainlen
= strlen (ndomain
?: "") + 1;
223 if (nhost
== NULL
|| nuser
== NULL
|| ndomain
== NULL
224 || nhost
> nuser
|| nuser
> ndomain
)
226 const char *last
= nhost
;
228 || (nuser
!= NULL
&& nuser
> last
))
231 || (ndomain
!= NULL
&& ndomain
> last
))
237 : last
+ strlen (last
) + 1 - buffer
);
239 /* We have to make temporary copies. */
240 size_t needed
= hostlen
+ userlen
+ domainlen
;
242 if (buflen
- req
->key_len
- bufused
< needed
)
244 buflen
+= MAX (buflen
, 2 * needed
);
245 /* Save offset in the old buffer. We don't
246 bother with the NULL check here since
247 we'll do that later anyway. */
248 size_t nhostdiff
= nhost
- buffer
;
249 size_t nuserdiff
= nuser
- buffer
;
250 size_t ndomaindiff
= ndomain
- buffer
;
252 char *newbuf
= xrealloc (buffer
, buflen
);
253 /* Fix up the triplet pointers into the new
255 nhost
= (nhost
? newbuf
+ nhostdiff
257 nuser
= (nuser
? newbuf
+ nuserdiff
259 ndomain
= (ndomain
? newbuf
+ ndomaindiff
264 nhost
= memcpy (buffer
+ bufused
,
265 nhost
?: "", hostlen
);
266 nuser
= memcpy ((char *) nhost
+ hostlen
,
267 nuser
?: "", userlen
);
268 ndomain
= memcpy ((char *) nuser
+ userlen
,
269 ndomain
?: "", domainlen
);
272 char *wp
= buffer
+ buffilled
;
273 wp
= memmove (wp
, nhost
?: "", hostlen
);
275 wp
= memmove (wp
, nuser
?: "", userlen
);
277 wp
= memmove (wp
, ndomain
?: "", domainlen
);
279 buffilled
= wp
- buffer
;
284 /* Check that the group has not been
286 struct name_list
*runp
= data
.needed_groups
;
290 if (strcmp (runp
->name
, data
.val
.group
) == 0)
294 if (runp
== data
.needed_groups
)
303 runp
= data
.known_groups
;
305 if (strcmp (runp
->name
, data
.val
.group
) == 0)
313 /* A new group is requested. */
314 size_t namelen
= strlen (data
.val
.group
) + 1;
315 struct name_list
*newg
= alloca (sizeof (*newg
)
317 memcpy (newg
->name
, data
.val
.group
, namelen
);
318 if (data
.needed_groups
== NULL
)
319 data
.needed_groups
= newg
->next
= newg
;
322 newg
->next
= data
.needed_groups
->next
;
323 data
.needed_groups
->next
= newg
;
324 data
.needed_groups
= newg
;
329 else if (status
== NSS_STATUS_UNAVAIL
&& e
== ERANGE
)
332 buffer
= xrealloc (buffer
, buflen
);
336 enum nss_status (*endfct
) (struct __netgrent
*);
337 endfct
= __nss_lookup_function (nip
, "endnetgrent");
339 (void) DL_CALL_FCT (*endfct
, (&data
));
344 no_more
= __nss_next2 (&nip
, "setnetgrent", NULL
, &setfct
.ptr
,
349 /* No results. Return a failure and write out a notfound record in the
353 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
360 /* Fill in the dataset. */
361 dataset
= (struct dataset
*) buffer
;
362 dataset
->head
.allocsize
= total
+ req
->key_len
;
363 dataset
->head
.recsize
= total
- offsetof (struct dataset
, resp
);
364 dataset
->head
.notfound
= false;
365 dataset
->head
.nreloads
= he
== NULL
? 0 : (dh
->nreloads
+ 1);
366 dataset
->head
.usable
= true;
367 dataset
->head
.ttl
= db
->postimeout
;
368 timeout
= dataset
->head
.timeout
= time (NULL
) + dataset
->head
.ttl
;
370 dataset
->resp
.version
= NSCD_VERSION
;
371 dataset
->resp
.found
= 1;
372 dataset
->resp
.nresults
= nentries
;
373 dataset
->resp
.result_len
= buffilled
- sizeof (*dataset
);
375 assert (buflen
- buffilled
>= req
->key_len
);
376 key_copy
= memcpy (buffer
+ buffilled
, key
, req
->key_len
);
377 buffilled
+= req
->key_len
;
379 /* Now we can determine whether on refill we have to create a new
385 if (dataset
->head
.allocsize
== dh
->allocsize
386 && dataset
->head
.recsize
== dh
->recsize
387 && memcmp (&dataset
->resp
, dh
->data
,
388 dh
->allocsize
- offsetof (struct dataset
, resp
)) == 0)
390 /* The data has not changed. We will just bump the timeout
391 value. Note that the new record has been allocated on
392 the stack and need not be freed. */
393 dh
->timeout
= dataset
->head
.timeout
;
394 dh
->ttl
= dataset
->head
.ttl
;
396 dataset
= (struct dataset
*) dh
;
404 = (struct dataset
*) mempool_alloc (db
, total
+ req
->key_len
, 1);
405 if (__glibc_likely (newp
!= NULL
))
407 /* Adjust pointer into the memory block. */
408 key_copy
= (char *) newp
+ (key_copy
- buffer
);
410 dataset
= memcpy (newp
, dataset
, total
+ req
->key_len
);
414 /* Mark the old record as obsolete. */
419 if (he
== NULL
&& fd
!= -1)
421 /* We write the dataset before inserting it to the database
422 since while inserting this thread might block and so would
423 unnecessarily let the receiver wait. */
426 if (__builtin_expect (db
->mmap_used
, 1) && cacheable
)
428 assert (db
->wr_fd
!= -1);
429 assert ((char *) &dataset
->resp
> (char *) db
->data
);
430 assert ((char *) dataset
- (char *) db
->head
+ total
431 <= (sizeof (struct database_pers_head
)
432 + db
->head
->module
* sizeof (ref_t
)
433 + db
->head
->data_size
));
434 # ifndef __ASSUME_SENDFILE
437 sendfileall (fd
, db
->wr_fd
, (char *) &dataset
->resp
438 - (char *) db
->head
, dataset
->head
.recsize
);
439 # ifndef __ASSUME_SENDFILE
440 if (written
== -1 && errno
== ENOSYS
)
447 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
450 writeall (fd
, &dataset
->resp
, dataset
->head
.recsize
);
456 /* If necessary, we also propagate the data to disk. */
460 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
461 msync ((void *) pval
,
462 ((uintptr_t) dataset
& pagesize_m1
) + total
+ req
->key_len
,
466 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
467 true, db
, uid
, he
== NULL
);
469 pthread_rwlock_unlock (&db
->lock
);
471 /* Mark the old entry as obsolete. */
486 addinnetgrX (struct database_dyn
*db
, int fd
, request_header
*req
,
487 char *key
, uid_t uid
, struct hashentry
*he
,
490 const char *group
= key
;
491 key
= (char *) rawmemchr (key
, '\0') + 1;
492 size_t group_len
= key
- group
- 1;
493 const char *host
= *key
++ ? key
: NULL
;
495 key
= (char *) rawmemchr (key
, '\0') + 1;
496 const char *user
= *key
++ ? key
: NULL
;
498 key
= (char *) rawmemchr (key
, '\0') + 1;
499 const char *domain
= *key
++ ? key
: NULL
;
501 if (__glibc_unlikely (debug_level
> 0))
504 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
505 group
, host
?: "", user
?: "", domain
?: "");
507 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
508 group
, host
?: "", user
?: "", domain
?: "");
511 struct dataset
*result
= (struct dataset
*) cache_search (GETNETGRENT
,
516 timeout
= result
->head
.timeout
;
519 request_header req_get
=
524 timeout
= addgetnetgrentX (db
, -1, &req_get
, group
, uid
, NULL
, NULL
,
530 struct datahead head
;
531 innetgroup_response_header resp
;
533 = (struct indataset
*) mempool_alloc (db
,
534 sizeof (*dataset
) + req
->key_len
,
536 struct indataset dataset_mem
;
537 bool cacheable
= true;
538 if (__glibc_unlikely (dataset
== NULL
))
541 dataset
= &dataset_mem
;
544 dataset
->head
.allocsize
= sizeof (*dataset
) + req
->key_len
;
545 dataset
->head
.recsize
= sizeof (innetgroup_response_header
);
546 dataset
->head
.notfound
= result
->head
.notfound
;
547 dataset
->head
.nreloads
= he
== NULL
? 0 : (dh
->nreloads
+ 1);
548 dataset
->head
.usable
= true;
549 dataset
->head
.ttl
= result
->head
.ttl
;
550 dataset
->head
.timeout
= timeout
;
552 dataset
->resp
.version
= NSCD_VERSION
;
553 dataset
->resp
.found
= result
->resp
.found
;
554 /* Until we find a matching entry the result is 0. */
555 dataset
->resp
.result
= 0;
557 char *key_copy
= memcpy ((char *) (dataset
+ 1), group
, req
->key_len
);
559 if (dataset
->resp
.found
)
561 const char *triplets
= (const char *) (&result
->resp
+ 1);
563 for (nscd_ssize_t i
= result
->resp
.nresults
; i
> 0; --i
)
567 /* For the host, user and domain in each triplet, we assume success
568 if the value is blank because that is how the wildcard entry to
569 match anything is stored in the netgroup cache. */
570 if (host
!= NULL
&& *triplets
!= '\0')
571 success
= strcmp (host
, triplets
) == 0;
572 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
574 if (success
&& user
!= NULL
&& *triplets
!= '\0')
575 success
= strcmp (user
, triplets
) == 0;
576 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
578 if (success
&& (domain
== NULL
|| *triplets
== '\0'
579 || strcmp (domain
, triplets
) == 0))
581 dataset
->resp
.result
= 1;
584 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
588 if (he
!= NULL
&& dh
->data
[0].innetgroupdata
.result
== dataset
->resp
.result
)
590 /* The data has not changed. We will just bump the timeout
591 value. Note that the new record has been allocated on
592 the stack and need not be freed. */
593 dh
->timeout
= timeout
;
594 dh
->ttl
= dataset
->head
.ttl
;
601 /* We write the dataset before inserting it to the database
602 since while inserting this thread might block and so would
603 unnecessarily let the receiver wait. */
607 if (__builtin_expect (db
->mmap_used
, 1) && cacheable
)
609 assert (db
->wr_fd
!= -1);
610 assert ((char *) &dataset
->resp
> (char *) db
->data
);
611 assert ((char *) dataset
- (char *) db
->head
+ sizeof (*dataset
)
612 <= (sizeof (struct database_pers_head
)
613 + db
->head
->module
* sizeof (ref_t
)
614 + db
->head
->data_size
));
615 # ifndef __ASSUME_SENDFILE
618 sendfileall (fd
, db
->wr_fd
,
619 (char *) &dataset
->resp
- (char *) db
->head
,
620 sizeof (innetgroup_response_header
));
621 # ifndef __ASSUME_SENDFILE
622 if (written
== -1 && errno
== ENOSYS
)
629 #if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE
632 writeall (fd
, &dataset
->resp
, sizeof (innetgroup_response_header
));
638 /* If necessary, we also propagate the data to disk. */
642 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
643 msync ((void *) pval
,
644 ((uintptr_t) dataset
& pagesize_m1
) + sizeof (*dataset
)
649 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
650 true, db
, uid
, he
== NULL
);
652 pthread_rwlock_unlock (&db
->lock
);
654 /* Mark the old entry as obsolete. */
664 addgetnetgrent (struct database_dyn
*db
, int fd
, request_header
*req
,
665 void *key
, uid_t uid
)
667 struct dataset
*ignore
;
669 addgetnetgrentX (db
, fd
, req
, key
, uid
, NULL
, NULL
, &ignore
);
674 readdgetnetgrent (struct database_dyn
*db
, struct hashentry
*he
,
682 struct dataset
*ignore
;
684 return addgetnetgrentX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
,
690 addinnetgr (struct database_dyn
*db
, int fd
, request_header
*req
,
691 void *key
, uid_t uid
)
693 addinnetgrX (db
, fd
, req
, key
, uid
, NULL
, NULL
);
698 readdinnetgr (struct database_dyn
*db
, struct hashentry
*he
,
707 return addinnetgrX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
);