1 /* Cache handling for netgroup lookup.
2 Copyright (C) 2011-2025 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/>. */
26 #include <scratch_buffer.h>
28 #include "../nss/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 /* Send a notfound response to FD. Always returns -1 to indicate an
72 send_notfound (int fd
)
75 TEMP_FAILURE_RETRY (send (fd
, ¬found
, sizeof (notfound
), MSG_NOSIGNAL
));
79 /* Sends a notfound message and prepares a notfound dataset to write to the
80 cache. Returns true if there was enough memory to allocate the dataset and
81 returns the dataset in DATASETP, total bytes to write in TOTALP and the
82 timeout in TIMEOUTP. KEY_COPY is set to point to the copy of the key in the
85 do_notfound (struct database_dyn
*db
, int fd
, request_header
*req
,
86 const char *key
, struct dataset
**datasetp
, ssize_t
*totalp
,
87 time_t *timeoutp
, char **key_copy
)
89 struct dataset
*dataset
;
92 bool cacheable
= false;
94 total
= sizeof (notfound
);
95 timeout
= time (NULL
) + db
->negtimeout
;
99 dataset
= mempool_alloc (db
, sizeof (struct dataset
) + req
->key_len
, 1);
100 /* If we cannot permanently store the result, so be it. */
103 timeout
= datahead_init_neg (&dataset
->head
,
104 sizeof (struct dataset
) + req
->key_len
,
105 total
, db
->negtimeout
);
107 /* This is the reply. */
108 memcpy (&dataset
->resp
, ¬found
, total
);
110 /* Copy the key data. */
111 memcpy (dataset
->strdata
, key
, req
->key_len
);
112 *key_copy
= dataset
->strdata
;
122 struct addgetnetgrentX_scratch
124 /* This is the result that the caller should use. It can be NULL,
125 point into buffer, or it can be in the cache. */
126 struct dataset
*dataset
;
128 struct scratch_buffer buffer
;
130 /* Used internally in addgetnetgrentX as a staging area. */
131 struct scratch_buffer tmp
;
133 /* Number of bytes in buffer that are actually used. */
138 addgetnetgrentX_scratch_init (struct addgetnetgrentX_scratch
*scratch
)
140 scratch
->dataset
= NULL
;
141 scratch_buffer_init (&scratch
->buffer
);
142 scratch_buffer_init (&scratch
->tmp
);
144 /* Reserve space for the header. */
145 scratch
->buffer_used
= sizeof (struct dataset
);
146 static_assert (sizeof (struct dataset
) < sizeof (scratch
->tmp
.__space
),
147 "initial buffer space");
148 memset (scratch
->tmp
.data
, 0, sizeof (struct dataset
));
152 addgetnetgrentX_scratch_free (struct addgetnetgrentX_scratch
*scratch
)
154 scratch_buffer_free (&scratch
->buffer
);
155 scratch_buffer_free (&scratch
->tmp
);
158 /* Copy LENGTH bytes from S into SCRATCH. Returns NULL if SCRATCH
159 could not be resized, otherwise a pointer to the copy. */
161 addgetnetgrentX_append_n (struct addgetnetgrentX_scratch
*scratch
,
162 const char *s
, size_t length
)
166 size_t remaining
= scratch
->buffer
.length
- scratch
->buffer_used
;
167 if (remaining
>= length
)
169 if (!scratch_buffer_grow_preserve (&scratch
->buffer
))
172 char *copy
= scratch
->buffer
.data
+ scratch
->buffer_used
;
173 memcpy (copy
, s
, length
);
174 scratch
->buffer_used
+= length
;
178 /* Copy S into SCRATCH, including its null terminator. Returns false
179 if SCRATCH could not be resized. */
181 addgetnetgrentX_append (struct addgetnetgrentX_scratch
*scratch
, const char *s
)
185 return addgetnetgrentX_append_n (scratch
, s
, strlen (s
) + 1) != NULL
;
188 /* Caller must initialize and free *SCRATCH. If the return value is
189 negative, this function has sent a notfound response. */
191 addgetnetgrentX (struct database_dyn
*db
, int fd
, request_header
*req
,
192 const char *key
, uid_t uid
, struct hashentry
*he
,
193 struct datahead
*dh
, struct addgetnetgrentX_scratch
*scratch
)
195 if (__glibc_unlikely (debug_level
> 0))
198 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key
);
200 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key
);
203 static nss_action_list netgroup_database
;
205 struct dataset
*dataset
;
206 bool cacheable
= false;
210 char *key_copy
= NULL
;
211 struct __netgrent data
;
213 size_t group_len
= strlen (key
) + 1;
214 struct name_list
*first_needed
215 = alloca (sizeof (struct name_list
) + group_len
);
217 if (netgroup_database
== NULL
218 && !__nss_database_get (nss_database_netgroup
, &netgroup_database
))
220 /* No such service. */
221 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
223 goto maybe_cache_add
;
226 memset (&data
, '\0', sizeof (data
));
227 first_needed
->next
= first_needed
;
228 memcpy (first_needed
->name
, key
, group_len
);
229 data
.needed_groups
= first_needed
;
231 while (data
.needed_groups
!= NULL
)
233 /* Add the next group to the list of those which are known. */
234 struct name_list
*this_group
= data
.needed_groups
->next
;
235 if (this_group
== data
.needed_groups
)
236 data
.needed_groups
= NULL
;
238 data
.needed_groups
->next
= this_group
->next
;
239 this_group
->next
= data
.known_groups
;
240 data
.known_groups
= this_group
;
244 enum nss_status (*f
) (const char *, struct __netgrent
*);
248 nss_action_list nip
= netgroup_database
;
249 int no_more
= __nss_lookup (&nip
, "setnetgrent", NULL
, &setfct
.ptr
);
252 enum nss_status status
253 = DL_CALL_FCT (*setfct
.f
, (data
.known_groups
->name
, &data
));
255 if (status
== NSS_STATUS_SUCCESS
)
260 enum nss_status (*f
) (struct __netgrent
*, char *, size_t,
264 getfct
.ptr
= __nss_lookup_function (nip
, "getnetgrent_r");
265 if (getfct
.f
!= NULL
)
269 status
= getfct
.f (&data
, scratch
->tmp
.data
,
270 scratch
->tmp
.length
, &e
);
271 if (status
== NSS_STATUS_SUCCESS
)
273 if (data
.type
== triple_val
)
275 const char *nhost
= data
.val
.triple
.host
;
276 const char *nuser
= data
.val
.triple
.user
;
277 const char *ndomain
= data
.val
.triple
.domain
;
278 if (!(addgetnetgrentX_append (scratch
, nhost
)
279 && addgetnetgrentX_append (scratch
, nuser
)
280 && addgetnetgrentX_append (scratch
, ndomain
)))
281 return send_notfound (fd
);
286 /* Check that the group has not been
288 struct name_list
*runp
= data
.needed_groups
;
292 if (strcmp (runp
->name
, data
.val
.group
) == 0)
296 if (runp
== data
.needed_groups
)
305 runp
= data
.known_groups
;
307 if (strcmp (runp
->name
, data
.val
.group
) == 0)
315 /* A new group is requested. */
316 size_t namelen
= strlen (data
.val
.group
) + 1;
317 struct name_list
*newg
= alloca (sizeof (*newg
)
319 memcpy (newg
->name
, data
.val
.group
, namelen
);
320 if (data
.needed_groups
== NULL
)
321 data
.needed_groups
= newg
->next
= newg
;
324 newg
->next
= data
.needed_groups
->next
;
325 data
.needed_groups
->next
= newg
;
326 data
.needed_groups
= newg
;
331 else if (status
== NSS_STATUS_TRYAGAIN
&& e
== ERANGE
)
333 if (!scratch_buffer_grow (&scratch
->tmp
))
334 return send_notfound (fd
);
336 else if (status
== NSS_STATUS_RETURN
337 || status
== NSS_STATUS_NOTFOUND
338 || status
== NSS_STATUS_UNAVAIL
)
339 /* This was either the last one for this group or the
340 group was empty or the NSS module had an internal
341 failure. Look at next group if available. */
345 enum nss_status (*endfct
) (struct __netgrent
*);
346 endfct
= __nss_lookup_function (nip
, "endnetgrent");
348 (void) DL_CALL_FCT (*endfct
, (&data
));
353 no_more
= __nss_next2 (&nip
, "setnetgrent", NULL
, &setfct
.ptr
,
358 /* No results. Return a failure and write out a notfound record in the
362 cacheable
= do_notfound (db
, fd
, req
, key
, &dataset
, &total
, &timeout
,
364 goto maybe_cache_add
;
367 /* Capture the result size without the key appended. */
368 total
= scratch
->buffer_used
;
370 /* Make a copy of the key. The scratch buffer must not move after
372 key_copy
= addgetnetgrentX_append_n (scratch
, key
, req
->key_len
);
373 if (key_copy
== NULL
)
374 return send_notfound (fd
);
376 /* Fill in the dataset. */
377 dataset
= scratch
->buffer
.data
;
378 timeout
= datahead_init_pos (&dataset
->head
, total
+ req
->key_len
,
379 total
- offsetof (struct dataset
, resp
),
380 he
== NULL
? 0 : dh
->nreloads
+ 1,
383 dataset
->resp
.version
= NSCD_VERSION
;
384 dataset
->resp
.found
= 1;
385 dataset
->resp
.nresults
= nentries
;
386 dataset
->resp
.result_len
= total
- sizeof (*dataset
);
388 /* Now we can determine whether on refill we have to create a new
394 if (dataset
->head
.allocsize
== dh
->allocsize
395 && dataset
->head
.recsize
== dh
->recsize
396 && memcmp (&dataset
->resp
, dh
->data
,
397 dh
->allocsize
- offsetof (struct dataset
, resp
)) == 0)
399 /* The data has not changed. We will just bump the timeout
400 value. Note that the new record has been allocated on
401 the stack and need not be freed. */
402 dh
->timeout
= dataset
->head
.timeout
;
403 dh
->ttl
= dataset
->head
.ttl
;
405 dataset
= (struct dataset
*) dh
;
413 = (struct dataset
*) mempool_alloc (db
, total
+ req
->key_len
, 1);
414 if (__glibc_likely (newp
!= NULL
))
416 /* Adjust pointer into the memory block. */
417 key_copy
= (char *) newp
+ (key_copy
- (char *) dataset
);
419 dataset
= memcpy (newp
, dataset
, total
+ req
->key_len
);
423 /* Mark the old record as obsolete. */
428 if (he
== NULL
&& fd
!= -1)
429 /* We write the dataset before inserting it to the database since
430 while inserting this thread might block and so would
431 unnecessarily let the receiver wait. */
432 writeall (fd
, &dataset
->resp
, dataset
->head
.recsize
);
437 /* If necessary, we also propagate the data to disk. */
441 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
442 msync ((void *) pval
,
443 ((uintptr_t) dataset
& pagesize_m1
) + total
+ req
->key_len
,
447 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
448 true, db
, uid
, he
== NULL
);
450 pthread_rwlock_unlock (&db
->lock
);
452 /* Mark the old entry as obsolete. */
458 scratch
->dataset
= dataset
;
465 addinnetgrX (struct database_dyn
*db
, int fd
, request_header
*req
,
466 char *key
, uid_t uid
, struct hashentry
*he
,
469 const char *group
= key
;
470 key
= strchr (key
, '\0') + 1;
471 size_t group_len
= key
- group
;
472 const char *host
= *key
++ ? key
: NULL
;
474 key
= strchr (key
, '\0') + 1;
475 const char *user
= *key
++ ? key
: NULL
;
477 key
= strchr (key
, '\0') + 1;
478 const char *domain
= *key
++ ? key
: NULL
;
479 struct addgetnetgrentX_scratch scratch
;
481 addgetnetgrentX_scratch_init (&scratch
);
483 if (__glibc_unlikely (debug_level
> 0))
486 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
487 group
, host
?: "", user
?: "", domain
?: "");
489 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
490 group
, host
?: "", user
?: "", domain
?: "");
493 struct dataset
*result
= (struct dataset
*) cache_search (GETNETGRENT
,
498 timeout
= result
->head
.timeout
;
501 request_header req_get
=
506 timeout
= addgetnetgrentX (db
, -1, &req_get
, group
, uid
, NULL
, NULL
,
508 result
= scratch
.dataset
;
515 struct datahead head
;
516 innetgroup_response_header resp
;
518 = (struct indataset
*) mempool_alloc (db
,
519 sizeof (*dataset
) + req
->key_len
,
521 bool cacheable
= true;
522 if (__glibc_unlikely (dataset
== NULL
))
525 /* The alloca is safe because nscd_run_worker verifies that
526 key_len is not larger than MAXKEYLEN. */
527 dataset
= alloca (sizeof (*dataset
) + req
->key_len
);
530 datahead_init_pos (&dataset
->head
, sizeof (*dataset
) + req
->key_len
,
531 sizeof (innetgroup_response_header
),
532 he
== NULL
? 0 : dh
->nreloads
+ 1,
533 result
== NULL
? db
->negtimeout
: result
->head
.ttl
);
534 /* Set the notfound status and timeout based on the result from
536 dataset
->head
.notfound
= result
== NULL
|| result
->head
.notfound
;
537 dataset
->head
.timeout
= timeout
;
539 dataset
->resp
.version
= NSCD_VERSION
;
540 dataset
->resp
.found
= result
!= NULL
&& result
->resp
.found
;
541 /* Until we find a matching entry the result is 0. */
542 dataset
->resp
.result
= 0;
544 char *key_copy
= memcpy ((char *) (dataset
+ 1), group
, req
->key_len
);
546 if (dataset
->resp
.found
)
548 const char *triplets
= (const char *) (&result
->resp
+ 1);
550 for (nscd_ssize_t i
= result
->resp
.nresults
; i
> 0; --i
)
554 /* For the host, user and domain in each triplet, we assume success
555 if the value is blank because that is how the wildcard entry to
556 match anything is stored in the netgroup cache. */
557 if (host
!= NULL
&& *triplets
!= '\0')
558 success
= strcmp (host
, triplets
) == 0;
559 triplets
= strchr (triplets
, '\0') + 1;
561 if (success
&& user
!= NULL
&& *triplets
!= '\0')
562 success
= strcmp (user
, triplets
) == 0;
563 triplets
= strchr (triplets
, '\0') + 1;
565 if (success
&& (domain
== NULL
|| *triplets
== '\0'
566 || strcmp (domain
, triplets
) == 0))
568 dataset
->resp
.result
= 1;
571 triplets
= strchr (triplets
, '\0') + 1;
575 if (he
!= NULL
&& dh
->data
[0].innetgroupdata
.result
== dataset
->resp
.result
)
577 /* The data has not changed. We will just bump the timeout
578 value. Note that the new record has been allocated on
579 the stack and need not be freed. */
580 dh
->timeout
= timeout
;
581 dh
->ttl
= dataset
->head
.ttl
;
584 pthread_rwlock_unlock (&db
->lock
);
588 /* addgetnetgrentX may have already sent a notfound response. Do
589 not send another one. */
590 if (he
== NULL
&& dataset
->resp
.found
)
592 /* We write the dataset before inserting it to the database
593 since while inserting this thread might block and so would
594 unnecessarily let the receiver wait. */
597 writeall (fd
, &dataset
->resp
, sizeof (innetgroup_response_header
));
602 /* If necessary, we also propagate the data to disk. */
606 uintptr_t pval
= (uintptr_t) dataset
& ~pagesize_m1
;
607 msync ((void *) pval
,
608 ((uintptr_t) dataset
& pagesize_m1
) + sizeof (*dataset
)
613 (void) cache_add (req
->type
, key_copy
, req
->key_len
, &dataset
->head
,
614 true, db
, uid
, he
== NULL
);
616 pthread_rwlock_unlock (&db
->lock
);
618 /* Mark the old entry as obsolete. */
624 addgetnetgrentX_scratch_free (&scratch
);
630 addgetnetgrentX_ignore (struct database_dyn
*db
, int fd
, request_header
*req
,
631 const char *key
, uid_t uid
, struct hashentry
*he
,
634 struct addgetnetgrentX_scratch scratch
;
635 addgetnetgrentX_scratch_init (&scratch
);
636 time_t timeout
= addgetnetgrentX (db
, fd
, req
, key
, uid
, he
, dh
, &scratch
);
637 addgetnetgrentX_scratch_free (&scratch
);
644 addgetnetgrent (struct database_dyn
*db
, int fd
, request_header
*req
,
645 void *key
, uid_t uid
)
647 addgetnetgrentX_ignore (db
, fd
, req
, key
, uid
, NULL
, NULL
);
652 readdgetnetgrent (struct database_dyn
*db
, struct hashentry
*he
,
660 return addgetnetgrentX_ignore
661 (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
, he
, dh
);
666 addinnetgr (struct database_dyn
*db
, int fd
, request_header
*req
,
667 void *key
, uid_t uid
)
669 addinnetgrX (db
, fd
, req
, key
, uid
, NULL
, NULL
);
674 readdinnetgr (struct database_dyn
*db
, struct hashentry
*he
,
683 time_t timeout
= addinnetgrX (db
, -1, &req
, db
->data
+ he
->key
, he
->owner
,