Add support for XPG7 testing.
[glibc.git] / nis / nss_compat / compat-initgroups.c
blob07a3b9282c37c9dd81c3c9421f06d6b2c5e71867
1 /* Copyright (C) 1998-2004, 2006, 2007, 2009 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_getgrent_r == NULL && nss_initgroups_dyn == NULL)
478 return NSS_STATUS_UNAVAIL;
480 ent->files = false;
482 if (nss_initgroups_dyn == NULL && nss_setgrent != NULL)
484 nss_setgrent (1);
485 ent->need_endgrent = true;
487 ent->skip_initgroups_dyn = true;
489 return getgrent_next_nss (ent, buffer, buflen, user, group,
490 start, size, groupsp, limit, errnop);
494 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
496 return NSS_STATUS_SUCCESS;
500 enum nss_status
501 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
502 long int *size, gid_t **groupsp, long int limit,
503 int *errnop)
505 size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
506 char *tmpbuf;
507 enum nss_status status;
508 ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
510 status = internal_setgrent (&intern);
511 if (status != NSS_STATUS_SUCCESS)
512 return status;
514 tmpbuf = __alloca (buflen);
518 while ((status = internal_getgrent_r (&intern, tmpbuf, buflen,
519 user, group, start, size,
520 groupsp, limit, errnop))
521 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
522 tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
524 while (status == NSS_STATUS_SUCCESS);
526 internal_endgrent (&intern);
528 return NSS_STATUS_SUCCESS;
532 /* Support routines for remembering -@netgroup and -user entries.
533 The names are stored in a single string with `|' as separator. */
534 static void
535 blacklist_store_name (const char *name, ent_t *ent)
537 int namelen = strlen (name);
538 char *tmp;
540 /* First call, setup cache. */
541 if (ent->blacklist.size == 0)
543 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
544 ent->blacklist.data = malloc (ent->blacklist.size);
545 if (ent->blacklist.data == NULL)
546 return;
547 ent->blacklist.data[0] = '|';
548 ent->blacklist.data[1] = '\0';
549 ent->blacklist.current = 1;
551 else
553 if (in_blacklist (name, namelen, ent))
554 return; /* no duplicates */
556 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
558 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
559 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
560 if (tmp == NULL)
562 free (ent->blacklist.data);
563 ent->blacklist.size = 0;
564 return;
566 ent->blacklist.data = tmp;
570 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
571 *tmp++ = '|';
572 *tmp = '\0';
573 ent->blacklist.current += namelen + 1;
575 return;
578 /* returns TRUE if ent->blacklist contains name, else FALSE */
579 static bool_t
580 in_blacklist (const char *name, int namelen, ent_t *ent)
582 char buf[namelen + 3];
583 char *cp;
585 if (ent->blacklist.data == NULL)
586 return FALSE;
588 buf[0] = '|';
589 cp = stpcpy (&buf[1], name);
590 *cp++ = '|';
591 *cp = '\0';
592 return strstr (ent->blacklist.data, buf) != NULL;