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
30NSS_DECLARE_MODULE_FUNCTIONS (compat)
31
32static nss_action_list ni;
33static enum nss_status (*setgrent_impl) (int stayopen);
34static enum nss_status (*getgrnam_r_impl) (const char *name,
35 struct group * grp, char *buffer,
36 size_t buflen, int *errnop);
37static enum nss_status (*getgrgid_r_impl) (gid_t gid, struct group * grp,
38 char *buffer, size_t buflen,
39 int *errnop);
40static enum nss_status (*getgrent_r_impl) (struct group * grp, char *buffer,
41 size_t buflen, int *errnop);
42static 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
53struct blacklist_t
54{
55 char *data;
56 int current;
57 int size;
58};
59
60struct ent_t
61{
62 bool files;
63 enum nss_status setent_status;
64 FILE *stream;
65 struct blacklist_t blacklist;
66};
67typedef struct ent_t ent_t;
68
69static 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. */
75static void blacklist_store_name (const char *, ent_t *);
76static bool in_blacklist (const char *, int, ent_t *);
77
78/* Initialize the NSS interface/functions. The calling function must
79 hold the lock. */
80static void
81init_nss_interface (void)
82{
83 if (__nss_database_get (db: nss_database_group_compat, actions: &ni))
84 {
85 setgrent_impl = __nss_lookup_function (ni, fct_name: "setgrent");
86 getgrnam_r_impl = __nss_lookup_function (ni, fct_name: "getgrnam_r");
87 getgrgid_r_impl = __nss_lookup_function (ni, fct_name: "getgrgid_r");
88 getgrent_r_impl = __nss_lookup_function (ni, fct_name: "getgrent_r");
89 endgrent_impl = __nss_lookup_function (ni, fct_name: "endgrent");
90 }
91}
92
93static enum nss_status
94internal_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 (path: "/etc/group");
112
113 if (ent->stream == NULL)
114 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
115 }
116 else
117 rewind (stream: 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
126enum nss_status
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 (ent: &ext_ent, stayopen, needent: 1);
137
138 __libc_lock_unlock (lock);
139
140 return result;
141}
142
143
144static enum nss_status __attribute_warn_unused_result__
145internal_endgrent (ent_t *ent)
146{
147 if (ent->stream != NULL)
148 {
149 fclose (stream: 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. */
166static void
167internal_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
174enum nss_status
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 (ent: &ext_ent);
185
186 __libc_lock_unlock (lock);
187
188 return result;
189}
190
191/* get the next group from NSS (+ entry) */
192static enum nss_status
193getgrent_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 (s: result->gr_name), ent));
212
213 return NSS_STATUS_SUCCESS;
214}
215
216/* This function handle the +group entrys in /etc/group */
217static enum nss_status
218getgrnam_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 (s: result->gr_name), ent))
230 return NSS_STATUS_NOTFOUND;
231
232 /* We found the entry. */
233 return NSS_STATUS_SUCCESS;
234}
235
236static enum nss_status
237getgrent_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 (stream: ent->stream, pos: &pos);
258 buffer[buflen - 1] = '\xff';
259 p = fgets_unlocked (s: buffer, n: buflen, stream: ent->stream);
260 if (p == NULL && feof_unlocked (stream: 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 (stream: ent->stream, pos: &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 (line: p, result, data, datalen: 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 (s: 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 (dest: buf, src: &result->gr_name[1], n: len);
310 status = getgrnam_plusgroup (name: &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
341enum nss_status
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 (ent: &ext_ent, stayopen: 1, needent: 1);
355
356 if (result == NSS_STATUS_SUCCESS)
357 {
358 if (ext_ent.files)
359 result = getgrent_next_file (result: grp, ent: &ext_ent, buffer, buflen, errnop);
360 else
361 result = getgrent_next_nss (result: grp, ent: &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 */
369static enum nss_status
370internal_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 (stream: ent->stream, pos: &pos);
391 buffer[buflen - 1] = '\xff';
392 p = fgets_unlocked (s: buffer, n: buflen, stream: ent->stream);
393 if (p == NULL && feof_unlocked (stream: 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 (stream: ent->stream, pos: &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 (line: p, result, data, datalen: 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 (s1: result->gr_name, s2: 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 (s1: &result->gr_name[1], s2: 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 (s1: name, s2: &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
472enum nss_status
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: &ent, stayopen: 0, needent: 0);
490
491 if (result == NSS_STATUS_SUCCESS)
492 result = internal_getgrnam_r (name, result: grp, ent: &ent, buffer, buflen, errnop);
493
494 internal_endgrent_noerror (ent: &ent);
495
496 return result;
497}
498
499/* Searches in /etc/group and the NIS/NIS+ map for a special group id */
500static enum nss_status
501internal_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 (stream: ent->stream, pos: &pos);
522 buffer[buflen - 1] = '\xff';
523 p = fgets_unlocked (s: buffer, n: buflen, stream: ent->stream);
524 if (p == NULL && feof_unlocked (stream: 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 (stream: ent->stream, pos: &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 (line: p, result, data, datalen: 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 (s: 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 (dest: buf, src: &result->gr_name[1], n: len);
578 status = getgrnam_plusgroup (name: &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
604enum nss_status
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: &ent, stayopen: 0, needent: 0);
619
620 if (result == NSS_STATUS_SUCCESS)
621 result = internal_getgrgid_r (gid, result: grp, ent: &ent, buffer, buflen, errnop);
622
623 internal_endgrent_noerror (ent: &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. */
631static void
632blacklist_store_name (const char *name, ent_t *ent)
633{
634 int namelen = strlen (s: 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 (size: 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 (ptr: ent->blacklist.data, size: ent->blacklist.size);
657 if (tmp == NULL)
658 {
659 free (ptr: 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. */
676static bool
677in_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 (haystack: ent->blacklist.data, needle: buf) != NULL;
690}
691

source code of glibc/nss/nss_compat/compat-grp.c