1 /* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2018 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/>. */
28 #include "../inet/netgroup.h"
32 #include <kernel-features.h>
35 /* This is the standard reply in case the service is disabled. */
36 static const netgroup_response_header disabled
=
38 .version
= NSCD_VERSION
,
44 /* This is the struct describing how to write this record. */
45 const struct iovec netgroup_iov_disabled
=
47 .iov_base
= (void *) &disabled
,
48 .iov_len
= sizeof (disabled
)
52 /* This is the standard reply in case we haven't found the dataset. */
53 static const netgroup_response_header notfound
=
55 .version
= NSCD_VERSION
,
65 netgroup_response_header resp
;
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
75 do_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
)
79 struct dataset
*dataset
;
82 bool cacheable
= false;
84 total
= sizeof (notfound
);
85 timeout
= time (NULL
) + db
->negtimeout
;
88 TEMP_FAILURE_RETRY (send (fd
, ¬found
, total
, MSG_NOSIGNAL
));
90 dataset
= mempool_alloc (db
, sizeof (struct dataset
) + req
->key_len
, 1);
91 /* If we cannot permanently store the result, so be it. */
94 timeout
= datahead_init_neg (&dataset
->head
,
95 sizeof (struct dataset
) + req
->key_len
,
96 total
, db
->negtimeout
);
98 /* This is the reply. */
99 memcpy (&dataset
->resp
, ¬found
, total
);
101 /* Copy the key data. */
102 memcpy (dataset
->strdata
, key
, req
->key_len
);
103 *key_copy
= dataset
->strdata
;
114 addgetnetgrentX (struct database_dyn
*db
, int fd
, request_header
*req
,
115 const char *key
, uid_t uid
, struct hashentry
*he
,
116 struct datahead
*dh
, struct dataset
**resultp
)
118 if (__glibc_unlikely (debug_level
> 0))
121 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key
);
123 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key
);
126 static service_user
*netgroup_database
;
128 struct dataset
*dataset
;
129 bool cacheable
= false;
133 char *key_copy
= NULL
;
134 struct __netgrent data
;
135 size_t buflen
= MAX (1024, sizeof (*dataset
) + req
->key_len
);
136 size_t buffilled
= sizeof (*dataset
);
139 size_t group_len
= strlen (key
) + 1;
140 struct name_list
*first_needed
141 = alloca (sizeof (struct name_list
) + group_len
);
143 if (netgroup_database
== NULL
144 && __nss_database_lookup ("netgroup", NULL
, NULL
, &netgroup_database
))
146 /* No such service. */
147 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
152 memset (&data
, '\0', sizeof (data
));
153 buffer
= xmalloc (buflen
);
154 first_needed
->next
= first_needed
;
155 memcpy (first_needed
->name
, key
, group_len
);
156 data
.needed_groups
= first_needed
;
158 while (data
.needed_groups
!= NULL
)
160 /* Add the next group to the list of those which are known. */
161 struct name_list
*this_group
= data
.needed_groups
->next
;
162 if (this_group
== data
.needed_groups
)
163 data
.needed_groups
= NULL
;
165 data
.needed_groups
->next
= this_group
->next
;
166 this_group
->next
= data
.known_groups
;
167 data
.known_groups
= this_group
;
171 enum nss_status (*f
) (const char *, struct __netgrent
*);
175 service_user
*nip
= netgroup_database
;
176 int no_more
= __nss_lookup (&nip
, "setnetgrent", NULL
, &setfct
.ptr
);
179 enum nss_status status
180 = DL_CALL_FCT (*setfct
.f
, (data
.known_groups
->name
, &data
));
182 if (status
== NSS_STATUS_SUCCESS
)
187 enum nss_status (*f
) (struct __netgrent
*, char *, size_t,
191 getfct
.ptr
= __nss_lookup_function (nip
, "getnetgrent_r");
192 if (getfct
.f
!= NULL
)
196 status
= getfct
.f (&data
, buffer
+ buffilled
,
197 buflen
- buffilled
- req
->key_len
, &e
);
198 if (status
== NSS_STATUS_SUCCESS
)
200 if (data
.type
== triple_val
)
202 const char *nhost
= data
.val
.triple
.host
;
203 const char *nuser
= data
.val
.triple
.user
;
204 const char *ndomain
= data
.val
.triple
.domain
;
206 size_t hostlen
= strlen (nhost
?: "") + 1;
207 size_t userlen
= strlen (nuser
?: "") + 1;
208 size_t domainlen
= strlen (ndomain
?: "") + 1;
210 if (nhost
== NULL
|| nuser
== NULL
|| ndomain
== NULL
211 || nhost
> nuser
|| nuser
> ndomain
)
213 const char *last
= nhost
;
215 || (nuser
!= NULL
&& nuser
> last
))
218 || (ndomain
!= NULL
&& ndomain
> last
))
224 : last
+ strlen (last
) + 1 - buffer
);
226 /* We have to make temporary copies. */
227 size_t needed
= hostlen
+ userlen
+ domainlen
;
229 if (buflen
- req
->key_len
- bufused
< needed
)
231 buflen
+= MAX (buflen
, 2 * needed
);
232 /* Save offset in the old buffer. We don't
233 bother with the NULL check here since
234 we'll do that later anyway. */
235 size_t nhostdiff
= nhost
- buffer
;
236 size_t nuserdiff
= nuser
- buffer
;
237 size_t ndomaindiff
= ndomain
- buffer
;
239 char *newbuf
= xrealloc (buffer
, buflen
);
240 /* Fix up the triplet pointers into the new
242 nhost
= (nhost
? newbuf
+ nhostdiff
244 nuser
= (nuser
? newbuf
+ nuserdiff
246 ndomain
= (ndomain
? newbuf
+ ndomaindiff
251 nhost
= memcpy (buffer
+ bufused
,
252 nhost
?: "", hostlen
);
253 nuser
= memcpy ((char *) nhost
+ hostlen
,
254 nuser
?: "", userlen
);
255 ndomain
= memcpy ((char *) nuser
+ userlen
,
256 ndomain
?: "", domainlen
);
259 char *wp
= buffer
+ buffilled
;
260 wp
= memmove (wp
, nhost
?: "", hostlen
);
262 wp
= memmove (wp
, nuser
?: "", userlen
);
264 wp
= memmove (wp
, ndomain
?: "", domainlen
);
266 buffilled
= wp
- buffer
;
271 /* Check that the group has not been
273 struct name_list
*runp
= data
.needed_groups
;
277 if (strcmp (runp
->name
, data
.val
.group
) == 0)
281 if (runp
== data
.needed_groups
)
290 runp
= data
.known_groups
;
292 if (strcmp (runp
->name
, data
.val
.group
) == 0)
300 /* A new group is requested. */
301 size_t namelen
= strlen (data
.val
.group
) + 1;
302 struct name_list
*newg
= alloca (sizeof (*newg
)
304 memcpy (newg
->name
, data
.val
.group
, namelen
);
305 if (data
.needed_groups
== NULL
)
306 data
.needed_groups
= newg
->next
= newg
;
309 newg
->next
= data
.needed_groups
->next
;
310 data
.needed_groups
->next
= newg
;
311 data
.needed_groups
= newg
;
316 else if (status
== NSS_STATUS_TRYAGAIN
&& e
== ERANGE
)
319 buffer
= xrealloc (buffer
, buflen
);
321 else if (status
== NSS_STATUS_RETURN
322 || status
== NSS_STATUS_NOTFOUND
323 || status
== NSS_STATUS_UNAVAIL
)
324 /* This was either the last one for this group or the
325 group was empty or the NSS module had an internal
326 failure. Look at next group if available. */
330 enum nss_status (*endfct
) (struct __netgrent
*);
331 endfct
= __nss_lookup_function (nip
, "endnetgrent");
333 (void) DL_CALL_FCT (*endfct
, (&data
));
338 no_more
= __nss_next2 (&nip
, "setnetgrent", NULL
, &setfct
.ptr
,
343 /* No results. Return a failure and write out a notfound record in the
347 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
354 /* Fill in the dataset. */
355 dataset
= (struct dataset
*) buffer
;
356 timeout
= datahead_init_pos (&dataset
->head
, total
+ req
->key_len
,
357 total
- offsetof (struct dataset
, resp
),
358 he
== NULL
? 0 : dh
->nreloads
+ 1,
361 dataset
->resp
.version
= NSCD_VERSION
;
362 dataset
->resp
.found
= 1;
363 dataset
->resp
.nresults
= nentries
;
364 dataset
->resp
.result_len
= buffilled
- sizeof (*dataset
);
366 assert (buflen
- buffilled
>= req
->key_len
);
367 key_copy
= memcpy (buffer
+ buffilled
, key
, req
->key_len
);
368 buffilled
+= req
->key_len
;
370 /* Now we can determine whether on refill we have to create a new
376 if (dataset
->head
.allocsize
== dh
->allocsize
377 && dataset
->head
.recsize
== dh
->recsize
378 && memcmp (&dataset
->resp
, dh
->data
,
379 dh
->allocsize
- offsetof (struct dataset
, resp
)) == 0)
381 /* The data has not changed. We will just bump the timeout
382 value. Note that the new record has been allocated on
383 the stack and need not be freed. */
384 dh
->timeout
= dataset
->head
.timeout
;
385 dh
->ttl
= dataset
->head
.ttl
;
387 dataset
= (struct dataset
*) dh
;
395 = (struct dataset
*) mempool_alloc (db
, total
+ req
->key_len
, 1);
396 if (__glibc_likely (newp
!= NULL
))
398 /* Adjust pointer into the memory block. */
399 key_copy
= (char *) newp
+ (key_copy
- buffer
);
401 dataset
= memcpy (newp
, dataset
, total
+ req
->key_len
);
405 /* Mark the old record as obsolete. */
410 if (he
== NULL
&& fd
!= -1)
412 /* We write the dataset before inserting it to the database
413 since while inserting this thread might block and so would
414 unnecessarily let the receiver wait. */
416 writeall (fd
, &dataset
->resp
, dataset
->head
.recsize
);
421 /* If necessary, we also propagate the data to disk. */
425 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
426 msync ((void *) pval
,
427 ((uintptr_t) dataset
& pagesize_m1
) + total
+ req
->key_len
,
431 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
432 true, db
, uid
, he
== NULL
);
434 pthread_rwlock_unlock (&db
->lock
);
436 /* Mark the old entry as obsolete. */
451 addinnetgrX (struct database_dyn
*db
, int fd
, request_header
*req
,
452 char *key
, uid_t uid
, struct hashentry
*he
,
455 const char *group
= key
;
456 key
= (char *) rawmemchr (key
, '\0') + 1;
457 size_t group_len
= key
- group
;
458 const char *host
= *key
++ ? key
: NULL
;
460 key
= (char *) rawmemchr (key
, '\0') + 1;
461 const char *user
= *key
++ ? key
: NULL
;
463 key
= (char *) rawmemchr (key
, '\0') + 1;
464 const char *domain
= *key
++ ? key
: NULL
;
466 if (__glibc_unlikely (debug_level
> 0))
469 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
470 group
, host
?: "", user
?: "", domain
?: "");
472 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
473 group
, host
?: "", user
?: "", domain
?: "");
476 struct dataset
*result
= (struct dataset
*) cache_search (GETNETGRENT
,
481 timeout
= result
->head
.timeout
;
484 request_header req_get
=
489 timeout
= addgetnetgrentX (db
, -1, &req_get
, group
, uid
, NULL
, NULL
,
495 struct datahead head
;
496 innetgroup_response_header resp
;
498 = (struct indataset
*) mempool_alloc (db
,
499 sizeof (*dataset
) + req
->key_len
,
501 struct indataset dataset_mem
;
502 bool cacheable
= true;
503 if (__glibc_unlikely (dataset
== NULL
))
506 dataset
= &dataset_mem
;
509 datahead_init_pos (&dataset
->head
, sizeof (*dataset
) + req
->key_len
,
510 sizeof (innetgroup_response_header
),
511 he
== NULL
? 0 : dh
->nreloads
+ 1, result
->head
.ttl
);
512 /* Set the notfound status and timeout based on the result from
514 dataset
->head
.notfound
= result
->head
.notfound
;
515 dataset
->head
.timeout
= timeout
;
517 dataset
->resp
.version
= NSCD_VERSION
;
518 dataset
->resp
.found
= result
->resp
.found
;
519 /* Until we find a matching entry the result is 0. */
520 dataset
->resp
.result
= 0;
522 char *key_copy
= memcpy ((char *) (dataset
+ 1), group
, req
->key_len
);
524 if (dataset
->resp
.found
)
526 const char *triplets
= (const char *) (&result
->resp
+ 1);
528 for (nscd_ssize_t i
= result
->resp
.nresults
; i
> 0; --i
)
532 /* For the host, user and domain in each triplet, we assume success
533 if the value is blank because that is how the wildcard entry to
534 match anything is stored in the netgroup cache. */
535 if (host
!= NULL
&& *triplets
!= '\0')
536 success
= strcmp (host
, triplets
) == 0;
537 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
539 if (success
&& user
!= NULL
&& *triplets
!= '\0')
540 success
= strcmp (user
, triplets
) == 0;
541 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
543 if (success
&& (domain
== NULL
|| *triplets
== '\0'
544 || strcmp (domain
, triplets
) == 0))
546 dataset
->resp
.result
= 1;
549 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
553 if (he
!= NULL
&& dh
->data
[0].innetgroupdata
.result
== dataset
->resp
.result
)
555 /* The data has not changed. We will just bump the timeout
556 value. Note that the new record has been allocated on
557 the stack and need not be freed. */
558 dh
->timeout
= timeout
;
559 dh
->ttl
= dataset
->head
.ttl
;
562 pthread_rwlock_unlock (&db
->lock
);
568 /* We write the dataset before inserting it to the database
569 since while inserting this thread might block and so would
570 unnecessarily let the receiver wait. */
573 writeall (fd
, &dataset
->resp
, sizeof (innetgroup_response_header
));
578 /* If necessary, we also propagate the data to disk. */
582 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
583 msync ((void *) pval
,
584 ((uintptr_t) dataset
& pagesize_m1
) + sizeof (*dataset
)
589 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
590 true, db
, uid
, he
== NULL
);
592 pthread_rwlock_unlock (&db
->lock
);
594 /* Mark the old entry as obsolete. */
604 addgetnetgrent (struct database_dyn
*db
, int fd
, request_header
*req
,
605 void *key
, uid_t uid
)
607 struct dataset
*ignore
;
609 addgetnetgrentX (db
, fd
, req
, key
, uid
, NULL
, NULL
, &ignore
);
614 readdgetnetgrent (struct database_dyn
*db
, struct hashentry
*he
,
622 struct dataset
*ignore
;
624 return addgetnetgrentX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
,
630 addinnetgr (struct database_dyn
*db
, int fd
, request_header
*req
,
631 void *key
, uid_t uid
)
633 addinnetgrX (db
, fd
, req
, key
, uid
, NULL
, NULL
);
638 readdinnetgr (struct database_dyn
*db
, struct hashentry
*he
,
647 return addinnetgrX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
);