1/* Copyright (C) 1998-2024 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
33NSS_DECLARE_MODULE_FUNCTIONS (compat)
34
35static nss_action_list ni;
36static enum nss_status (*initgroups_dyn_impl) (const char *, gid_t,
37 long int *, long int *,
38 gid_t **, long int, int *);
39static enum nss_status (*getgrnam_r_impl) (const char *name,
40 struct group * grp, char *buffer,
41 size_t buflen, int *errnop);
42static enum nss_status (*getgrgid_r_impl) (gid_t gid, struct group * grp,
43 char *buffer, size_t buflen,
44 int *errnop);
45static enum nss_status (*setgrent_impl) (int stayopen);
46static enum nss_status (*getgrent_r_impl) (struct group * grp, char *buffer,
47 size_t buflen, int *errnop);
48static 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
63struct blacklist_t
64{
65 char *data;
66 int current;
67 int size;
68};
69
70struct ent_t
71{
72 bool files;
73 bool need_endgrent;
74 bool skip_initgroups_dyn;
75 FILE *stream;
76 struct blacklist_t blacklist;
77};
78typedef struct ent_t ent_t;
79
80/* Prototypes for local functions. */
81static void blacklist_store_name (const char *, ent_t *);
82static bool in_blacklist (const char *, int, ent_t *);
83
84/* Initialize the NSS interface/functions. The calling function must
85 hold the lock. */
86static void
87init_nss_interface (void)
88{
89 __libc_lock_lock (lock);
90
91 /* Retest. */
92 if (ni == NULL
93 && __nss_database_get (db: nss_database_group_compat, actions: &ni))
94 {
95 initgroups_dyn_impl = __nss_lookup_function (ni, fct_name: "initgroups_dyn");
96 getgrnam_r_impl = __nss_lookup_function (ni, fct_name: "getgrnam_r");
97 getgrgid_r_impl = __nss_lookup_function (ni, fct_name: "getgrgid_r");
98 setgrent_impl = __nss_lookup_function (ni, fct_name: "setgrent");
99 getgrent_r_impl = __nss_lookup_function (ni, fct_name: "getgrent_r");
100 endgrent_impl = __nss_lookup_function (ni, fct_name: "endgrent");
101 }
102
103 __libc_lock_unlock (lock);
104}
105
106static enum nss_status
107internal_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 (path: "/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
134static enum nss_status __attribute_warn_unused_result__
135internal_endgrent (ent_t *ent)
136{
137 if (ent->stream != NULL)
138 {
139 fclose (stream: 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. */
159static void
160internal_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. */
168static void
169add_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 (ptr: groups, size: 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. */
204static int
205check_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 (s1: *member, s2: user) == 0)
217 {
218 add_group (start, size, groupsp, limit, gid: 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. */
227static enum nss_status
228getgrent_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 (size: 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, gid: 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 (ptr: tmpbuf);
293 tmpbuf = malloc (size: 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 (s: grpbuf.gr_name), ent)
308 && check_and_add_group (user, group, start, size,
309 groupsp, limit, grp: &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 (ptr: tmpbuf);
328 }
329
330 free (ptr: mygroups);
331
332 return status;
333 }
334
335 free (ptr: 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 (s: grpbuf.gr_name), ent));
349
350 if (status == NSS_STATUS_SUCCESS)
351 check_and_add_group (user, group, start, size, groupsp, limit, grp: &grpbuf);
352
353 return status;
354}
355
356static enum nss_status
357internal_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 (stream: ent->stream, pos: &pos);
385 buffer[buflen - 1] = '\xff';
386 p = fgets_unlocked (s: buffer, n: buflen, stream: ent->stream);
387 if (p == NULL && feof_unlocked (stream: 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 (stream: ent->stream, pos: &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 (line: p, result: &grpbuf, data, datalen: 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 (s: &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, grp: &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, grp: &grpbuf);
475
476 return NSS_STATUS_SUCCESS;
477}
478
479
480enum nss_status
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 (ent: &intern);
489 if (status != NSS_STATUS_SUCCESS)
490 return status;
491
492 struct scratch_buffer tmpbuf;
493 scratch_buffer_init (buffer: &tmpbuf);
494
495 do
496 {
497 while ((status = internal_getgrent_r (ent: &intern, buffer: tmpbuf.data, buflen: tmpbuf.length,
498 user, group, start, size,
499 groupsp, limit, errnop))
500 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
501 if (!scratch_buffer_grow (buffer: &tmpbuf))
502 goto done;
503 }
504 while (status == NSS_STATUS_SUCCESS);
505
506 status = NSS_STATUS_SUCCESS;
507
508 done:
509 scratch_buffer_free (buffer: &tmpbuf);
510
511 internal_endgrent_noerror (ent: &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. */
519static void
520blacklist_store_name (const char *name, ent_t *ent)
521{
522 int namelen = strlen (s: 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 (size: 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 (ptr: ent->blacklist.data, size: ent->blacklist.size);
545 if (tmp == NULL)
546 {
547 free (ptr: 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. */
564static bool
565in_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 (haystack: ent->blacklist.data, needle: buf) != NULL;
578}
579

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