Prepare for glibc 2.34 release.
[glibc.git] / nss / nss_compat / compat-initgroups.c
blobc3b065c931083b17bffed6a9551da2c49cc8865b
1 /* Copyright (C) 1998-2021 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
19 #include <ctype.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <grp.h>
23 #include <nss.h>
24 #include <stdio_ext.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/param.h>
28 #include <nsswitch.h>
29 #include <libc-lock.h>
30 #include <kernel-features.h>
31 #include <scratch_buffer.h>
32 #include <nss_files.h>
34 NSS_DECLARE_MODULE_FUNCTIONS (compat)
36 static nss_action_list ni;
37 static enum nss_status (*initgroups_dyn_impl) (const char *, gid_t,
38 long int *, long int *,
39 gid_t **, long int, int *);
40 static enum nss_status (*getgrnam_r_impl) (const char *name,
41 struct group * grp, char *buffer,
42 size_t buflen, int *errnop);
43 static enum nss_status (*getgrgid_r_impl) (gid_t gid, struct group * grp,
44 char *buffer, size_t buflen,
45 int *errnop);
46 static enum nss_status (*setgrent_impl) (int stayopen);
47 static enum nss_status (*getgrent_r_impl) (struct group * grp, char *buffer,
48 size_t buflen, int *errnop);
49 static enum nss_status (*endgrent_impl) (void);
51 /* Protect global state against multiple changers. */
52 __libc_lock_define_initialized (static, lock)
55 /* Get the declaration of the parser function. */
56 #define ENTNAME grent
57 #define STRUCTURE group
58 #define EXTERN_PARSER
59 #include <nss/nss_files/files-parse.c>
61 /* Structure for remembering -group members ... */
62 #define BLACKLIST_INITIAL_SIZE 512
63 #define BLACKLIST_INCREMENT 256
64 struct blacklist_t
66 char *data;
67 int current;
68 int size;
71 struct ent_t
73 bool files;
74 bool need_endgrent;
75 bool skip_initgroups_dyn;
76 FILE *stream;
77 struct blacklist_t blacklist;
79 typedef struct ent_t ent_t;
81 /* Prototypes for local functions. */
82 static void blacklist_store_name (const char *, ent_t *);
83 static bool in_blacklist (const char *, int, ent_t *);
85 /* Initialize the NSS interface/functions. The calling function must
86 hold the lock. */
87 static void
88 init_nss_interface (void)
90 __libc_lock_lock (lock);
92 /* Retest. */
93 if (ni == NULL
94 && __nss_database_get (nss_database_group_compat, &ni))
96 initgroups_dyn_impl = __nss_lookup_function (ni, "initgroups_dyn");
97 getgrnam_r_impl = __nss_lookup_function (ni, "getgrnam_r");
98 getgrgid_r_impl = __nss_lookup_function (ni, "getgrgid_r");
99 setgrent_impl = __nss_lookup_function (ni, "setgrent");
100 getgrent_r_impl = __nss_lookup_function (ni, "getgrent_r");
101 endgrent_impl = __nss_lookup_function (ni, "endgrent");
104 __libc_lock_unlock (lock);
107 static enum nss_status
108 internal_setgrent (ent_t *ent)
110 enum nss_status status = NSS_STATUS_SUCCESS;
112 ent->files = true;
114 if (ni == NULL)
115 init_nss_interface ();
117 if (ent->blacklist.data != NULL)
119 ent->blacklist.current = 1;
120 ent->blacklist.data[0] = '|';
121 ent->blacklist.data[1] = '\0';
123 else
124 ent->blacklist.current = 0;
126 ent->stream = __nss_files_fopen ("/etc/group");
128 if (ent->stream == NULL)
129 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
131 return status;
135 static enum nss_status __attribute_warn_unused_result__
136 internal_endgrent (ent_t *ent)
138 if (ent->stream != NULL)
140 fclose (ent->stream);
141 ent->stream = NULL;
144 if (ent->blacklist.data != NULL)
146 ent->blacklist.current = 1;
147 ent->blacklist.data[0] = '|';
148 ent->blacklist.data[1] = '\0';
150 else
151 ent->blacklist.current = 0;
153 if (ent->need_endgrent && endgrent_impl != NULL)
154 endgrent_impl ();
156 return NSS_STATUS_SUCCESS;
159 /* Like internal_endgrent, but preserve errno in all cases. */
160 static void
161 internal_endgrent_noerror (ent_t *ent)
163 int saved_errno = errno;
164 enum nss_status unused __attribute__ ((unused)) = internal_endgrent (ent);
165 __set_errno (saved_errno);
168 /* Add new group record. */
169 static void
170 add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
171 gid_t gid)
173 gid_t *groups = *groupsp;
175 /* Matches user. Insert this group. */
176 if (__glibc_unlikely (*start == *size))
178 /* Need a bigger buffer. */
179 gid_t *newgroups;
180 long int newsize;
182 if (limit > 0 && *size == limit)
183 /* We reached the maximum. */
184 return;
186 if (limit <= 0)
187 newsize = 2 * *size;
188 else
189 newsize = MIN (limit, 2 * *size);
191 newgroups = realloc (groups, newsize * sizeof (*groups));
192 if (newgroups == NULL)
193 return;
194 *groupsp = groups = newgroups;
195 *size = newsize;
198 groups[*start] = gid;
199 *start += 1;
202 /* This function checks, if the user is a member of this group and if
203 yes, add the group id to the list. Return nonzero is we couldn't
204 handle the group because the user is not in the member list. */
205 static int
206 check_and_add_group (const char *user, gid_t group, long int *start,
207 long int *size, gid_t **groupsp, long int limit,
208 struct group *grp)
210 char **member;
212 /* Don't add main group to list of groups. */
213 if (grp->gr_gid == group)
214 return 0;
216 for (member = grp->gr_mem; *member != NULL; ++member)
217 if (strcmp (*member, user) == 0)
219 add_group (start, size, groupsp, limit, grp->gr_gid);
220 return 0;
223 return 1;
226 /* Get the next group from NSS (+ entry). If the NSS module supports
227 initgroups_dyn, get all entries at once. */
228 static enum nss_status
229 getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
230 gid_t group, long int *start, long int *size,
231 gid_t **groupsp, long int limit, int *errnop)
233 enum nss_status status;
234 struct group grpbuf;
236 /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
237 If this function is not supported, step through the whole group
238 database with getgrent_r. */
239 if (! ent->skip_initgroups_dyn)
241 long int mystart = 0;
242 long int mysize = limit <= 0 ? *size : limit;
243 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
245 if (mygroups == NULL)
246 return NSS_STATUS_TRYAGAIN;
248 /* For every gid in the list we get from the NSS module,
249 get the whole group entry. We need to do this, since we
250 need the group name to check if it is in the blacklist.
251 In worst case, this is as twice as slow as stepping with
252 getgrent_r through the whole group database. But for large
253 group databases this is faster, since the user can only be
254 in a limited number of groups. */
255 if (initgroups_dyn_impl (user, group, &mystart, &mysize, &mygroups,
256 limit, errnop) == NSS_STATUS_SUCCESS)
258 status = NSS_STATUS_NOTFOUND;
260 /* If there is no blacklist we can trust the underlying
261 initgroups implementation. */
262 if (ent->blacklist.current <= 1)
263 for (int i = 0; i < mystart; i++)
264 add_group (start, size, groupsp, limit, mygroups[i]);
265 else
267 /* A temporary buffer. We use the normal buffer, until we find
268 an entry, for which this buffer is to small. In this case, we
269 overwrite the pointer with one to a bigger buffer. */
270 char *tmpbuf = buffer;
271 size_t tmplen = buflen;
273 for (int i = 0; i < mystart; i++)
275 while ((status = getgrgid_r_impl (mygroups[i], &grpbuf,
276 tmpbuf, tmplen, errnop))
277 == NSS_STATUS_TRYAGAIN
278 && *errnop == ERANGE)
280 /* Check for overflow. */
281 if (__glibc_unlikely (tmplen * 2 < tmplen))
283 __set_errno (ENOMEM);
284 status = NSS_STATUS_TRYAGAIN;
285 goto done;
287 /* Increase the size. Make sure that we retry
288 with a reasonable size. */
289 tmplen *= 2;
290 if (tmplen < 1024)
291 tmplen = 1024;
292 if (tmpbuf != buffer)
293 free (tmpbuf);
294 tmpbuf = malloc (tmplen);
295 if (__glibc_unlikely (tmpbuf == NULL))
297 status = NSS_STATUS_TRYAGAIN;
298 goto done;
302 if (__builtin_expect (status != NSS_STATUS_NOTFOUND, 1))
304 if (__builtin_expect (status != NSS_STATUS_SUCCESS, 0))
305 goto done;
307 if (!in_blacklist (grpbuf.gr_name,
308 strlen (grpbuf.gr_name), ent)
309 && check_and_add_group (user, group, start, size,
310 groupsp, limit, &grpbuf))
312 if (setgrent_impl != NULL)
314 setgrent_impl (1);
315 ent->need_endgrent = true;
317 ent->skip_initgroups_dyn = true;
319 goto iter;
324 status = NSS_STATUS_NOTFOUND;
326 done:
327 if (tmpbuf != buffer)
328 free (tmpbuf);
331 free (mygroups);
333 return status;
336 free (mygroups);
339 /* If we come here, the NSS module does not support initgroups_dyn
340 or we were confronted with a split group. In these cases we have
341 to step through the whole list ourself. */
342 iter:
345 if ((status = getgrent_r_impl (&grpbuf, buffer, buflen, errnop))
346 != NSS_STATUS_SUCCESS)
347 break;
349 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
351 if (status == NSS_STATUS_SUCCESS)
352 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
354 return status;
357 static enum nss_status
358 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
359 gid_t group, long int *start, long int *size,
360 gid_t **groupsp, long int limit, int *errnop)
362 struct parser_data *data = (void *) buffer;
363 struct group grpbuf;
365 if (!ent->files)
366 return getgrent_next_nss (ent, buffer, buflen, user, group,
367 start, size, groupsp, limit, errnop);
369 while (1)
371 fpos_t pos;
372 int parse_res = 0;
373 char *p;
377 /* We need at least 3 characters for one line. */
378 if (__glibc_unlikely (buflen < 3))
380 erange:
381 *errnop = ERANGE;
382 return NSS_STATUS_TRYAGAIN;
385 fgetpos (ent->stream, &pos);
386 buffer[buflen - 1] = '\xff';
387 p = fgets_unlocked (buffer, buflen, ent->stream);
388 if (p == NULL && feof_unlocked (ent->stream))
389 return NSS_STATUS_NOTFOUND;
391 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
393 erange_reset:
394 fsetpos (ent->stream, &pos);
395 goto erange;
398 /* Terminate the line for any case. */
399 buffer[buflen - 1] = '\0';
401 /* Skip leading blanks. */
402 while (isspace (*p))
403 ++p;
405 while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */
406 /* Parse the line. If it is invalid, loop to
407 get the next line of the file to parse. */
408 || !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
409 errnop)));
411 if (__glibc_unlikely (parse_res == -1))
412 /* The parser ran out of space. */
413 goto erange_reset;
415 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
416 /* This is a real entry. */
417 break;
419 /* -group */
420 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
421 && grpbuf.gr_name[1] != '@')
423 blacklist_store_name (&grpbuf.gr_name[1], ent);
424 continue;
427 /* +group */
428 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
429 && grpbuf.gr_name[1] != '@')
431 if (in_blacklist (&grpbuf.gr_name[1],
432 strlen (&grpbuf.gr_name[1]), ent))
433 continue;
434 /* Store the group in the blacklist for the "+" at the end of
435 /etc/group */
436 blacklist_store_name (&grpbuf.gr_name[1], ent);
437 if (getgrnam_r_impl == NULL)
438 return NSS_STATUS_UNAVAIL;
439 else if (getgrnam_r_impl (&grpbuf.gr_name[1], &grpbuf, buffer,
440 buflen, errnop) != NSS_STATUS_SUCCESS)
441 continue;
443 check_and_add_group (user, group, start, size, groupsp,
444 limit, &grpbuf);
446 return NSS_STATUS_SUCCESS;
449 /* +:... */
450 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
452 /* If the selected module does not support getgrent_r or
453 initgroups_dyn, abort. We cannot find the needed group
454 entries. */
455 if (initgroups_dyn_impl == NULL || getgrgid_r_impl == NULL)
457 if (setgrent_impl != NULL)
459 setgrent_impl (1);
460 ent->need_endgrent = true;
462 ent->skip_initgroups_dyn = true;
464 if (getgrent_r_impl == NULL)
465 return NSS_STATUS_UNAVAIL;
468 ent->files = false;
470 return getgrent_next_nss (ent, buffer, buflen, user, group,
471 start, size, groupsp, limit, errnop);
475 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
477 return NSS_STATUS_SUCCESS;
481 enum nss_status
482 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
483 long int *size, gid_t **groupsp, long int limit,
484 int *errnop)
486 enum nss_status status;
487 ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
489 status = internal_setgrent (&intern);
490 if (status != NSS_STATUS_SUCCESS)
491 return status;
493 struct scratch_buffer tmpbuf;
494 scratch_buffer_init (&tmpbuf);
498 while ((status = internal_getgrent_r (&intern, tmpbuf.data, tmpbuf.length,
499 user, group, start, size,
500 groupsp, limit, errnop))
501 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
502 if (!scratch_buffer_grow (&tmpbuf))
503 goto done;
505 while (status == NSS_STATUS_SUCCESS);
507 status = NSS_STATUS_SUCCESS;
509 done:
510 scratch_buffer_free (&tmpbuf);
512 internal_endgrent_noerror (&intern);
514 return status;
518 /* Support routines for remembering -@netgroup and -user entries.
519 The names are stored in a single string with `|' as separator. */
520 static void
521 blacklist_store_name (const char *name, ent_t *ent)
523 int namelen = strlen (name);
524 char *tmp;
526 /* First call, setup cache. */
527 if (ent->blacklist.size == 0)
529 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
530 ent->blacklist.data = malloc (ent->blacklist.size);
531 if (ent->blacklist.data == NULL)
532 return;
533 ent->blacklist.data[0] = '|';
534 ent->blacklist.data[1] = '\0';
535 ent->blacklist.current = 1;
537 else
539 if (in_blacklist (name, namelen, ent))
540 return; /* no duplicates */
542 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
544 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
545 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
546 if (tmp == NULL)
548 free (ent->blacklist.data);
549 ent->blacklist.size = 0;
550 return;
552 ent->blacklist.data = tmp;
556 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
557 *tmp++ = '|';
558 *tmp = '\0';
559 ent->blacklist.current += namelen + 1;
561 return;
564 /* Return whether ent->blacklist contains name. */
565 static bool
566 in_blacklist (const char *name, int namelen, ent_t *ent)
568 char buf[namelen + 3];
569 char *cp;
571 if (ent->blacklist.data == NULL)
572 return false;
574 buf[0] = '|';
575 cp = stpcpy (&buf[1], name);
576 *cp++ = '|';
577 *cp = '\0';
578 return strstr (ent->blacklist.data, buf) != NULL;