Use O_CLOEXEC in tzfile handling
[glibc.git] / nis / nss_compat / compat-initgroups.c
blobcaff38f9cccebfecacc24be40bee0de64beaf3ab
1 /* Copyright (C) 1998-2004,2006,2007,2009,2010 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, write to the Free
17 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18 02111-1307 USA. */
20 #include <alloca.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <grp.h>
25 #include <nss.h>
26 #include <stdio_ext.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <rpc/types.h>
30 #include <sys/param.h>
31 #include <nsswitch.h>
32 #include <bits/libc-lock.h>
33 #include <kernel-features.h>
35 static service_user *ni;
36 /* Type of the lookup function. */
37 static enum nss_status (*nss_initgroups_dyn) (const char *, gid_t,
38 long int *, long int *,
39 gid_t **, long int, int *);
40 static enum nss_status (*nss_getgrnam_r) (const char *name,
41 struct group * grp, char *buffer,
42 size_t buflen, int *errnop);
43 static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
44 char *buffer, size_t buflen,
45 int *errnop);
46 static enum nss_status (*nss_setgrent) (int stayopen);
47 static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
48 size_t buflen, int *errnop);
49 static enum nss_status (*nss_endgrent) (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;
82 /* Positive if O_CLOEXEC is supported, negative if it is not supported,
83 zero if it is still undecided. This variable is shared with the
84 other compat functions. */
85 #ifdef __ASSUME_O_CLOEXEC
86 # define __compat_have_cloexec 1
87 #else
88 # ifdef O_CLOEXEC
89 extern int __compat_have_cloexec;
90 # else
91 # define __compat_have_cloexec -1
92 # endif
93 #endif
95 /* Prototypes for local functions. */
96 static void blacklist_store_name (const char *, ent_t *);
97 static int in_blacklist (const char *, int, ent_t *);
99 /* Initialize the NSS interface/functions. The calling function must
100 hold the lock. */
101 static void
102 init_nss_interface (void)
104 __libc_lock_lock (lock);
106 /* Retest. */
107 if (ni == NULL
108 && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
110 nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
111 nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
112 nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
113 nss_setgrent = __nss_lookup_function (ni, "setgrent");
114 nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
115 nss_endgrent = __nss_lookup_function (ni, "endgrent");
118 __libc_lock_unlock (lock);
121 static enum nss_status
122 internal_setgrent (ent_t *ent)
124 enum nss_status status = NSS_STATUS_SUCCESS;
126 ent->files = true;
128 if (ni == NULL)
129 init_nss_interface ();
131 if (ent->blacklist.data != NULL)
133 ent->blacklist.current = 1;
134 ent->blacklist.data[0] = '|';
135 ent->blacklist.data[1] = '\0';
137 else
138 ent->blacklist.current = 0;
140 ent->stream = fopen ("/etc/group", "rme");
142 if (ent->stream == NULL)
143 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
144 else
146 /* We have to make sure the file is `closed on exec'. */
147 int result = 0;
149 if (__compat_have_cloexec <= 0)
151 int flags;
152 result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
153 if (result >= 0)
155 #if defined O_CLOEXEC && !defined __ASSUME_O_CLOEXEC
156 if (__compat_have_cloexec == 0)
157 __compat_have_cloexec = (flags & FD_CLOEXEC) ? 1 : -1;
159 if (__compat_have_cloexec < 0)
160 #endif
162 flags |= FD_CLOEXEC;
163 result = fcntl (fileno_unlocked (ent->stream), F_SETFD,
164 flags);
169 if (result < 0)
171 /* Something went wrong. Close the stream and return a
172 failure. */
173 fclose (ent->stream);
174 ent->stream = NULL;
175 status = NSS_STATUS_UNAVAIL;
177 else
178 /* We take care of locking ourself. */
179 __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
182 return status;
186 static enum nss_status
187 internal_endgrent (ent_t *ent)
189 if (ent->stream != NULL)
191 fclose (ent->stream);
192 ent->stream = NULL;
195 if (ent->blacklist.data != NULL)
197 ent->blacklist.current = 1;
198 ent->blacklist.data[0] = '|';
199 ent->blacklist.data[1] = '\0';
201 else
202 ent->blacklist.current = 0;
204 if (ent->need_endgrent && nss_endgrent != NULL)
205 nss_endgrent ();
207 return NSS_STATUS_SUCCESS;
210 /* Add new group record. */
211 static void
212 add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
213 gid_t gid)
215 gid_t *groups = *groupsp;
217 /* Matches user. Insert this group. */
218 if (__builtin_expect (*start == *size, 0))
220 /* Need a bigger buffer. */
221 gid_t *newgroups;
222 long int newsize;
224 if (limit > 0 && *size == limit)
225 /* We reached the maximum. */
226 return;
228 if (limit <= 0)
229 newsize = 2 * *size;
230 else
231 newsize = MIN (limit, 2 * *size);
233 newgroups = realloc (groups, newsize * sizeof (*groups));
234 if (newgroups == NULL)
235 return;
236 *groupsp = groups = newgroups;
237 *size = newsize;
240 groups[*start] = gid;
241 *start += 1;
244 /* This function checks, if the user is a member of this group and if
245 yes, add the group id to the list. Return nonzero is we couldn't
246 handle the group because the user is not in the member list. */
247 static int
248 check_and_add_group (const char *user, gid_t group, long int *start,
249 long int *size, gid_t **groupsp, long int limit,
250 struct group *grp)
252 char **member;
254 /* Don't add main group to list of groups. */
255 if (grp->gr_gid == group)
256 return 0;
258 for (member = grp->gr_mem; *member != NULL; ++member)
259 if (strcmp (*member, user) == 0)
261 add_group (start, size, groupsp, limit, grp->gr_gid);
262 return 0;
265 return 1;
268 /* Get the next group from NSS (+ entry). If the NSS module supports
269 initgroups_dyn, get all entries at once. */
270 static enum nss_status
271 getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
272 gid_t group, long int *start, long int *size,
273 gid_t **groupsp, long int limit, int *errnop)
275 enum nss_status status;
276 struct group grpbuf;
278 /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
279 If this function is not supported, step through the whole group
280 database with getgrent_r. */
281 if (! ent->skip_initgroups_dyn)
283 long int mystart = 0;
284 long int mysize = limit <= 0 ? *size : limit;
285 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
287 if (mygroups == NULL)
288 return NSS_STATUS_TRYAGAIN;
290 /* For every gid in the list we get from the NSS module,
291 get the whole group entry. We need to do this, since we
292 need the group name to check if it is in the blacklist.
293 In worst case, this is as twice as slow as stepping with
294 getgrent_r through the whole group database. But for large
295 group databases this is faster, since the user can only be
296 in a limited number of groups. */
297 if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
298 limit, errnop) == NSS_STATUS_SUCCESS)
300 /* If there is no blacklist we can trust the underlying
301 initgroups implementation. */
302 if (ent->blacklist.current <= 1)
303 for (int i = 0; i < mystart; i++)
304 add_group (start, size, groupsp, limit, mygroups[i]);
305 else
307 /* A temporary buffer. We use the normal buffer, until we find
308 an entry, for which this buffer is to small. In this case, we
309 overwrite the pointer with one to a bigger buffer. */
310 char *tmpbuf = buffer;
311 size_t tmplen = buflen;
313 for (int i = 0; i < mystart; i++)
315 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf,
316 tmpbuf, tmplen, errnop))
317 == NSS_STATUS_TRYAGAIN
318 && *errnop == ERANGE)
319 if (tmpbuf == buffer)
321 tmplen *= 2;
322 tmpbuf = __alloca (tmplen);
324 else
325 tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen);
327 if (__builtin_expect (status != NSS_STATUS_NOTFOUND, 1))
329 if (__builtin_expect (status != NSS_STATUS_SUCCESS, 0))
331 free (mygroups);
332 return status;
335 if (!in_blacklist (grpbuf.gr_name,
336 strlen (grpbuf.gr_name), ent)
337 && check_and_add_group (user, group, start, size,
338 groupsp, limit, &grpbuf))
340 if (nss_setgrent != NULL)
342 nss_setgrent (1);
343 ent->need_endgrent = true;
345 ent->skip_initgroups_dyn = true;
347 goto iter;
353 free (mygroups);
355 return NSS_STATUS_NOTFOUND;
358 free (mygroups);
361 /* If we come here, the NSS module does not support initgroups_dyn
362 or we were confronted with a split group. In these cases we have
363 to step through the whole list ourself. */
364 iter:
367 if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
368 NSS_STATUS_SUCCESS)
369 break;
371 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
373 if (status == NSS_STATUS_SUCCESS)
374 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
376 return status;
379 static enum nss_status
380 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
381 gid_t group, long int *start, long int *size,
382 gid_t **groupsp, long int limit, int *errnop)
384 struct parser_data *data = (void *) buffer;
385 struct group grpbuf;
387 if (!ent->files)
388 return getgrent_next_nss (ent, buffer, buflen, user, group,
389 start, size, groupsp, limit, errnop);
391 while (1)
393 fpos_t pos;
394 int parse_res = 0;
395 char *p;
399 /* We need at least 3 characters for one line. */
400 if (__builtin_expect (buflen < 3, 0))
402 erange:
403 *errnop = ERANGE;
404 return NSS_STATUS_TRYAGAIN;
407 fgetpos (ent->stream, &pos);
408 buffer[buflen - 1] = '\xff';
409 p = fgets_unlocked (buffer, buflen, ent->stream);
410 if (p == NULL && feof_unlocked (ent->stream))
411 return NSS_STATUS_NOTFOUND;
413 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
415 erange_reset:
416 fsetpos (ent->stream, &pos);
417 goto erange;
420 /* Terminate the line for any case. */
421 buffer[buflen - 1] = '\0';
423 /* Skip leading blanks. */
424 while (isspace (*p))
425 ++p;
427 while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
428 /* Parse the line. If it is invalid, loop to
429 get the next line of the file to parse. */
430 !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
431 errnop)));
433 if (__builtin_expect (parse_res == -1, 0))
434 /* The parser ran out of space. */
435 goto erange_reset;
437 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
438 /* This is a real entry. */
439 break;
441 /* -group */
442 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
443 && grpbuf.gr_name[1] != '@')
445 blacklist_store_name (&grpbuf.gr_name[1], ent);
446 continue;
449 /* +group */
450 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
451 && grpbuf.gr_name[1] != '@')
453 if (in_blacklist (&grpbuf.gr_name[1],
454 strlen (&grpbuf.gr_name[1]), ent))
455 continue;
456 /* Store the group in the blacklist for the "+" at the end of
457 /etc/group */
458 blacklist_store_name (&grpbuf.gr_name[1], ent);
459 if (nss_getgrnam_r == NULL)
460 return NSS_STATUS_UNAVAIL;
461 else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer,
462 buflen, errnop) != NSS_STATUS_SUCCESS)
463 continue;
465 check_and_add_group (user, group, start, size, groupsp,
466 limit, &grpbuf);
468 return NSS_STATUS_SUCCESS;
471 /* +:... */
472 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
474 /* If the selected module does not support getgrent_r or
475 initgroups_dyn, abort. We cannot find the needed group
476 entries. */
477 if (nss_initgroups_dyn == NULL || nss_getgrgid_r == NULL)
479 if (nss_setgrent != NULL)
481 nss_setgrent (1);
482 ent->need_endgrent = true;
484 ent->skip_initgroups_dyn = true;
486 if (nss_getgrent_r == NULL)
487 return NSS_STATUS_UNAVAIL;
490 ent->files = false;
492 return getgrent_next_nss (ent, buffer, buflen, user, group,
493 start, size, groupsp, limit, errnop);
497 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
499 return NSS_STATUS_SUCCESS;
503 enum nss_status
504 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
505 long int *size, gid_t **groupsp, long int limit,
506 int *errnop)
508 size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
509 char *tmpbuf;
510 enum nss_status status;
511 ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
513 status = internal_setgrent (&intern);
514 if (status != NSS_STATUS_SUCCESS)
515 return status;
517 tmpbuf = __alloca (buflen);
521 while ((status = internal_getgrent_r (&intern, tmpbuf, buflen,
522 user, group, start, size,
523 groupsp, limit, errnop))
524 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
525 tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
527 while (status == NSS_STATUS_SUCCESS);
529 internal_endgrent (&intern);
531 return NSS_STATUS_SUCCESS;
535 /* Support routines for remembering -@netgroup and -user entries.
536 The names are stored in a single string with `|' as separator. */
537 static void
538 blacklist_store_name (const char *name, ent_t *ent)
540 int namelen = strlen (name);
541 char *tmp;
543 /* First call, setup cache. */
544 if (ent->blacklist.size == 0)
546 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
547 ent->blacklist.data = malloc (ent->blacklist.size);
548 if (ent->blacklist.data == NULL)
549 return;
550 ent->blacklist.data[0] = '|';
551 ent->blacklist.data[1] = '\0';
552 ent->blacklist.current = 1;
554 else
556 if (in_blacklist (name, namelen, ent))
557 return; /* no duplicates */
559 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
561 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
562 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
563 if (tmp == NULL)
565 free (ent->blacklist.data);
566 ent->blacklist.size = 0;
567 return;
569 ent->blacklist.data = tmp;
573 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
574 *tmp++ = '|';
575 *tmp = '\0';
576 ent->blacklist.current += namelen + 1;
578 return;
581 /* returns TRUE if ent->blacklist contains name, else FALSE */
582 static bool_t
583 in_blacklist (const char *name, int namelen, ent_t *ent)
585 char buf[namelen + 3];
586 char *cp;
588 if (ent->blacklist.data == NULL)
589 return FALSE;
591 buf[0] = '|';
592 cp = stpcpy (&buf[1], name);
593 *cp++ = '|';
594 *cp = '\0';
595 return strstr (ent->blacklist.data, buf) != NULL;