Consolidate non cancellable open call
[glibc.git] / nis / nss_compat / compat-initgroups.c
blob795213448c7c98b732b2cedc3136f33533f73c01
1 /* Copyright (C) 1998-2017 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 <http://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 <rpc/types.h>
28 #include <sys/param.h>
29 #include <nsswitch.h>
30 #include <libc-lock.h>
31 #include <kernel-features.h>
32 #include <scratch_buffer.h>
34 static service_user *ni;
35 /* Type of the lookup function. */
36 static enum nss_status (*nss_initgroups_dyn) (const char *, gid_t,
37 long int *, long int *,
38 gid_t **, long int, int *);
39 static enum nss_status (*nss_getgrnam_r) (const char *name,
40 struct group * grp, char *buffer,
41 size_t buflen, int *errnop);
42 static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
43 char *buffer, size_t buflen,
44 int *errnop);
45 static enum nss_status (*nss_setgrent) (int stayopen);
46 static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
47 size_t buflen, int *errnop);
48 static enum nss_status (*nss_endgrent) (void);
50 /* Protect global state against multiple changers. */
51 __libc_lock_define_initialized (static, lock)
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>
60 /* Structure for remembering -group members ... */
61 #define BLACKLIST_INITIAL_SIZE 512
62 #define BLACKLIST_INCREMENT 256
63 struct blacklist_t
65 char *data;
66 int current;
67 int size;
70 struct ent_t
72 bool files;
73 bool need_endgrent;
74 bool skip_initgroups_dyn;
75 FILE *stream;
76 struct blacklist_t blacklist;
78 typedef struct ent_t ent_t;
80 /* Prototypes for local functions. */
81 static void blacklist_store_name (const char *, ent_t *);
82 static int in_blacklist (const char *, int, ent_t *);
84 /* Initialize the NSS interface/functions. The calling function must
85 hold the lock. */
86 static void
87 init_nss_interface (void)
89 __libc_lock_lock (lock);
91 /* Retest. */
92 if (ni == NULL
93 && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
95 nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
96 nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
97 nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
98 nss_setgrent = __nss_lookup_function (ni, "setgrent");
99 nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
100 nss_endgrent = __nss_lookup_function (ni, "endgrent");
103 __libc_lock_unlock (lock);
106 static enum nss_status
107 internal_setgrent (ent_t *ent)
109 enum nss_status status = NSS_STATUS_SUCCESS;
111 ent->files = true;
113 if (ni == NULL)
114 init_nss_interface ();
116 if (ent->blacklist.data != NULL)
118 ent->blacklist.current = 1;
119 ent->blacklist.data[0] = '|';
120 ent->blacklist.data[1] = '\0';
122 else
123 ent->blacklist.current = 0;
125 ent->stream = fopen ("/etc/group", "rme");
127 if (ent->stream == NULL)
128 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
129 else
130 /* We take care of locking ourself. */
131 __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
133 return status;
137 static enum nss_status
138 internal_endgrent (ent_t *ent)
140 if (ent->stream != NULL)
142 fclose (ent->stream);
143 ent->stream = NULL;
146 if (ent->blacklist.data != NULL)
148 ent->blacklist.current = 1;
149 ent->blacklist.data[0] = '|';
150 ent->blacklist.data[1] = '\0';
152 else
153 ent->blacklist.current = 0;
155 if (ent->need_endgrent && nss_endgrent != NULL)
156 nss_endgrent ();
158 return NSS_STATUS_SUCCESS;
161 /* Add new group record. */
162 static void
163 add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
164 gid_t gid)
166 gid_t *groups = *groupsp;
168 /* Matches user. Insert this group. */
169 if (__glibc_unlikely (*start == *size))
171 /* Need a bigger buffer. */
172 gid_t *newgroups;
173 long int newsize;
175 if (limit > 0 && *size == limit)
176 /* We reached the maximum. */
177 return;
179 if (limit <= 0)
180 newsize = 2 * *size;
181 else
182 newsize = MIN (limit, 2 * *size);
184 newgroups = realloc (groups, newsize * sizeof (*groups));
185 if (newgroups == NULL)
186 return;
187 *groupsp = groups = newgroups;
188 *size = newsize;
191 groups[*start] = gid;
192 *start += 1;
195 /* This function checks, if the user is a member of this group and if
196 yes, add the group id to the list. Return nonzero is we couldn't
197 handle the group because the user is not in the member list. */
198 static int
199 check_and_add_group (const char *user, gid_t group, long int *start,
200 long int *size, gid_t **groupsp, long int limit,
201 struct group *grp)
203 char **member;
205 /* Don't add main group to list of groups. */
206 if (grp->gr_gid == group)
207 return 0;
209 for (member = grp->gr_mem; *member != NULL; ++member)
210 if (strcmp (*member, user) == 0)
212 add_group (start, size, groupsp, limit, grp->gr_gid);
213 return 0;
216 return 1;
219 /* Get the next group from NSS (+ entry). If the NSS module supports
220 initgroups_dyn, get all entries at once. */
221 static enum nss_status
222 getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
223 gid_t group, long int *start, long int *size,
224 gid_t **groupsp, long int limit, int *errnop)
226 enum nss_status status;
227 struct group grpbuf;
229 /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
230 If this function is not supported, step through the whole group
231 database with getgrent_r. */
232 if (! ent->skip_initgroups_dyn)
234 long int mystart = 0;
235 long int mysize = limit <= 0 ? *size : limit;
236 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
238 if (mygroups == NULL)
239 return NSS_STATUS_TRYAGAIN;
241 /* For every gid in the list we get from the NSS module,
242 get the whole group entry. We need to do this, since we
243 need the group name to check if it is in the blacklist.
244 In worst case, this is as twice as slow as stepping with
245 getgrent_r through the whole group database. But for large
246 group databases this is faster, since the user can only be
247 in a limited number of groups. */
248 if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
249 limit, errnop) == NSS_STATUS_SUCCESS)
251 status = NSS_STATUS_NOTFOUND;
253 /* If there is no blacklist we can trust the underlying
254 initgroups implementation. */
255 if (ent->blacklist.current <= 1)
256 for (int i = 0; i < mystart; i++)
257 add_group (start, size, groupsp, limit, mygroups[i]);
258 else
260 /* A temporary buffer. We use the normal buffer, until we find
261 an entry, for which this buffer is to small. In this case, we
262 overwrite the pointer with one to a bigger buffer. */
263 char *tmpbuf = buffer;
264 size_t tmplen = buflen;
265 bool use_malloc = false;
267 for (int i = 0; i < mystart; i++)
269 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf,
270 tmpbuf, tmplen, errnop))
271 == NSS_STATUS_TRYAGAIN
272 && *errnop == ERANGE)
274 if (__libc_use_alloca (tmplen * 2))
276 if (tmpbuf == buffer)
278 tmplen *= 2;
279 tmpbuf = __alloca (tmplen);
281 else
282 tmpbuf = extend_alloca (tmpbuf, tmplen, tmplen * 2);
284 else
286 tmplen *= 2;
287 char *newbuf = realloc (use_malloc ? tmpbuf : NULL, tmplen);
289 if (newbuf == NULL)
291 status = NSS_STATUS_TRYAGAIN;
292 goto done;
294 use_malloc = true;
295 tmpbuf = newbuf;
299 if (__builtin_expect (status != NSS_STATUS_NOTFOUND, 1))
301 if (__builtin_expect (status != NSS_STATUS_SUCCESS, 0))
302 goto done;
304 if (!in_blacklist (grpbuf.gr_name,
305 strlen (grpbuf.gr_name), ent)
306 && check_and_add_group (user, group, start, size,
307 groupsp, limit, &grpbuf))
309 if (nss_setgrent != NULL)
311 nss_setgrent (1);
312 ent->need_endgrent = true;
314 ent->skip_initgroups_dyn = true;
316 goto iter;
321 status = NSS_STATUS_NOTFOUND;
323 done:
324 if (use_malloc)
325 free (tmpbuf);
328 free (mygroups);
330 return status;
333 free (mygroups);
336 /* If we come here, the NSS module does not support initgroups_dyn
337 or we were confronted with a split group. In these cases we have
338 to step through the whole list ourself. */
339 iter:
342 if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
343 NSS_STATUS_SUCCESS)
344 break;
346 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
348 if (status == NSS_STATUS_SUCCESS)
349 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
351 return status;
354 static enum nss_status
355 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
356 gid_t group, long int *start, long int *size,
357 gid_t **groupsp, long int limit, int *errnop)
359 struct parser_data *data = (void *) buffer;
360 struct group grpbuf;
362 if (!ent->files)
363 return getgrent_next_nss (ent, buffer, buflen, user, group,
364 start, size, groupsp, limit, errnop);
366 while (1)
368 fpos_t pos;
369 int parse_res = 0;
370 char *p;
374 /* We need at least 3 characters for one line. */
375 if (__glibc_unlikely (buflen < 3))
377 erange:
378 *errnop = ERANGE;
379 return NSS_STATUS_TRYAGAIN;
382 fgetpos (ent->stream, &pos);
383 buffer[buflen - 1] = '\xff';
384 p = fgets_unlocked (buffer, buflen, ent->stream);
385 if (p == NULL && feof_unlocked (ent->stream))
386 return NSS_STATUS_NOTFOUND;
388 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
390 erange_reset:
391 fsetpos (ent->stream, &pos);
392 goto erange;
395 /* Terminate the line for any case. */
396 buffer[buflen - 1] = '\0';
398 /* Skip leading blanks. */
399 while (isspace (*p))
400 ++p;
402 while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
403 /* Parse the line. If it is invalid, loop to
404 get the next line of the file to parse. */
405 !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
406 errnop)));
408 if (__glibc_unlikely (parse_res == -1))
409 /* The parser ran out of space. */
410 goto erange_reset;
412 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
413 /* This is a real entry. */
414 break;
416 /* -group */
417 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
418 && grpbuf.gr_name[1] != '@')
420 blacklist_store_name (&grpbuf.gr_name[1], ent);
421 continue;
424 /* +group */
425 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
426 && grpbuf.gr_name[1] != '@')
428 if (in_blacklist (&grpbuf.gr_name[1],
429 strlen (&grpbuf.gr_name[1]), ent))
430 continue;
431 /* Store the group in the blacklist for the "+" at the end of
432 /etc/group */
433 blacklist_store_name (&grpbuf.gr_name[1], ent);
434 if (nss_getgrnam_r == NULL)
435 return NSS_STATUS_UNAVAIL;
436 else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer,
437 buflen, errnop) != NSS_STATUS_SUCCESS)
438 continue;
440 check_and_add_group (user, group, start, size, groupsp,
441 limit, &grpbuf);
443 return NSS_STATUS_SUCCESS;
446 /* +:... */
447 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
449 /* If the selected module does not support getgrent_r or
450 initgroups_dyn, abort. We cannot find the needed group
451 entries. */
452 if (nss_initgroups_dyn == NULL || nss_getgrgid_r == NULL)
454 if (nss_setgrent != NULL)
456 nss_setgrent (1);
457 ent->need_endgrent = true;
459 ent->skip_initgroups_dyn = true;
461 if (nss_getgrent_r == NULL)
462 return NSS_STATUS_UNAVAIL;
465 ent->files = false;
467 return getgrent_next_nss (ent, buffer, buflen, user, group,
468 start, size, groupsp, limit, errnop);
472 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
474 return NSS_STATUS_SUCCESS;
478 enum nss_status
479 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
480 long int *size, gid_t **groupsp, long int limit,
481 int *errnop)
483 enum nss_status status;
484 ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
486 status = internal_setgrent (&intern);
487 if (status != NSS_STATUS_SUCCESS)
488 return status;
490 struct scratch_buffer tmpbuf;
491 scratch_buffer_init (&tmpbuf);
495 while ((status = internal_getgrent_r (&intern, tmpbuf.data, tmpbuf.length,
496 user, group, start, size,
497 groupsp, limit, errnop))
498 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
499 if (!scratch_buffer_grow (&tmpbuf))
500 goto done;
502 while (status == NSS_STATUS_SUCCESS);
504 status = NSS_STATUS_SUCCESS;
506 done:
507 scratch_buffer_free (&tmpbuf);
509 internal_endgrent (&intern);
511 return status;
515 /* Support routines for remembering -@netgroup and -user entries.
516 The names are stored in a single string with `|' as separator. */
517 static void
518 blacklist_store_name (const char *name, ent_t *ent)
520 int namelen = strlen (name);
521 char *tmp;
523 /* First call, setup cache. */
524 if (ent->blacklist.size == 0)
526 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
527 ent->blacklist.data = malloc (ent->blacklist.size);
528 if (ent->blacklist.data == NULL)
529 return;
530 ent->blacklist.data[0] = '|';
531 ent->blacklist.data[1] = '\0';
532 ent->blacklist.current = 1;
534 else
536 if (in_blacklist (name, namelen, ent))
537 return; /* no duplicates */
539 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
541 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
542 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
543 if (tmp == NULL)
545 free (ent->blacklist.data);
546 ent->blacklist.size = 0;
547 return;
549 ent->blacklist.data = tmp;
553 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
554 *tmp++ = '|';
555 *tmp = '\0';
556 ent->blacklist.current += namelen + 1;
558 return;
561 /* returns TRUE if ent->blacklist contains name, else FALSE */
562 static bool_t
563 in_blacklist (const char *name, int namelen, ent_t *ent)
565 char buf[namelen + 3];
566 char *cp;
568 if (ent->blacklist.data == NULL)
569 return FALSE;
571 buf[0] = '|';
572 cp = stpcpy (&buf[1], name);
573 *cp++ = '|';
574 *cp = '\0';
575 return strstr (ent->blacklist.data, buf) != NULL;