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