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 <ctype.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <grp.h>
22 #include <nss.h>
23 #include <nsswitch.h>
24 #include <stdio_ext.h>
25 #include <string.h>
26 #include <libc-lock.h>
27 #include <kernel-features.h>
28 #include <nss_files.h>
29 
30 NSS_DECLARE_MODULE_FUNCTIONS (compat)
31 
32 static nss_action_list ni;
33 static enum nss_status (*setgrent_impl) (int stayopen);
34 static enum nss_status (*getgrnam_r_impl) (const char *name,
35 					   struct group * grp, char *buffer,
36 					   size_t buflen, int *errnop);
37 static enum nss_status (*getgrgid_r_impl) (gid_t gid, struct group * grp,
38 					   char *buffer, size_t buflen,
39 					   int *errnop);
40 static enum nss_status (*getgrent_r_impl) (struct group * grp, char *buffer,
41 					   size_t buflen, int *errnop);
42 static enum nss_status (*endgrent_impl) (void);
43 
44 /* Get the declaration of the parser function.  */
45 #define ENTNAME grent
46 #define STRUCTURE group
47 #define EXTERN_PARSER
48 #include <nss/nss_files/files-parse.c>
49 
50 /* Structure for remembering -group members ... */
51 #define BLACKLIST_INITIAL_SIZE 512
52 #define BLACKLIST_INCREMENT 256
53 struct blacklist_t
54 {
55   char *data;
56   int current;
57   int size;
58 };
59 
60 struct ent_t
61 {
62   bool files;
63   enum nss_status setent_status;
64   FILE *stream;
65   struct blacklist_t blacklist;
66 };
67 typedef struct ent_t ent_t;
68 
69 static ent_t ext_ent = { true, NSS_STATUS_SUCCESS, NULL, { NULL, 0, 0 }};
70 
71 /* Protect global state against multiple changers.  */
72 __libc_lock_define_initialized (static, lock)
73 
74 /* Prototypes for local functions.  */
75 static void blacklist_store_name (const char *, ent_t *);
76 static bool in_blacklist (const char *, int, ent_t *);
77 
78 /* Initialize the NSS interface/functions. The calling function must
79    hold the lock.  */
80 static void
init_nss_interface(void)81 init_nss_interface (void)
82 {
83   if (__nss_database_get (nss_database_group_compat, &ni))
84     {
85       setgrent_impl = __nss_lookup_function (ni, "setgrent");
86       getgrnam_r_impl = __nss_lookup_function (ni, "getgrnam_r");
87       getgrgid_r_impl = __nss_lookup_function (ni, "getgrgid_r");
88       getgrent_r_impl = __nss_lookup_function (ni, "getgrent_r");
89       endgrent_impl = __nss_lookup_function (ni, "endgrent");
90     }
91 }
92 
93 static enum nss_status
internal_setgrent(ent_t * ent,int stayopen,int needent)94 internal_setgrent (ent_t *ent, int stayopen, int needent)
95 {
96   enum nss_status status = NSS_STATUS_SUCCESS;
97 
98   ent->files = true;
99 
100   if (ent->blacklist.data != NULL)
101     {
102       ent->blacklist.current = 1;
103       ent->blacklist.data[0] = '|';
104       ent->blacklist.data[1] = '\0';
105     }
106   else
107     ent->blacklist.current = 0;
108 
109   if (ent->stream == NULL)
110     {
111       ent->stream = __nss_files_fopen ("/etc/group");
112 
113       if (ent->stream == NULL)
114 	status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
115     }
116   else
117     rewind (ent->stream);
118 
119   if (needent && status == NSS_STATUS_SUCCESS && setgrent_impl)
120     ent->setent_status = setgrent_impl (stayopen);
121 
122   return status;
123 }
124 
125 
126 enum nss_status
_nss_compat_setgrent(int stayopen)127 _nss_compat_setgrent (int stayopen)
128 {
129   enum nss_status result;
130 
131   __libc_lock_lock (lock);
132 
133   if (ni == NULL)
134     init_nss_interface ();
135 
136   result = internal_setgrent (&ext_ent, stayopen, 1);
137 
138   __libc_lock_unlock (lock);
139 
140   return result;
141 }
142 
143 
144 static enum nss_status __attribute_warn_unused_result__
internal_endgrent(ent_t * ent)145 internal_endgrent (ent_t *ent)
146 {
147   if (ent->stream != NULL)
148     {
149       fclose (ent->stream);
150       ent->stream = NULL;
151     }
152 
153   if (ent->blacklist.data != NULL)
154     {
155       ent->blacklist.current = 1;
156       ent->blacklist.data[0] = '|';
157       ent->blacklist.data[1] = '\0';
158     }
159   else
160     ent->blacklist.current = 0;
161 
162   return NSS_STATUS_SUCCESS;
163 }
164 
165 /* Like internal_endgrent, but preserve errno in all cases.  */
166 static void
internal_endgrent_noerror(ent_t * ent)167 internal_endgrent_noerror (ent_t *ent)
168 {
169   int saved_errno = errno;
170   enum nss_status unused __attribute__ ((unused)) = internal_endgrent (ent);
171   __set_errno (saved_errno);
172 }
173 
174 enum nss_status
_nss_compat_endgrent(void)175 _nss_compat_endgrent (void)
176 {
177   enum nss_status result;
178 
179   __libc_lock_lock (lock);
180 
181   if (endgrent_impl)
182     endgrent_impl ();
183 
184   result = internal_endgrent (&ext_ent);
185 
186   __libc_lock_unlock (lock);
187 
188   return result;
189 }
190 
191 /* get the next group from NSS  (+ entry) */
192 static enum nss_status
getgrent_next_nss(struct group * result,ent_t * ent,char * buffer,size_t buflen,int * errnop)193 getgrent_next_nss (struct group *result, ent_t *ent, char *buffer,
194 		   size_t buflen, int *errnop)
195 {
196   if (!getgrent_r_impl)
197     return NSS_STATUS_UNAVAIL;
198 
199   /* If the setgrent call failed, say so.  */
200   if (ent->setent_status != NSS_STATUS_SUCCESS)
201     return ent->setent_status;
202 
203   do
204     {
205       enum nss_status status;
206 
207       if ((status = getgrent_r_impl (result, buffer, buflen, errnop))
208 	  != NSS_STATUS_SUCCESS)
209 	return status;
210     }
211   while (in_blacklist (result->gr_name, strlen (result->gr_name), ent));
212 
213   return NSS_STATUS_SUCCESS;
214 }
215 
216 /* This function handle the +group entrys in /etc/group */
217 static enum nss_status
getgrnam_plusgroup(const char * name,struct group * result,ent_t * ent,char * buffer,size_t buflen,int * errnop)218 getgrnam_plusgroup (const char *name, struct group *result, ent_t *ent,
219 		    char *buffer, size_t buflen, int *errnop)
220 {
221   if (!getgrnam_r_impl)
222     return NSS_STATUS_UNAVAIL;
223 
224   enum nss_status status = getgrnam_r_impl (name, result, buffer, buflen,
225 					    errnop);
226   if (status != NSS_STATUS_SUCCESS)
227     return status;
228 
229   if (in_blacklist (result->gr_name, strlen (result->gr_name), ent))
230     return NSS_STATUS_NOTFOUND;
231 
232   /* We found the entry.  */
233   return NSS_STATUS_SUCCESS;
234 }
235 
236 static enum nss_status
getgrent_next_file(struct group * result,ent_t * ent,char * buffer,size_t buflen,int * errnop)237 getgrent_next_file (struct group *result, ent_t *ent,
238 		    char *buffer, size_t buflen, int *errnop)
239 {
240   struct parser_data *data = (void *) buffer;
241   while (1)
242     {
243       fpos_t pos;
244       int parse_res = 0;
245       char *p;
246 
247       do
248 	{
249 	  /* We need at least 3 characters for one line.  */
250 	  if (__glibc_unlikely (buflen < 3))
251 	    {
252 	    erange:
253 	      *errnop = ERANGE;
254 	      return NSS_STATUS_TRYAGAIN;
255 	    }
256 
257 	  fgetpos (ent->stream, &pos);
258 	  buffer[buflen - 1] = '\xff';
259 	  p = fgets_unlocked (buffer, buflen, ent->stream);
260 	  if (p == NULL && feof_unlocked (ent->stream))
261 	    return NSS_STATUS_NOTFOUND;
262 
263 	  if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
264 	    {
265 	    erange_reset:
266 	      fsetpos (ent->stream, &pos);
267 	      goto erange;
268 	    }
269 
270 	  /* Terminate the line for any case.  */
271 	  buffer[buflen - 1] = '\0';
272 
273 	  /* Skip leading blanks.  */
274 	  while (isspace (*p))
275 	    ++p;
276 	}
277       while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */
278 	     /* Parse the line.  If it is invalid, loop to
279 	        get the next line of the file to parse.  */
280 	     || !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
281 						      errnop)));
282 
283       if (__glibc_unlikely (parse_res == -1))
284 	/* The parser ran out of space.  */
285 	goto erange_reset;
286 
287       if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
288 	/* This is a real entry.  */
289 	break;
290 
291       /* -group */
292       if (result->gr_name[0] == '-' && result->gr_name[1] != '\0'
293 	  && result->gr_name[1] != '@')
294 	{
295 	  blacklist_store_name (&result->gr_name[1], ent);
296 	  continue;
297 	}
298 
299       /* +group */
300       if (result->gr_name[0] == '+' && result->gr_name[1] != '\0'
301 	  && result->gr_name[1] != '@')
302 	{
303 	  size_t len = strlen (result->gr_name);
304 	  char buf[len];
305 	  enum nss_status status;
306 
307 	  /* Store the group in the blacklist for the "+" at the end of
308 	     /etc/group */
309 	  memcpy (buf, &result->gr_name[1], len);
310 	  status = getgrnam_plusgroup (&result->gr_name[1], result, ent,
311 				       buffer, buflen, errnop);
312 	  blacklist_store_name (buf, ent);
313 	  if (status == NSS_STATUS_SUCCESS)	/* We found the entry. */
314 	    break;
315 	  else if (status == NSS_STATUS_RETURN /* We couldn't parse the entry*/
316 		   || status == NSS_STATUS_NOTFOUND)	/* No group in NIS */
317 	    continue;
318 	  else
319 	    {
320 	      if (status == NSS_STATUS_TRYAGAIN)
321 		/* The parser ran out of space.  */
322 		goto erange_reset;
323 
324 	      return status;
325 	    }
326 	}
327 
328       /* +:... */
329       if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
330 	{
331 	  ent->files = false;
332 
333 	  return getgrent_next_nss (result, ent, buffer, buflen, errnop);
334 	}
335     }
336 
337   return NSS_STATUS_SUCCESS;
338 }
339 
340 
341 enum nss_status
_nss_compat_getgrent_r(struct group * grp,char * buffer,size_t buflen,int * errnop)342 _nss_compat_getgrent_r (struct group *grp, char *buffer, size_t buflen,
343 			int *errnop)
344 {
345   enum nss_status result = NSS_STATUS_SUCCESS;
346 
347   __libc_lock_lock (lock);
348 
349   /* Be prepared that the setgrent function was not called before.  */
350   if (ni == NULL)
351     init_nss_interface ();
352 
353   if (ext_ent.stream == NULL)
354     result = internal_setgrent (&ext_ent, 1, 1);
355 
356   if (result == NSS_STATUS_SUCCESS)
357     {
358       if (ext_ent.files)
359 	result = getgrent_next_file (grp, &ext_ent, buffer, buflen, errnop);
360       else
361 	result = getgrent_next_nss (grp, &ext_ent, buffer, buflen, errnop);
362     }
363   __libc_lock_unlock (lock);
364 
365   return result;
366 }
367 
368 /* Searches in /etc/group and the NIS/NIS+ map for a special group */
369 static enum nss_status
internal_getgrnam_r(const char * name,struct group * result,ent_t * ent,char * buffer,size_t buflen,int * errnop)370 internal_getgrnam_r (const char *name, struct group *result, ent_t *ent,
371 		     char *buffer, size_t buflen, int *errnop)
372 {
373   struct parser_data *data = (void *) buffer;
374   while (1)
375     {
376       fpos_t pos;
377       int parse_res = 0;
378       char *p;
379 
380       do
381 	{
382 	  /* We need at least 3 characters for one line.  */
383 	  if (__glibc_unlikely (buflen < 3))
384 	    {
385 	    erange:
386 	      *errnop = ERANGE;
387 	      return NSS_STATUS_TRYAGAIN;
388 	    }
389 
390 	  fgetpos (ent->stream, &pos);
391 	  buffer[buflen - 1] = '\xff';
392 	  p = fgets_unlocked (buffer, buflen, ent->stream);
393 	  if (p == NULL && feof_unlocked (ent->stream))
394 	    return NSS_STATUS_NOTFOUND;
395 
396 	  if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
397 	    {
398 	    erange_reset:
399 	      fsetpos (ent->stream, &pos);
400 	      goto erange;
401 	    }
402 
403 	  /* Terminate the line for any case.  */
404 	  buffer[buflen - 1] = '\0';
405 
406 	  /* Skip leading blanks.  */
407 	  while (isspace (*p))
408 	    ++p;
409 	}
410       while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */
411 	     /* Parse the line.  If it is invalid, loop to
412 	        get the next line of the file to parse.  */
413 	     || !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
414 						      errnop)));
415 
416       if (__glibc_unlikely (parse_res == -1))
417 	/* The parser ran out of space.  */
418 	goto erange_reset;
419 
420       /* This is a real entry.  */
421       if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
422 	{
423 	  if (strcmp (result->gr_name, name) == 0)
424 	    return NSS_STATUS_SUCCESS;
425 	  else
426 	    continue;
427 	}
428 
429       /* -group */
430       if (result->gr_name[0] == '-' && result->gr_name[1] != '\0')
431 	{
432 	  if (strcmp (&result->gr_name[1], name) == 0)
433 	    return NSS_STATUS_NOTFOUND;
434 	  else
435 	    continue;
436 	}
437 
438       /* +group */
439       if (result->gr_name[0] == '+' && result->gr_name[1] != '\0')
440 	{
441 	  if (strcmp (name, &result->gr_name[1]) == 0)
442 	    {
443 	      enum nss_status status;
444 
445 	      status = getgrnam_plusgroup (name, result, ent,
446 					   buffer, buflen, errnop);
447 	      if (status == NSS_STATUS_RETURN)
448 		/* We couldn't parse the entry */
449 		continue;
450 	      else
451 		return status;
452 	    }
453 	}
454       /* +:... */
455       if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
456 	{
457 	  enum nss_status status;
458 
459 	  status = getgrnam_plusgroup (name, result, ent,
460 				       buffer, buflen, errnop);
461 	  if (status == NSS_STATUS_RETURN)
462 	    /* We couldn't parse the entry */
463 	    continue;
464 	  else
465 	    return status;
466 	}
467     }
468 
469   return NSS_STATUS_SUCCESS;
470 }
471 
472 enum nss_status
_nss_compat_getgrnam_r(const char * name,struct group * grp,char * buffer,size_t buflen,int * errnop)473 _nss_compat_getgrnam_r (const char *name, struct group *grp,
474 			char *buffer, size_t buflen, int *errnop)
475 {
476   ent_t ent = { true, NSS_STATUS_SUCCESS, NULL, { NULL, 0, 0 }};
477   enum nss_status result;
478 
479   if (name[0] == '-' || name[0] == '+')
480     return NSS_STATUS_NOTFOUND;
481 
482   __libc_lock_lock (lock);
483 
484   if (ni == NULL)
485     init_nss_interface ();
486 
487   __libc_lock_unlock (lock);
488 
489   result = internal_setgrent (&ent, 0, 0);
490 
491   if (result == NSS_STATUS_SUCCESS)
492     result = internal_getgrnam_r (name, grp, &ent, buffer, buflen, errnop);
493 
494   internal_endgrent_noerror (&ent);
495 
496   return result;
497 }
498 
499 /* Searches in /etc/group and the NIS/NIS+ map for a special group id */
500 static enum nss_status
internal_getgrgid_r(gid_t gid,struct group * result,ent_t * ent,char * buffer,size_t buflen,int * errnop)501 internal_getgrgid_r (gid_t gid, struct group *result, ent_t *ent,
502 		     char *buffer, size_t buflen, int *errnop)
503 {
504   struct parser_data *data = (void *) buffer;
505   while (1)
506     {
507       fpos_t pos;
508       int parse_res = 0;
509       char *p;
510 
511       do
512 	{
513 	  /* We need at least 3 characters for one line.  */
514 	  if (__glibc_unlikely (buflen < 3))
515 	    {
516 	    erange:
517 	      *errnop = ERANGE;
518 	      return NSS_STATUS_TRYAGAIN;
519 	    }
520 
521 	  fgetpos (ent->stream, &pos);
522 	  buffer[buflen - 1] = '\xff';
523 	  p = fgets_unlocked (buffer, buflen, ent->stream);
524 	  if (p == NULL && feof_unlocked (ent->stream))
525 	    return NSS_STATUS_NOTFOUND;
526 
527 	  if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
528 	    {
529 	    erange_reset:
530 	      fsetpos (ent->stream, &pos);
531 	      goto erange;
532 	    }
533 
534 	  /* Terminate the line for any case.  */
535 	  buffer[buflen - 1] = '\0';
536 
537 	  /* Skip leading blanks.  */
538 	  while (isspace (*p))
539 	    ++p;
540 	}
541       while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */
542 	     /* Parse the line.  If it is invalid, loop to
543 	        get the next line of the file to parse.  */
544 	     || !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
545 						      errnop)));
546 
547       if (__glibc_unlikely (parse_res == -1))
548 	/* The parser ran out of space.  */
549 	goto erange_reset;
550 
551       /* This is a real entry.  */
552       if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
553 	{
554 	  if (result->gr_gid == gid)
555 	    return NSS_STATUS_SUCCESS;
556 	  else
557 	    continue;
558 	}
559 
560       /* -group */
561       if (result->gr_name[0] == '-' && result->gr_name[1] != '\0')
562 	{
563 	  blacklist_store_name (&result->gr_name[1], ent);
564 	  continue;
565 	}
566 
567       /* +group */
568       if (result->gr_name[0] == '+' && result->gr_name[1] != '\0')
569 	{
570 	  /* Yes, no +1, see the memcpy call below.  */
571 	  size_t len = strlen (result->gr_name);
572 	  char buf[len];
573 	  enum nss_status status;
574 
575 	  /* Store the group in the blacklist for the "+" at the end of
576 	     /etc/group */
577 	  memcpy (buf, &result->gr_name[1], len);
578 	  status = getgrnam_plusgroup (&result->gr_name[1], result, ent,
579 				       buffer, buflen, errnop);
580 	  blacklist_store_name (buf, ent);
581 	  if (status == NSS_STATUS_SUCCESS && result->gr_gid == gid)
582 	    break;
583 	  else
584 	    continue;
585 	}
586       /* +:... */
587       if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
588 	{
589 	  if (!getgrgid_r_impl)
590 	    return NSS_STATUS_UNAVAIL;
591 
592 	  enum nss_status status = getgrgid_r_impl (gid, result,
593 						    buffer, buflen, errnop);
594 	  if (status == NSS_STATUS_RETURN) /* We couldn't parse the entry */
595 	    return NSS_STATUS_NOTFOUND;
596 	  else
597 	    return status;
598 	}
599     }
600 
601   return NSS_STATUS_SUCCESS;
602 }
603 
604 enum nss_status
_nss_compat_getgrgid_r(gid_t gid,struct group * grp,char * buffer,size_t buflen,int * errnop)605 _nss_compat_getgrgid_r (gid_t gid, struct group *grp,
606 			char *buffer, size_t buflen, int *errnop)
607 {
608   ent_t ent = { true, NSS_STATUS_SUCCESS, NULL, { NULL, 0, 0 }};
609   enum nss_status result;
610 
611   __libc_lock_lock (lock);
612 
613   if (ni == NULL)
614     init_nss_interface ();
615 
616   __libc_lock_unlock (lock);
617 
618   result = internal_setgrent (&ent, 0, 0);
619 
620   if (result == NSS_STATUS_SUCCESS)
621     result = internal_getgrgid_r (gid, grp, &ent, buffer, buflen, errnop);
622 
623   internal_endgrent_noerror (&ent);
624 
625   return result;
626 }
627 
628 
629 /* Support routines for remembering -@netgroup and -user entries.
630    The names are stored in a single string with `|' as separator. */
631 static void
blacklist_store_name(const char * name,ent_t * ent)632 blacklist_store_name (const char *name, ent_t *ent)
633 {
634   int namelen = strlen (name);
635   char *tmp;
636 
637   /* first call, setup cache */
638   if (ent->blacklist.size == 0)
639     {
640       ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
641       ent->blacklist.data = malloc (ent->blacklist.size);
642       if (ent->blacklist.data == NULL)
643 	return;
644       ent->blacklist.data[0] = '|';
645       ent->blacklist.data[1] = '\0';
646       ent->blacklist.current = 1;
647     }
648   else
649     {
650       if (in_blacklist (name, namelen, ent))
651 	return;			/* no duplicates */
652 
653       if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
654 	{
655 	  ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
656 	  tmp = realloc (ent->blacklist.data, ent->blacklist.size);
657 	  if (tmp == NULL)
658 	    {
659 	      free (ent->blacklist.data);
660 	      ent->blacklist.size = 0;
661 	      return;
662 	    }
663 	  ent->blacklist.data = tmp;
664 	}
665     }
666 
667   tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
668   *tmp++ = '|';
669   *tmp = '\0';
670   ent->blacklist.current += namelen + 1;
671 
672   return;
673 }
674 
675 /* Return whether ent->blacklist contains name.  */
676 static bool
in_blacklist(const char * name,int namelen,ent_t * ent)677 in_blacklist (const char *name, int namelen, ent_t *ent)
678 {
679   char buf[namelen + 3];
680   char *cp;
681 
682   if (ent->blacklist.data == NULL)
683     return false;
684 
685   buf[0] = '|';
686   cp = stpcpy (&buf[1], name);
687   *cp++ = '|';
688   *cp = '\0';
689   return strstr (ent->blacklist.data, buf) != NULL;
690 }
691