1/* Copyright (C) 1996-2022 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <https://www.gnu.org/licenses/>. */
17
18#include <assert.h>
19#include <atomic.h>
20#include <libc-lock.h>
21#include <errno.h>
22#include <netdb.h>
23#include <stdbool.h>
24#include <stdlib.h>
25#include <string.h>
26#include "netgroup.h"
27#include "nsswitch.h"
28#include <sysdep.h>
29#include <nscd/nscd_proto.h>
30
31
32/* Protect above variable against multiple uses at the same time. */
33__libc_lock_define_initialized (static, lock)
34
35/* The whole information for the set/get/endnetgrent functions are
36 kept in this structure. */
37static struct __netgrent dataset;
38
39/* Set up NIP to run through the services. Return nonzero if there are no
40 services (left). */
41static int
42setup (void **fctp, nss_action_list *nipp)
43{
44 int no_more;
45
46 no_more = __nss_netgroup_lookup2 (nipp, "setnetgrent", NULL, fctp);
47
48 return no_more;
49}
50
51/* Free used memory. */
52static void
53free_memory (struct __netgrent *data)
54{
55 while (data->known_groups != NULL)
56 {
57 struct name_list *tmp = data->known_groups;
58 data->known_groups = data->known_groups->next;
59 free (ptr: tmp);
60 }
61
62 while (data->needed_groups != NULL)
63 {
64 struct name_list *tmp = data->needed_groups;
65 data->needed_groups = data->needed_groups->next;
66 free (ptr: tmp);
67 }
68}
69
70static void
71endnetgrent_hook (struct __netgrent *datap)
72{
73 enum nss_status (*endfct) (struct __netgrent *);
74
75 if (datap->nip == NULL || datap->nip == (nss_action_list) -1l)
76 return;
77
78 endfct = __nss_lookup_function (datap->nip, "endnetgrent");
79 if (endfct != NULL)
80 (void) (*endfct) (datap);
81 datap->nip = NULL;
82}
83
84static int
85__internal_setnetgrent_reuse (const char *group, struct __netgrent *datap,
86 int *errnop)
87{
88 union
89 {
90 enum nss_status (*f) (const char *, struct __netgrent *);
91 void *ptr;
92 } fct;
93 enum nss_status status = NSS_STATUS_UNAVAIL;
94 struct name_list *new_elem;
95
96 /* Free data from previous service. */
97 endnetgrent_hook (datap);
98
99 /* Cycle through all the services and run their setnetgrent functions. */
100 int no_more = setup (fctp: &fct.ptr, nipp: &datap->nip);
101 while (! no_more)
102 {
103 assert (datap->data == NULL);
104
105 /* Ignore status, we force check in `__nss_next2'. */
106 status = DL_CALL_FCT (*fct.f, (group, datap));
107
108 nss_action_list old_nip = datap->nip;
109 no_more = __nss_next2 (&datap->nip, "setnetgrent", NULL, &fct.ptr,
110 status, 0);
111
112 if (status == NSS_STATUS_SUCCESS && ! no_more)
113 {
114 enum nss_status (*endfct) (struct __netgrent *);
115
116 endfct = __nss_lookup_function (old_nip, "endnetgrent");
117 if (endfct != NULL)
118 (void) DL_CALL_FCT (*endfct, (datap));
119 }
120 }
121
122 /* Add the current group to the list of known groups. */
123 size_t group_len = strlen (group) + 1;
124 new_elem = (struct name_list *) malloc (size: sizeof (struct name_list)
125 + group_len);
126 if (new_elem == NULL)
127 {
128 *errnop = errno;
129 status = NSS_STATUS_TRYAGAIN;
130 }
131 else
132 {
133 new_elem->next = datap->known_groups;
134 memcpy (new_elem->name, group, group_len);
135 datap->known_groups = new_elem;
136 }
137
138 return status == NSS_STATUS_SUCCESS;
139}
140
141int
142__internal_setnetgrent (const char *group, struct __netgrent *datap)
143{
144 /* Free list of all netgroup names from last run. */
145 free_memory (data: datap);
146
147 return __internal_setnetgrent_reuse (group, datap, errnop: &errno);
148}
149libc_hidden_def (__internal_setnetgrent)
150
151static int
152nscd_setnetgrent (const char *group)
153{
154#ifdef USE_NSCD
155 if (__nss_not_use_nscd_netgroup > 0
156 && ++__nss_not_use_nscd_netgroup > NSS_NSCD_RETRY)
157 __nss_not_use_nscd_netgroup = 0;
158
159 if (!__nss_not_use_nscd_netgroup
160 && !__nss_database_custom[NSS_DBSIDX_netgroup])
161 return __nscd_setnetgrent (group, datap: &dataset);
162#endif
163 return -1;
164}
165
166int
167setnetgrent (const char *group)
168{
169 int result;
170
171 __libc_lock_lock (lock);
172
173 result = nscd_setnetgrent (group);
174 if (result < 0)
175 result = __internal_setnetgrent (group, datap: &dataset);
176
177 __libc_lock_unlock (lock);
178
179 return result;
180}
181
182void
183__internal_endnetgrent (struct __netgrent *datap)
184{
185 endnetgrent_hook (datap);
186 /* Now free list of all netgroup names from last run. */
187 free_memory (data: datap);
188}
189libc_hidden_def (__internal_endnetgrent)
190
191
192void
193endnetgrent (void)
194{
195 __libc_lock_lock (lock);
196
197 __internal_endnetgrent (datap: &dataset);
198
199 __libc_lock_unlock (lock);
200}
201
202#ifdef USE_NSCD
203static const char *
204get_nonempty_val (const char *in)
205{
206 if (*in == '\0')
207 return NULL;
208 return in;
209}
210
211static enum nss_status
212nscd_getnetgrent (struct __netgrent *datap, char *buffer, size_t buflen,
213 int *errnop)
214{
215 if (datap->cursor >= datap->data + datap->data_size)
216 return NSS_STATUS_UNAVAIL;
217
218 datap->type = triple_val;
219 datap->val.triple.host = get_nonempty_val (in: datap->cursor);
220 datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
221 datap->val.triple.user = get_nonempty_val (in: datap->cursor);
222 datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
223 datap->val.triple.domain = get_nonempty_val (in: datap->cursor);
224 datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
225
226 return NSS_STATUS_SUCCESS;
227}
228#endif
229
230int
231__internal_getnetgrent_r (char **hostp, char **userp, char **domainp,
232 struct __netgrent *datap,
233 char *buffer, size_t buflen, int *errnop)
234{
235 enum nss_status (*fct) (struct __netgrent *, char *, size_t, int *);
236
237 /* Initialize status to return if no more functions are found. */
238 enum nss_status status = NSS_STATUS_NOTFOUND;
239
240 /* Run through available functions, starting with the same function last
241 run. We will repeat each function as long as it succeeds, and then go
242 on to the next service action. */
243 int no_more = datap->nip == NULL;
244 if (! no_more)
245 {
246#ifdef USE_NSCD
247 /* This bogus function pointer is a special marker left by
248 __nscd_setnetgrent to tell us to use the data it left
249 before considering any modules. */
250 if (datap->nip == (nss_action_list) -1l)
251 fct = nscd_getnetgrent;
252 else
253#endif
254 {
255 fct = __nss_lookup_function (datap->nip, "getnetgrent_r");
256 no_more = fct == NULL;
257 }
258
259 while (! no_more)
260 {
261 status = DL_CALL_FCT (*fct, (datap, buffer, buflen, &errno));
262
263 if (status == NSS_STATUS_RETURN
264 /* The service returned a NOTFOUND, but there are more groups that
265 we need to resolve before we give up. */
266 || (status == NSS_STATUS_NOTFOUND && datap->needed_groups != NULL))
267 {
268 /* This was the last one for this group. Look at next group
269 if available. */
270 int found = 0;
271 while (datap->needed_groups != NULL && ! found)
272 {
273 struct name_list *tmp = datap->needed_groups;
274 datap->needed_groups = datap->needed_groups->next;
275 tmp->next = datap->known_groups;
276 datap->known_groups = tmp;
277
278 found = __internal_setnetgrent_reuse (group: datap->known_groups->name,
279 datap, errnop);
280 }
281
282 if (found && datap->nip != NULL)
283 {
284 fct = __nss_lookup_function (datap->nip, "getnetgrent_r");
285 if (fct != NULL)
286 continue;
287 }
288 }
289 else if (status == NSS_STATUS_SUCCESS && datap->type == group_val)
290 {
291 /* The last entry was a name of another netgroup. */
292 struct name_list *namep;
293
294 /* Ignore if we've seen the name before. */
295 for (namep = datap->known_groups; namep != NULL;
296 namep = namep->next)
297 if (strcmp (datap->val.group, namep->name) == 0)
298 break;
299 if (namep == NULL)
300 for (namep = datap->needed_groups; namep != NULL;
301 namep = namep->next)
302 if (strcmp (datap->val.group, namep->name) == 0)
303 break;
304 if (namep != NULL)
305 /* Really ignore. */
306 continue;
307
308 size_t group_len = strlen (datap->val.group) + 1;
309 namep = (struct name_list *) malloc (size: sizeof (struct name_list)
310 + group_len);
311 if (namep == NULL)
312 /* We are out of memory. */
313 status = NSS_STATUS_RETURN;
314 else
315 {
316 namep->next = datap->needed_groups;
317 memcpy (namep->name, datap->val.group, group_len);
318 datap->needed_groups = namep;
319 /* And get the next entry. */
320 continue;
321 }
322 }
323 break;
324 }
325 }
326
327 if (status == NSS_STATUS_SUCCESS)
328 {
329 *hostp = (char *) datap->val.triple.host;
330 *userp = (char *) datap->val.triple.user;
331 *domainp = (char *) datap->val.triple.domain;
332 }
333
334 return status == NSS_STATUS_SUCCESS ? 1 : 0;
335}
336libc_hidden_def (__internal_getnetgrent_r)
337
338/* The real entry point. */
339int
340__getnetgrent_r (char **hostp, char **userp, char **domainp,
341 char *buffer, size_t buflen)
342{
343 enum nss_status status;
344
345 __libc_lock_lock (lock);
346
347 status = __internal_getnetgrent_r (hostp, userp, domainp, datap: &dataset,
348 buffer, buflen, errnop: &errno);
349
350 __libc_lock_unlock (lock);
351
352 return status;
353}
354weak_alias (__getnetgrent_r, getnetgrent_r)
355
356/* Test whether given (host,user,domain) triple is in NETGROUP. */
357int
358innetgr (const char *netgroup, const char *host, const char *user,
359 const char *domain)
360{
361#ifdef USE_NSCD
362 if (__nss_not_use_nscd_netgroup > 0
363 && ++__nss_not_use_nscd_netgroup > NSS_NSCD_RETRY)
364 __nss_not_use_nscd_netgroup = 0;
365
366 if (!__nss_not_use_nscd_netgroup
367 && !__nss_database_custom[NSS_DBSIDX_netgroup])
368 {
369 int result = __nscd_innetgr (netgroup, host, user, domain);
370 if (result >= 0)
371 return result;
372 }
373#endif
374
375 union
376 {
377 enum nss_status (*f) (const char *, struct __netgrent *);
378 void *ptr;
379 } setfct;
380 void (*endfct) (struct __netgrent *);
381 int (*getfct) (struct __netgrent *, char *, size_t, int *);
382 struct __netgrent entry;
383 int result = 0;
384 const char *current_group = netgroup;
385
386 memset (&entry, '\0', sizeof (entry));
387
388 /* Walk through the services until we found an answer or we shall
389 not work further. We can do some optimization here. Since all
390 services must provide the `setnetgrent' function we can do all
391 the work during one walk through the service list. */
392 while (1)
393 {
394 int no_more = setup (fctp: &setfct.ptr, nipp: &entry.nip);
395 while (! no_more)
396 {
397 assert (entry.data == NULL);
398
399 /* Open netgroup. */
400 enum nss_status status = DL_CALL_FCT (*setfct.f,
401 (current_group, &entry));
402
403 if (status == NSS_STATUS_SUCCESS
404 && (getfct = __nss_lookup_function (entry.nip, "getnetgrent_r"))
405 != NULL)
406 {
407 char buffer[1024];
408
409 while (DL_CALL_FCT (*getfct,
410 (&entry, buffer, sizeof buffer, &errno))
411 == NSS_STATUS_SUCCESS)
412 {
413 if (entry.type == group_val)
414 {
415 /* Make sure we haven't seen the name before. */
416 struct name_list *namep;
417
418 for (namep = entry.known_groups; namep != NULL;
419 namep = namep->next)
420 if (strcmp (entry.val.group, namep->name) == 0)
421 break;
422 if (namep == NULL)
423 for (namep = entry.needed_groups; namep != NULL;
424 namep = namep->next)
425 if (strcmp (entry.val.group, namep->name) == 0)
426 break;
427 if (namep == NULL
428 && strcmp (netgroup, entry.val.group) != 0)
429 {
430 size_t group_len = strlen (entry.val.group) + 1;
431 namep =
432 (struct name_list *) malloc (size: sizeof (*namep)
433 + group_len);
434 if (namep == NULL)
435 {
436 /* Out of memory, simply return. */
437 result = -1;
438 break;
439 }
440
441 namep->next = entry.needed_groups;
442 memcpy (namep->name, entry.val.group, group_len);
443 entry.needed_groups = namep;
444 }
445 }
446 else
447 {
448 if ((entry.val.triple.host == NULL || host == NULL
449 || __strcasecmp (entry.val.triple.host, host) == 0)
450 && (entry.val.triple.user == NULL || user == NULL
451 || strcmp (entry.val.triple.user, user) == 0)
452 && (entry.val.triple.domain == NULL || domain == NULL
453 || __strcasecmp (entry.val.triple.domain,
454 domain) == 0))
455 {
456 result = 1;
457 break;
458 }
459 }
460 }
461
462 /* If we found one service which does know the given
463 netgroup we don't try further. */
464 status = NSS_STATUS_RETURN;
465 }
466
467 /* Free all resources of the service. */
468 endfct = __nss_lookup_function (entry.nip, "endnetgrent");
469 if (endfct != NULL)
470 DL_CALL_FCT (*endfct, (&entry));
471
472 if (result != 0)
473 break;
474
475 /* Look for the next service. */
476 no_more = __nss_next2 (&entry.nip, "setnetgrent", NULL,
477 &setfct.ptr, status, 0);
478 }
479
480 if (result == 0 && entry.needed_groups != NULL)
481 {
482 struct name_list *tmp = entry.needed_groups;
483 entry.needed_groups = tmp->next;
484 tmp->next = entry.known_groups;
485 entry.known_groups = tmp;
486 current_group = tmp->name;
487 continue;
488 }
489
490 /* No way out. */
491 break;
492 }
493
494 /* Free the memory. */
495 free_memory (data: &entry);
496
497 return result == 1;
498}
499libc_hidden_def (innetgr)
500

source code of glibc/inet/getnetgrent_r.c