1 /* Copyright (C) 1998-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 <ctype.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <grp.h>
22 #include <nss.h>
23 #include <stdio_ext.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <sys/param.h>
27 #include <nsswitch.h>
28 #include <libc-lock.h>
29 #include <kernel-features.h>
30 #include <scratch_buffer.h>
31 #include <nss_files.h>
32 
33 NSS_DECLARE_MODULE_FUNCTIONS (compat)
34 
35 static nss_action_list ni;
36 static enum nss_status (*initgroups_dyn_impl) (const char *, gid_t,
37 					       long int *, long int *,
38 					       gid_t **, long int, int *);
39 static enum nss_status (*getgrnam_r_impl) (const char *name,
40 					   struct group * grp, char *buffer,
41 					   size_t buflen, int *errnop);
42 static enum nss_status (*getgrgid_r_impl) (gid_t gid, struct group * grp,
43 					   char *buffer, size_t buflen,
44 					   int *errnop);
45 static enum nss_status (*setgrent_impl) (int stayopen);
46 static enum nss_status (*getgrent_r_impl) (struct group * grp, char *buffer,
47 					   size_t buflen, int *errnop);
48 static enum nss_status (*endgrent_impl) (void);
49 
50 /* Protect global state against multiple changers.  */
51 __libc_lock_define_initialized (static, lock)
52 
53 
54 /* Get the declaration of the parser function.  */
55 #define ENTNAME grent
56 #define STRUCTURE group
57 #define EXTERN_PARSER
58 #include <nss/nss_files/files-parse.c>
59 
60 /* Structure for remembering -group members ... */
61 #define BLACKLIST_INITIAL_SIZE 512
62 #define BLACKLIST_INCREMENT 256
63 struct blacklist_t
64 {
65   char *data;
66   int current;
67   int size;
68 };
69 
70 struct ent_t
71 {
72   bool files;
73   bool need_endgrent;
74   bool skip_initgroups_dyn;
75   FILE *stream;
76   struct blacklist_t blacklist;
77 };
78 typedef struct ent_t ent_t;
79 
80 /* Prototypes for local functions.  */
81 static void blacklist_store_name (const char *, ent_t *);
82 static bool in_blacklist (const char *, int, ent_t *);
83 
84 /* Initialize the NSS interface/functions. The calling function must
85    hold the lock.  */
86 static void
init_nss_interface(void)87 init_nss_interface (void)
88 {
89   __libc_lock_lock (lock);
90 
91   /* Retest.  */
92   if (ni == NULL
93       && __nss_database_get (nss_database_group_compat, &ni))
94     {
95       initgroups_dyn_impl = __nss_lookup_function (ni, "initgroups_dyn");
96       getgrnam_r_impl = __nss_lookup_function (ni, "getgrnam_r");
97       getgrgid_r_impl = __nss_lookup_function (ni, "getgrgid_r");
98       setgrent_impl = __nss_lookup_function (ni, "setgrent");
99       getgrent_r_impl = __nss_lookup_function (ni, "getgrent_r");
100       endgrent_impl = __nss_lookup_function (ni, "endgrent");
101     }
102 
103   __libc_lock_unlock (lock);
104 }
105 
106 static enum nss_status
internal_setgrent(ent_t * ent)107 internal_setgrent (ent_t *ent)
108 {
109   enum nss_status status = NSS_STATUS_SUCCESS;
110 
111   ent->files = true;
112 
113   if (ni == NULL)
114     init_nss_interface ();
115 
116   if (ent->blacklist.data != NULL)
117     {
118       ent->blacklist.current = 1;
119       ent->blacklist.data[0] = '|';
120       ent->blacklist.data[1] = '\0';
121     }
122   else
123     ent->blacklist.current = 0;
124 
125   ent->stream = __nss_files_fopen ("/etc/group");
126 
127   if (ent->stream == NULL)
128     status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
129 
130   return status;
131 }
132 
133 
134 static enum nss_status __attribute_warn_unused_result__
internal_endgrent(ent_t * ent)135 internal_endgrent (ent_t *ent)
136 {
137   if (ent->stream != NULL)
138     {
139       fclose (ent->stream);
140       ent->stream = NULL;
141     }
142 
143   if (ent->blacklist.data != NULL)
144     {
145       ent->blacklist.current = 1;
146       ent->blacklist.data[0] = '|';
147       ent->blacklist.data[1] = '\0';
148     }
149   else
150     ent->blacklist.current = 0;
151 
152   if (ent->need_endgrent && endgrent_impl != NULL)
153     endgrent_impl ();
154 
155   return NSS_STATUS_SUCCESS;
156 }
157 
158 /* Like internal_endgrent, but preserve errno in all cases.  */
159 static void
internal_endgrent_noerror(ent_t * ent)160 internal_endgrent_noerror (ent_t *ent)
161 {
162   int saved_errno = errno;
163   enum nss_status unused __attribute__ ((unused)) = internal_endgrent (ent);
164   __set_errno (saved_errno);
165 }
166 
167 /* Add new group record.  */
168 static void
add_group(long int * start,long int * size,gid_t ** groupsp,long int limit,gid_t gid)169 add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
170 	   gid_t gid)
171 {
172   gid_t *groups = *groupsp;
173 
174   /* Matches user.  Insert this group.  */
175   if (__glibc_unlikely (*start == *size))
176     {
177       /* Need a bigger buffer.  */
178       gid_t *newgroups;
179       long int newsize;
180 
181       if (limit > 0 && *size == limit)
182 	/* We reached the maximum.  */
183 	return;
184 
185       if (limit <= 0)
186 	newsize = 2 * *size;
187       else
188 	newsize = MIN (limit, 2 * *size);
189 
190       newgroups = realloc (groups, newsize * sizeof (*groups));
191       if (newgroups == NULL)
192 	return;
193       *groupsp = groups = newgroups;
194       *size = newsize;
195     }
196 
197   groups[*start] = gid;
198   *start += 1;
199 }
200 
201 /* This function checks, if the user is a member of this group and if
202    yes, add the group id to the list.  Return nonzero is we couldn't
203    handle the group because the user is not in the member list.  */
204 static int
check_and_add_group(const char * user,gid_t group,long int * start,long int * size,gid_t ** groupsp,long int limit,struct group * grp)205 check_and_add_group (const char *user, gid_t group, long int *start,
206 		     long int *size, gid_t **groupsp, long int limit,
207 		     struct group *grp)
208 {
209   char **member;
210 
211   /* Don't add main group to list of groups.  */
212   if (grp->gr_gid == group)
213     return 0;
214 
215   for (member = grp->gr_mem; *member != NULL; ++member)
216     if (strcmp (*member, user) == 0)
217       {
218 	add_group (start, size, groupsp, limit, grp->gr_gid);
219 	return 0;
220       }
221 
222   return 1;
223 }
224 
225 /* Get the next group from NSS  (+ entry). If the NSS module supports
226    initgroups_dyn, get all entries at once.  */
227 static enum nss_status
getgrent_next_nss(ent_t * ent,char * buffer,size_t buflen,const char * user,gid_t group,long int * start,long int * size,gid_t ** groupsp,long int limit,int * errnop)228 getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
229 		   gid_t group, long int *start, long int *size,
230 		   gid_t **groupsp, long int limit, int *errnop)
231 {
232   enum nss_status status;
233   struct group grpbuf;
234 
235   /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
236      If this function is not supported, step through the whole group
237      database with getgrent_r.  */
238   if (! ent->skip_initgroups_dyn)
239     {
240       long int mystart = 0;
241       long int mysize = limit <= 0 ? *size : limit;
242       gid_t *mygroups = malloc (mysize * sizeof (gid_t));
243 
244       if (mygroups == NULL)
245 	return NSS_STATUS_TRYAGAIN;
246 
247       /* For every gid in the list we get from the NSS module,
248 	 get the whole group entry. We need to do this, since we
249 	 need the group name to check if it is in the blacklist.
250 	 In worst case, this is as twice as slow as stepping with
251 	 getgrent_r through the whole group database. But for large
252 	 group databases this is faster, since the user can only be
253 	 in a limited number of groups.  */
254       if (initgroups_dyn_impl (user, group, &mystart, &mysize, &mygroups,
255 			       limit, errnop) == NSS_STATUS_SUCCESS)
256 	{
257 	  status = NSS_STATUS_NOTFOUND;
258 
259 	  /* If there is no blacklist we can trust the underlying
260 	     initgroups implementation.  */
261 	  if (ent->blacklist.current <= 1)
262 	    for (int i = 0; i < mystart; i++)
263 	      add_group (start, size, groupsp, limit, mygroups[i]);
264 	  else
265 	    {
266 	      /* A temporary buffer. We use the normal buffer, until we find
267 		 an entry, for which this buffer is to small.  In this case, we
268 		 overwrite the pointer with one to a bigger buffer.  */
269 	      char *tmpbuf = buffer;
270 	      size_t tmplen = buflen;
271 
272 	      for (int i = 0; i < mystart; i++)
273 		{
274 		  while ((status = getgrgid_r_impl (mygroups[i], &grpbuf,
275 						    tmpbuf, tmplen, errnop))
276 			 == NSS_STATUS_TRYAGAIN
277 			 && *errnop == ERANGE)
278                     {
279 		      /* Check for overflow. */
280 		      if (__glibc_unlikely (tmplen * 2 < tmplen))
281 			{
282 			  __set_errno (ENOMEM);
283 			  status = NSS_STATUS_TRYAGAIN;
284 			  goto done;
285 			}
286 		      /* Increase the size.  Make sure that we retry
287 			 with a reasonable size.  */
288 		      tmplen *= 2;
289 		      if (tmplen < 1024)
290 			tmplen = 1024;
291 		      if (tmpbuf != buffer)
292 			free (tmpbuf);
293 		      tmpbuf = malloc (tmplen);
294 		      if (__glibc_unlikely (tmpbuf == NULL))
295 			{
296 			  status = NSS_STATUS_TRYAGAIN;
297 			  goto done;
298 			}
299                     }
300 
301 		  if (__builtin_expect  (status != NSS_STATUS_NOTFOUND, 1))
302 		    {
303 		      if (__builtin_expect  (status != NSS_STATUS_SUCCESS, 0))
304 		        goto done;
305 
306 		      if (!in_blacklist (grpbuf.gr_name,
307 					 strlen (grpbuf.gr_name), ent)
308 			  && check_and_add_group (user, group, start, size,
309 						  groupsp, limit, &grpbuf))
310 			{
311 			  if (setgrent_impl != NULL)
312 			    {
313 			      setgrent_impl (1);
314 			      ent->need_endgrent = true;
315 			    }
316 			  ent->skip_initgroups_dyn = true;
317 
318 			  goto iter;
319 			}
320 		    }
321 		}
322 
323 	      status = NSS_STATUS_NOTFOUND;
324 
325  done:
326 	      if (tmpbuf != buffer)
327 	        free (tmpbuf);
328 	    }
329 
330 	  free (mygroups);
331 
332 	  return status;
333 	}
334 
335       free (mygroups);
336     }
337 
338   /* If we come here, the NSS module does not support initgroups_dyn
339      or we were confronted with a split group.  In these cases we have
340      to step through the whole list ourself.  */
341  iter:
342   do
343     {
344       if ((status = getgrent_r_impl (&grpbuf, buffer, buflen, errnop))
345 	  != NSS_STATUS_SUCCESS)
346 	break;
347     }
348   while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
349 
350   if (status == NSS_STATUS_SUCCESS)
351     check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
352 
353   return status;
354 }
355 
356 static enum nss_status
internal_getgrent_r(ent_t * ent,char * buffer,size_t buflen,const char * user,gid_t group,long int * start,long int * size,gid_t ** groupsp,long int limit,int * errnop)357 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
358 		     gid_t group, long int *start, long int *size,
359 		     gid_t **groupsp, long int limit, int *errnop)
360 {
361   struct parser_data *data = (void *) buffer;
362   struct group grpbuf;
363 
364   if (!ent->files)
365     return getgrent_next_nss (ent, buffer, buflen, user, group,
366 			      start, size, groupsp, limit, errnop);
367 
368   while (1)
369     {
370       fpos_t pos;
371       int parse_res = 0;
372       char *p;
373 
374       do
375 	{
376 	  /* We need at least 3 characters for one line.  */
377 	  if (__glibc_unlikely (buflen < 3))
378 	    {
379 	    erange:
380 	      *errnop = ERANGE;
381 	      return NSS_STATUS_TRYAGAIN;
382 	    }
383 
384 	  fgetpos (ent->stream, &pos);
385 	  buffer[buflen - 1] = '\xff';
386 	  p = fgets_unlocked (buffer, buflen, ent->stream);
387 	  if (p == NULL && feof_unlocked (ent->stream))
388 	    return NSS_STATUS_NOTFOUND;
389 
390 	  if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
391 	    {
392 	    erange_reset:
393 	      fsetpos (ent->stream, &pos);
394 	      goto erange;
395 	    }
396 
397 	  /* Terminate the line for any case.  */
398 	  buffer[buflen - 1] = '\0';
399 
400 	  /* Skip leading blanks.  */
401 	  while (isspace (*p))
402 	    ++p;
403 	}
404       while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */
405 	     /* Parse the line.  If it is invalid, loop to
406 		get the next line of the file to parse.  */
407 	     || !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
408 						      errnop)));
409 
410       if (__glibc_unlikely (parse_res == -1))
411 	/* The parser ran out of space.  */
412 	goto erange_reset;
413 
414       if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
415 	/* This is a real entry.  */
416 	break;
417 
418       /* -group */
419       if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
420 	  && grpbuf.gr_name[1] != '@')
421 	{
422 	  blacklist_store_name (&grpbuf.gr_name[1], ent);
423 	  continue;
424 	}
425 
426       /* +group */
427       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
428 	  && grpbuf.gr_name[1] != '@')
429 	{
430 	  if (in_blacklist (&grpbuf.gr_name[1],
431 			    strlen (&grpbuf.gr_name[1]), ent))
432 	    continue;
433 	  /* Store the group in the blacklist for the "+" at the end of
434 	     /etc/group */
435 	  blacklist_store_name (&grpbuf.gr_name[1], ent);
436 	  if (getgrnam_r_impl == NULL)
437 	    return NSS_STATUS_UNAVAIL;
438 	  else if (getgrnam_r_impl (&grpbuf.gr_name[1], &grpbuf, buffer,
439 				    buflen, errnop) != NSS_STATUS_SUCCESS)
440 	    continue;
441 
442 	  check_and_add_group (user, group, start, size, groupsp,
443 			       limit, &grpbuf);
444 
445 	  return NSS_STATUS_SUCCESS;
446 	}
447 
448       /* +:... */
449       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
450 	{
451 	  /* If the selected module does not support getgrent_r or
452 	     initgroups_dyn, abort. We cannot find the needed group
453 	     entries.  */
454 	  if (initgroups_dyn_impl == NULL || getgrgid_r_impl == NULL)
455 	    {
456 	      if (setgrent_impl != NULL)
457 		{
458 		  setgrent_impl (1);
459 		  ent->need_endgrent = true;
460 		}
461 	      ent->skip_initgroups_dyn = true;
462 
463 	      if (getgrent_r_impl == NULL)
464 		return NSS_STATUS_UNAVAIL;
465 	    }
466 
467 	  ent->files = false;
468 
469 	  return getgrent_next_nss (ent, buffer, buflen, user, group,
470 				    start, size, groupsp, limit, errnop);
471 	}
472     }
473 
474   check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
475 
476   return NSS_STATUS_SUCCESS;
477 }
478 
479 
480 enum nss_status
_nss_compat_initgroups_dyn(const char * user,gid_t group,long int * start,long int * size,gid_t ** groupsp,long int limit,int * errnop)481 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
482 			    long int *size, gid_t **groupsp, long int limit,
483 			    int *errnop)
484 {
485   enum nss_status status;
486   ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
487 
488   status = internal_setgrent (&intern);
489   if (status != NSS_STATUS_SUCCESS)
490     return status;
491 
492   struct scratch_buffer tmpbuf;
493   scratch_buffer_init (&tmpbuf);
494 
495   do
496     {
497       while ((status = internal_getgrent_r (&intern, tmpbuf.data, tmpbuf.length,
498 					    user, group, start, size,
499 					    groupsp, limit, errnop))
500 	     == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
501         if (!scratch_buffer_grow (&tmpbuf))
502 	    goto done;
503     }
504   while (status == NSS_STATUS_SUCCESS);
505 
506   status = NSS_STATUS_SUCCESS;
507 
508  done:
509   scratch_buffer_free (&tmpbuf);
510 
511   internal_endgrent_noerror (&intern);
512 
513   return status;
514 }
515 
516 
517 /* Support routines for remembering -@netgroup and -user entries.
518    The names are stored in a single string with `|' as separator. */
519 static void
blacklist_store_name(const char * name,ent_t * ent)520 blacklist_store_name (const char *name, ent_t *ent)
521 {
522   int namelen = strlen (name);
523   char *tmp;
524 
525   /* First call, setup cache.  */
526   if (ent->blacklist.size == 0)
527     {
528       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
529       ent->blacklist.data = malloc (ent->blacklist.size);
530       if (ent->blacklist.data == NULL)
531 	return;
532       ent->blacklist.data[0] = '|';
533       ent->blacklist.data[1] = '\0';
534       ent->blacklist.current = 1;
535     }
536   else
537     {
538       if (in_blacklist (name, namelen, ent))
539 	return;			/* no duplicates */
540 
541       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
542 	{
543 	  ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
544 	  tmp = realloc (ent->blacklist.data, ent->blacklist.size);
545 	  if (tmp == NULL)
546 	    {
547 	      free (ent->blacklist.data);
548 	      ent->blacklist.size = 0;
549 	      return;
550 	    }
551 	  ent->blacklist.data = tmp;
552 	}
553     }
554 
555   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
556   *tmp++ = '|';
557   *tmp = '\0';
558   ent->blacklist.current += namelen + 1;
559 
560   return;
561 }
562 
563 /* Return whether ent->blacklist contains name.  */
564 static bool
in_blacklist(const char * name,int namelen,ent_t * ent)565 in_blacklist (const char *name, int namelen, ent_t *ent)
566 {
567   char buf[namelen + 3];
568   char *cp;
569 
570   if (ent->blacklist.data == NULL)
571     return false;
572 
573   buf[0] = '|';
574   cp = stpcpy (&buf[1], name);
575   *cp++ = '|';
576   *cp = '\0';
577   return strstr (ent->blacklist.data, buf) != NULL;
578 }
579