1 /* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2020 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 <https://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
,
119 if (__glibc_unlikely (debug_level
> 0))
122 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key
);
124 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key
);
127 static service_user
*netgroup_database
;
129 struct dataset
*dataset
;
130 bool cacheable
= false;
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
);
140 size_t group_len
= strlen (key
) + 1;
141 struct name_list
*first_needed
142 = alloca (sizeof (struct name_list
) + group_len
);
145 if (netgroup_database
== NULL
146 && __nss_database_lookup2 ("netgroup", NULL
, NULL
, &netgroup_database
))
148 /* No such service. */
149 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
154 memset (&data
, '\0', sizeof (data
));
155 buffer
= xmalloc (buflen
);
157 first_needed
->next
= first_needed
;
158 memcpy (first_needed
->name
, key
, group_len
);
159 data
.needed_groups
= first_needed
;
161 while (data
.needed_groups
!= NULL
)
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
;
168 data
.needed_groups
->next
= this_group
->next
;
169 this_group
->next
= data
.known_groups
;
170 data
.known_groups
= this_group
;
174 enum nss_status (*f
) (const char *, struct __netgrent
*);
178 service_user
*nip
= netgroup_database
;
179 int no_more
= __nss_lookup (&nip
, "setnetgrent", NULL
, &setfct
.ptr
);
182 enum nss_status status
183 = DL_CALL_FCT (*setfct
.f
, (data
.known_groups
->name
, &data
));
185 if (status
== NSS_STATUS_SUCCESS
)
190 enum nss_status (*f
) (struct __netgrent
*, char *, size_t,
194 getfct
.ptr
= __nss_lookup_function (nip
, "getnetgrent_r");
195 if (getfct
.f
!= NULL
)
199 status
= getfct
.f (&data
, buffer
+ buffilled
,
200 buflen
- buffilled
- req
->key_len
, &e
);
201 if (status
== NSS_STATUS_SUCCESS
)
203 if (data
.type
== triple_val
)
205 const char *nhost
= data
.val
.triple
.host
;
206 const char *nuser
= data
.val
.triple
.user
;
207 const char *ndomain
= data
.val
.triple
.domain
;
209 size_t hostlen
= strlen (nhost
?: "") + 1;
210 size_t userlen
= strlen (nuser
?: "") + 1;
211 size_t domainlen
= strlen (ndomain
?: "") + 1;
213 if (nhost
== NULL
|| nuser
== NULL
|| ndomain
== NULL
214 || nhost
> nuser
|| nuser
> ndomain
)
216 const char *last
= nhost
;
218 || (nuser
!= NULL
&& nuser
> last
))
221 || (ndomain
!= NULL
&& ndomain
> last
))
227 : last
+ strlen (last
) + 1 - buffer
);
229 /* We have to make temporary copies. */
230 size_t needed
= hostlen
+ userlen
+ domainlen
;
232 if (buflen
- req
->key_len
- bufused
< needed
)
234 buflen
+= MAX (buflen
, 2 * needed
);
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
;
242 char *newbuf
= xrealloc (buffer
, buflen
);
243 /* Fix up the triplet pointers into the new
245 nhost
= (nhost
? newbuf
+ nhostdiff
247 nuser
= (nuser
? newbuf
+ nuserdiff
249 ndomain
= (ndomain
? newbuf
+ ndomaindiff
254 nhost
= memcpy (buffer
+ bufused
,
255 nhost
?: "", hostlen
);
256 nuser
= memcpy ((char *) nhost
+ hostlen
,
257 nuser
?: "", userlen
);
258 ndomain
= memcpy ((char *) nuser
+ userlen
,
259 ndomain
?: "", domainlen
);
262 char *wp
= buffer
+ buffilled
;
263 wp
= memmove (wp
, nhost
?: "", hostlen
);
265 wp
= memmove (wp
, nuser
?: "", userlen
);
267 wp
= memmove (wp
, ndomain
?: "", domainlen
);
269 buffilled
= wp
- buffer
;
274 /* Check that the group has not been
276 struct name_list
*runp
= data
.needed_groups
;
280 if (strcmp (runp
->name
, data
.val
.group
) == 0)
284 if (runp
== data
.needed_groups
)
293 runp
= data
.known_groups
;
295 if (strcmp (runp
->name
, data
.val
.group
) == 0)
303 /* A new group is requested. */
304 size_t namelen
= strlen (data
.val
.group
) + 1;
305 struct name_list
*newg
= alloca (sizeof (*newg
)
307 memcpy (newg
->name
, data
.val
.group
, namelen
);
308 if (data
.needed_groups
== NULL
)
309 data
.needed_groups
= newg
->next
= newg
;
312 newg
->next
= data
.needed_groups
->next
;
313 data
.needed_groups
->next
= newg
;
314 data
.needed_groups
= newg
;
319 else if (status
== NSS_STATUS_TRYAGAIN
&& e
== ERANGE
)
322 buffer
= xrealloc (buffer
, buflen
);
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. */
333 enum nss_status (*endfct
) (struct __netgrent
*);
334 endfct
= __nss_lookup_function (nip
, "endnetgrent");
336 (void) DL_CALL_FCT (*endfct
, (&data
));
341 no_more
= __nss_next2 (&nip
, "setnetgrent", NULL
, &setfct
.ptr
,
346 /* No results. Return a failure and write out a notfound record in the
350 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
357 /* Fill in the dataset. */
358 dataset
= (struct dataset
*) buffer
;
359 timeout
= datahead_init_pos (&dataset
->head
, total
+ req
->key_len
,
360 total
- offsetof (struct dataset
, resp
),
361 he
== NULL
? 0 : dh
->nreloads
+ 1,
364 dataset
->resp
.version
= NSCD_VERSION
;
365 dataset
->resp
.found
= 1;
366 dataset
->resp
.nresults
= nentries
;
367 dataset
->resp
.result_len
= buffilled
- sizeof (*dataset
);
369 assert (buflen
- buffilled
>= req
->key_len
);
370 key_copy
= memcpy (buffer
+ buffilled
, key
, req
->key_len
);
371 buffilled
+= req
->key_len
;
373 /* Now we can determine whether on refill we have to create a new
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)
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
;
390 dataset
= (struct dataset
*) dh
;
398 = (struct dataset
*) mempool_alloc (db
, total
+ req
->key_len
, 1);
399 if (__glibc_likely (newp
!= NULL
))
401 /* Adjust pointer into the memory block. */
402 key_copy
= (char *) newp
+ (key_copy
- buffer
);
404 dataset
= memcpy (newp
, dataset
, total
+ req
->key_len
);
408 /* Mark the old record as obsolete. */
413 if (he
== NULL
&& fd
!= -1)
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. */
419 writeall (fd
, &dataset
->resp
, dataset
->head
.recsize
);
424 /* If necessary, we also propagate the data to disk. */
428 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
429 msync ((void *) pval
,
430 ((uintptr_t) dataset
& pagesize_m1
) + total
+ req
->key_len
,
434 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
435 true, db
, uid
, he
== NULL
);
437 pthread_rwlock_unlock (&db
->lock
);
439 /* Mark the old entry as obsolete. */
452 addinnetgrX (struct database_dyn
*db
, int fd
, request_header
*req
,
453 char *key
, uid_t uid
, struct hashentry
*he
,
456 const char *group
= key
;
457 key
= (char *) rawmemchr (key
, '\0') + 1;
458 size_t group_len
= key
- group
;
459 const char *host
= *key
++ ? key
: NULL
;
461 key
= (char *) rawmemchr (key
, '\0') + 1;
462 const char *user
= *key
++ ? key
: NULL
;
464 key
= (char *) rawmemchr (key
, '\0') + 1;
465 const char *domain
= *key
++ ? key
: NULL
;
467 if (__glibc_unlikely (debug_level
> 0))
470 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
471 group
, host
?: "", user
?: "", domain
?: "");
473 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
474 group
, host
?: "", user
?: "", domain
?: "");
477 struct dataset
*result
= (struct dataset
*) cache_search (GETNETGRENT
,
484 timeout
= result
->head
.timeout
;
489 request_header req_get
=
494 timeout
= addgetnetgrentX (db
, -1, &req_get
, group
, uid
, NULL
, NULL
,
500 struct datahead head
;
501 innetgroup_response_header resp
;
503 = (struct indataset
*) mempool_alloc (db
,
504 sizeof (*dataset
) + req
->key_len
,
506 struct indataset dataset_mem
;
507 bool cacheable
= true;
508 if (__glibc_unlikely (dataset
== NULL
))
511 dataset
= &dataset_mem
;
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
519 dataset
->head
.notfound
= result
->head
.notfound
;
520 dataset
->head
.timeout
= timeout
;
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;
527 char *key_copy
= memcpy ((char *) (dataset
+ 1), group
, req
->key_len
);
529 if (dataset
->resp
.found
)
531 const char *triplets
= (const char *) (&result
->resp
+ 1);
533 for (nscd_ssize_t i
= result
->resp
.nresults
; i
> 0; --i
)
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')
541 success
= strcmp (host
, triplets
) == 0;
542 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
544 if (success
&& user
!= NULL
&& *triplets
!= '\0')
545 success
= strcmp (user
, triplets
) == 0;
546 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
548 if (success
&& (domain
== NULL
|| *triplets
== '\0'
549 || strcmp (domain
, triplets
) == 0))
551 dataset
->resp
.result
= 1;
554 triplets
= (const char *) rawmemchr (triplets
, '\0') + 1;
558 if (he
!= NULL
&& dh
->data
[0].innetgroupdata
.result
== dataset
->resp
.result
)
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
;
567 pthread_rwlock_unlock (&db
->lock
);
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. */
578 writeall (fd
, &dataset
->resp
, sizeof (innetgroup_response_header
));
583 /* If necessary, we also propagate the data to disk. */
587 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
588 msync ((void *) pval
,
589 ((uintptr_t) dataset
& pagesize_m1
) + sizeof (*dataset
)
594 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
595 true, db
, uid
, he
== NULL
);
597 pthread_rwlock_unlock (&db
->lock
);
599 /* Mark the old entry as obsolete. */
611 addgetnetgrentX_ignore (struct database_dyn
*db
, int fd
, request_header
*req
,
612 const char *key
, uid_t uid
, struct hashentry
*he
,
615 struct dataset
*ignore
;
617 time_t timeout
= addgetnetgrentX (db
, fd
, req
, key
, uid
, he
, dh
,
624 addgetnetgrent (struct database_dyn
*db
, int fd
, request_header
*req
,
625 void *key
, uid_t uid
)
627 addgetnetgrentX_ignore (db
, fd
, req
, key
, uid
, NULL
, NULL
);
632 readdgetnetgrent (struct database_dyn
*db
, struct hashentry
*he
,
640 return addgetnetgrentX_ignore
641 (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
);
646 addinnetgr (struct database_dyn
*db
, int fd
, request_header
*req
,
647 void *key
, uid_t uid
)
649 addinnetgrX (db
, fd
, req
, key
, uid
, NULL
, NULL
);
654 readdinnetgr (struct database_dyn
*db
, struct hashentry
*he
,
663 return addinnetgrX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
);