1 /* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, see <https://www.gnu.org/licenses/>. */
27 #include "../nss/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 timeout
= datahead_init_neg (&dataset
->head
,
94 sizeof (struct dataset
) + req
->key_len
,
95 total
, db
->negtimeout
);
97 /* This is the reply. */
98 memcpy (&dataset
->resp
, ¬found
, total
);
100 /* Copy the key data. */
101 memcpy (dataset
->strdata
, key
, req
->key_len
);
102 *key_copy
= dataset
->strdata
;
113 addgetnetgrentX (struct database_dyn
*db
, int fd
, request_header
*req
,
114 const char *key
, uid_t uid
, struct hashentry
*he
,
115 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 nss_action_list 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
);
144 if (netgroup_database
== NULL
145 && !__nss_database_get (nss_database_netgroup
, &netgroup_database
))
147 /* No such service. */
148 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
150 goto maybe_cache_add
;
153 memset (&data
, '\0', sizeof (data
));
154 buffer
= xmalloc (buflen
);
156 first_needed
->next
= first_needed
;
157 memcpy (first_needed
->name
, key
, group_len
);
158 data
.needed_groups
= first_needed
;
160 while (data
.needed_groups
!= NULL
)
162 /* Add the next group to the list of those which are known. */
163 struct name_list
*this_group
= data
.needed_groups
->next
;
164 if (this_group
== data
.needed_groups
)
165 data
.needed_groups
= NULL
;
167 data
.needed_groups
->next
= this_group
->next
;
168 this_group
->next
= data
.known_groups
;
169 data
.known_groups
= this_group
;
173 enum nss_status (*f
) (const char *, struct __netgrent
*);
177 nss_action_list nip
= netgroup_database
;
178 int no_more
= __nss_lookup (&nip
, "setnetgrent", NULL
, &setfct
.ptr
);
181 enum nss_status status
182 = DL_CALL_FCT (*setfct
.f
, (data
.known_groups
->name
, &data
));
184 if (status
== NSS_STATUS_SUCCESS
)
189 enum nss_status (*f
) (struct __netgrent
*, char *, size_t,
193 getfct
.ptr
= __nss_lookup_function (nip
, "getnetgrent_r");
194 if (getfct
.f
!= NULL
)
198 status
= getfct
.f (&data
, buffer
+ buffilled
,
199 buflen
- buffilled
- req
->key_len
, &e
);
200 if (status
== NSS_STATUS_SUCCESS
)
202 if (data
.type
== triple_val
)
204 const char *nhost
= data
.val
.triple
.host
;
205 const char *nuser
= data
.val
.triple
.user
;
206 const char *ndomain
= data
.val
.triple
.domain
;
208 size_t hostlen
= strlen (nhost
?: "") + 1;
209 size_t userlen
= strlen (nuser
?: "") + 1;
210 size_t domainlen
= strlen (ndomain
?: "") + 1;
212 if (nhost
== NULL
|| nuser
== NULL
|| ndomain
== NULL
213 || nhost
> nuser
|| nuser
> ndomain
)
215 const char *last
= nhost
;
217 || (nuser
!= NULL
&& nuser
> last
))
220 || (ndomain
!= NULL
&& ndomain
> last
))
226 : last
+ strlen (last
) + 1 - buffer
);
228 /* We have to make temporary copies. */
229 size_t needed
= hostlen
+ userlen
+ domainlen
;
231 if (buflen
- req
->key_len
- bufused
< needed
)
233 buflen
+= MAX (buflen
, 2 * needed
);
234 /* Save offset in the old buffer. We don't
235 bother with the NULL check here since
236 we'll do that later anyway. */
237 size_t nhostdiff
= nhost
- buffer
;
238 size_t nuserdiff
= nuser
- buffer
;
239 size_t ndomaindiff
= ndomain
- buffer
;
241 char *newbuf
= xrealloc (buffer
, buflen
);
242 /* Fix up the triplet pointers into the new
244 nhost
= (nhost
? newbuf
+ nhostdiff
246 nuser
= (nuser
? newbuf
+ nuserdiff
248 ndomain
= (ndomain
? newbuf
+ ndomaindiff
250 *tofreep
= buffer
= newbuf
;
253 nhost
= memcpy (buffer
+ bufused
,
254 nhost
?: "", hostlen
);
255 nuser
= memcpy ((char *) nhost
+ hostlen
,
256 nuser
?: "", userlen
);
257 ndomain
= memcpy ((char *) nuser
+ userlen
,
258 ndomain
?: "", domainlen
);
261 char *wp
= buffer
+ buffilled
;
262 wp
= memmove (wp
, nhost
?: "", hostlen
);
264 wp
= memmove (wp
, nuser
?: "", userlen
);
266 wp
= memmove (wp
, ndomain
?: "", domainlen
);
268 buffilled
= wp
- buffer
;
273 /* Check that the group has not been
275 struct name_list
*runp
= data
.needed_groups
;
279 if (strcmp (runp
->name
, data
.val
.group
) == 0)
283 if (runp
== data
.needed_groups
)
292 runp
= data
.known_groups
;
294 if (strcmp (runp
->name
, data
.val
.group
) == 0)
302 /* A new group is requested. */
303 size_t namelen
= strlen (data
.val
.group
) + 1;
304 struct name_list
*newg
= alloca (sizeof (*newg
)
306 memcpy (newg
->name
, data
.val
.group
, namelen
);
307 if (data
.needed_groups
== NULL
)
308 data
.needed_groups
= newg
->next
= newg
;
311 newg
->next
= data
.needed_groups
->next
;
312 data
.needed_groups
->next
= newg
;
313 data
.needed_groups
= newg
;
318 else if (status
== NSS_STATUS_TRYAGAIN
&& e
== ERANGE
)
321 *tofreep
= buffer
= xrealloc (buffer
, buflen
);
323 else if (status
== NSS_STATUS_RETURN
324 || status
== NSS_STATUS_NOTFOUND
325 || status
== NSS_STATUS_UNAVAIL
)
326 /* This was either the last one for this group or the
327 group was empty or the NSS module had an internal
328 failure. Look at next group if available. */
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
,
351 goto maybe_cache_add
;
356 /* Fill in the dataset. */
357 dataset
= (struct dataset
*) buffer
;
358 timeout
= datahead_init_pos (&dataset
->head
, total
+ req
->key_len
,
359 total
- offsetof (struct dataset
, resp
),
360 he
== NULL
? 0 : dh
->nreloads
+ 1,
363 dataset
->resp
.version
= NSCD_VERSION
;
364 dataset
->resp
.found
= 1;
365 dataset
->resp
.nresults
= nentries
;
366 dataset
->resp
.result_len
= buffilled
- sizeof (*dataset
);
368 assert (buflen
- buffilled
>= req
->key_len
);
369 key_copy
= memcpy (buffer
+ buffilled
, key
, req
->key_len
);
370 buffilled
+= req
->key_len
;
372 /* Now we can determine whether on refill we have to create a new
378 if (dataset
->head
.allocsize
== dh
->allocsize
379 && dataset
->head
.recsize
== dh
->recsize
380 && memcmp (&dataset
->resp
, dh
->data
,
381 dh
->allocsize
- offsetof (struct dataset
, resp
)) == 0)
383 /* The data has not changed. We will just bump the timeout
384 value. Note that the new record has been allocated on
385 the stack and need not be freed. */
386 dh
->timeout
= dataset
->head
.timeout
;
387 dh
->ttl
= dataset
->head
.ttl
;
389 dataset
= (struct dataset
*) dh
;
397 = (struct dataset
*) mempool_alloc (db
, total
+ req
->key_len
, 1);
398 if (__glibc_likely (newp
!= NULL
))
400 /* Adjust pointer into the memory block. */
401 key_copy
= (char *) newp
+ (key_copy
- buffer
);
403 dataset
= memcpy (newp
, dataset
, total
+ req
->key_len
);
407 /* Mark the old record as obsolete. */
412 if (he
== NULL
&& fd
!= -1)
413 /* We write the dataset before inserting it to the database since
414 while inserting this thread might block and so would
415 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. */
449 addinnetgrX (struct database_dyn
*db
, int fd
, request_header
*req
,
450 char *key
, uid_t uid
, struct hashentry
*he
,
453 const char *group
= key
;
454 key
= strchr (key
, '\0') + 1;
455 size_t group_len
= key
- group
;
456 const char *host
= *key
++ ? key
: NULL
;
458 key
= strchr (key
, '\0') + 1;
459 const char *user
= *key
++ ? key
: NULL
;
461 key
= strchr (key
, '\0') + 1;
462 const char *domain
= *key
++ ? key
: NULL
;
464 if (__glibc_unlikely (debug_level
> 0))
467 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
468 group
, host
?: "", user
?: "", domain
?: "");
470 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
471 group
, host
?: "", user
?: "", domain
?: "");
474 struct dataset
*result
= (struct dataset
*) cache_search (GETNETGRENT
,
481 timeout
= result
->head
.timeout
;
486 request_header req_get
=
491 timeout
= addgetnetgrentX (db
, -1, &req_get
, group
, uid
, NULL
, NULL
,
497 struct datahead head
;
498 innetgroup_response_header resp
;
500 = (struct indataset
*) mempool_alloc (db
,
501 sizeof (*dataset
) + req
->key_len
,
503 bool cacheable
= true;
504 if (__glibc_unlikely (dataset
== NULL
))
507 /* The alloca is safe because nscd_run_worker verfies that
508 key_len is not larger than MAXKEYLEN. */
509 dataset
= alloca (sizeof (*dataset
) + req
->key_len
);
512 datahead_init_pos (&dataset
->head
, sizeof (*dataset
) + req
->key_len
,
513 sizeof (innetgroup_response_header
),
514 he
== NULL
? 0 : dh
->nreloads
+ 1, result
->head
.ttl
);
515 /* Set the notfound status and timeout based on the result from
517 dataset
->head
.notfound
= result
->head
.notfound
;
518 dataset
->head
.timeout
= timeout
;
520 dataset
->resp
.version
= NSCD_VERSION
;
521 dataset
->resp
.found
= result
->resp
.found
;
522 /* Until we find a matching entry the result is 0. */
523 dataset
->resp
.result
= 0;
525 char *key_copy
= memcpy ((char *) (dataset
+ 1), group
, req
->key_len
);
527 if (dataset
->resp
.found
)
529 const char *triplets
= (const char *) (&result
->resp
+ 1);
531 for (nscd_ssize_t i
= result
->resp
.nresults
; i
> 0; --i
)
535 /* For the host, user and domain in each triplet, we assume success
536 if the value is blank because that is how the wildcard entry to
537 match anything is stored in the netgroup cache. */
538 if (host
!= NULL
&& *triplets
!= '\0')
539 success
= strcmp (host
, triplets
) == 0;
540 triplets
= strchr (triplets
, '\0') + 1;
542 if (success
&& user
!= NULL
&& *triplets
!= '\0')
543 success
= strcmp (user
, triplets
) == 0;
544 triplets
= strchr (triplets
, '\0') + 1;
546 if (success
&& (domain
== NULL
|| *triplets
== '\0'
547 || strcmp (domain
, triplets
) == 0))
549 dataset
->resp
.result
= 1;
552 triplets
= strchr (triplets
, '\0') + 1;
556 if (he
!= NULL
&& dh
->data
[0].innetgroupdata
.result
== dataset
->resp
.result
)
558 /* The data has not changed. We will just bump the timeout
559 value. Note that the new record has been allocated on
560 the stack and need not be freed. */
561 dh
->timeout
= timeout
;
562 dh
->ttl
= dataset
->head
.ttl
;
565 pthread_rwlock_unlock (&db
->lock
);
571 /* We write the dataset before inserting it to the database
572 since while inserting this thread might block and so would
573 unnecessarily let the receiver wait. */
576 writeall (fd
, &dataset
->resp
, sizeof (innetgroup_response_header
));
581 /* If necessary, we also propagate the data to disk. */
585 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
586 msync ((void *) pval
,
587 ((uintptr_t) dataset
& pagesize_m1
) + sizeof (*dataset
)
592 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
593 true, db
, uid
, he
== NULL
);
595 pthread_rwlock_unlock (&db
->lock
);
597 /* Mark the old entry as obsolete. */
609 addgetnetgrentX_ignore (struct database_dyn
*db
, int fd
, request_header
*req
,
610 const char *key
, uid_t uid
, struct hashentry
*he
,
613 struct dataset
*ignore
;
615 time_t timeout
= addgetnetgrentX (db
, fd
, req
, key
, uid
, he
, dh
,
622 addgetnetgrent (struct database_dyn
*db
, int fd
, request_header
*req
,
623 void *key
, uid_t uid
)
625 addgetnetgrentX_ignore (db
, fd
, req
, key
, uid
, NULL
, NULL
);
630 readdgetnetgrent (struct database_dyn
*db
, struct hashentry
*he
,
638 return addgetnetgrentX_ignore
639 (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
);
644 addinnetgr (struct database_dyn
*db
, int fd
, request_header
*req
,
645 void *key
, uid_t uid
)
647 addinnetgrX (db
, fd
, req
, key
, uid
, NULL
, NULL
);
652 readdinnetgr (struct database_dyn
*db
, struct hashentry
*he
,
661 return addinnetgrX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
);