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.  */
37 static struct __netgrent dataset;
38 
39 /* Set up NIP to run through the services.  Return nonzero if there are no
40    services (left).  */
41 static int
setup(void ** fctp,nss_action_list * nipp)42 setup (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.  */
52 static void
free_memory(struct __netgrent * data)53 free_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 (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 (tmp);
67     }
68 }
69 
70 static void
endnetgrent_hook(struct __netgrent * datap)71 endnetgrent_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 
84 static int
__internal_setnetgrent_reuse(const char * group,struct __netgrent * datap,int * errnop)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 (&fct.ptr, &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 (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 
141 int
__internal_setnetgrent(const char * group,struct __netgrent * datap)142 __internal_setnetgrent (const char *group, struct __netgrent *datap)
143 {
144   /* Free list of all netgroup names from last run.  */
145   free_memory (datap);
146 
147   return __internal_setnetgrent_reuse (group, datap, &errno);
148 }
libc_hidden_def(__internal_setnetgrent)149 libc_hidden_def (__internal_setnetgrent)
150 
151 static int
152 nscd_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, &dataset);
162 #endif
163   return -1;
164 }
165 
166 int
setnetgrent(const char * group)167 setnetgrent (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, &dataset);
176 
177   __libc_lock_unlock (lock);
178 
179   return result;
180 }
181 
182 void
__internal_endnetgrent(struct __netgrent * datap)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 (datap);
188 }
libc_hidden_def(__internal_endnetgrent)189 libc_hidden_def (__internal_endnetgrent)
190 
191 
192 void
193 endnetgrent (void)
194 {
195   __libc_lock_lock (lock);
196 
197   __internal_endnetgrent (&dataset);
198 
199   __libc_lock_unlock (lock);
200 }
201 
202 #ifdef USE_NSCD
203 static const char *
get_nonempty_val(const char * in)204 get_nonempty_val (const char *in)
205 {
206   if (*in == '\0')
207     return NULL;
208   return in;
209 }
210 
211 static enum nss_status
nscd_getnetgrent(struct __netgrent * datap,char * buffer,size_t buflen,int * errnop)212 nscd_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 (datap->cursor);
220   datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
221   datap->val.triple.user = get_nonempty_val (datap->cursor);
222   datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
223   datap->val.triple.domain = get_nonempty_val (datap->cursor);
224   datap->cursor = (char *) __rawmemchr (datap->cursor, '\0') + 1;
225 
226   return NSS_STATUS_SUCCESS;
227 }
228 #endif
229 
230 int
__internal_getnetgrent_r(char ** hostp,char ** userp,char ** domainp,struct __netgrent * datap,char * buffer,size_t buflen,int * errnop)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 (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 (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 }
libc_hidden_def(__internal_getnetgrent_r)336 libc_hidden_def (__internal_getnetgrent_r)
337 
338 /* The real entry point.  */
339 int
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, &dataset,
348 				     buffer, buflen, &errno);
349 
350   __libc_lock_unlock (lock);
351 
352   return status;
353 }
weak_alias(__getnetgrent_r,getnetgrent_r)354 weak_alias (__getnetgrent_r, getnetgrent_r)
355 
356 /* Test whether given (host,user,domain) triple is in NETGROUP.  */
357 int
358 innetgr (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 (&setfct.ptr, &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 (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 (&entry);
496 
497   return result == 1;
498 }
499 libc_hidden_def (innetgr)
500