2.9
[glibc/nacl-glibc.git] / nis / nss_compat / compat-initgroups.c
blob76ca95d1e6e0754169f617e27abd68d48d6e1d62
1 /* Copyright (C) 1998-2004, 2006, 2007 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_getgrent_r) (struct group * grp, char *buffer,
47 size_t buflen, int *errnop);
49 /* Protect global state against multiple changers. */
50 __libc_lock_define_initialized (static, lock)
53 /* Get the declaration of the parser function. */
54 #define ENTNAME grent
55 #define STRUCTURE group
56 #define EXTERN_PARSER
57 #include <nss/nss_files/files-parse.c>
59 /* Structure for remembering -group members ... */
60 #define BLACKLIST_INITIAL_SIZE 512
61 #define BLACKLIST_INCREMENT 256
62 struct blacklist_t
64 char *data;
65 int current;
66 int size;
69 struct ent_t
71 bool_t files;
72 FILE *stream;
73 struct blacklist_t blacklist;
75 typedef struct ent_t ent_t;
78 /* Positive if O_CLOEXEC is supported, negative if it is not supported,
79 zero if it is still undecided. This variable is shared with the
80 other compat functions. */
81 #ifdef __ASSUME_O_CLOEXEC
82 # define __compat_have_cloexec 1
83 #else
84 # ifdef O_CLOEXEC
85 extern int __compat_have_cloexec;
86 # else
87 # define __compat_have_cloexec -1
88 # endif
89 #endif
91 /* Prototypes for local functions. */
92 static void blacklist_store_name (const char *, ent_t *);
93 static int in_blacklist (const char *, int, ent_t *);
95 /* Initialize the NSS interface/functions. The calling function must
96 hold the lock. */
97 static void
98 init_nss_interface (void)
100 __libc_lock_lock (lock);
102 /* Retest. */
103 if (ni == NULL
104 && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
106 nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
107 nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
108 nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
109 nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
112 __libc_lock_unlock (lock);
115 static enum nss_status
116 internal_setgrent (ent_t *ent)
118 enum nss_status status = NSS_STATUS_SUCCESS;
120 ent->files = TRUE;
122 if (ni == NULL)
123 init_nss_interface ();
125 if (ent->blacklist.data != NULL)
127 ent->blacklist.current = 1;
128 ent->blacklist.data[0] = '|';
129 ent->blacklist.data[1] = '\0';
131 else
132 ent->blacklist.current = 0;
134 ent->stream = fopen ("/etc/group", "rme");
136 if (ent->stream == NULL)
137 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
138 else
140 /* We have to make sure the file is `closed on exec'. */
141 int result = 0;
143 if (__compat_have_cloexec <= 0)
145 int flags;
146 result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
147 if (result >= 0)
149 #if defined O_CLOEXEC && !defined __ASSUME_O_CLOEXEC
150 if (__compat_have_cloexec == 0)
151 __compat_have_cloexec = (flags & FD_CLOEXEC) ? 1 : -1;
153 if (__compat_have_cloexec < 0)
154 #endif
156 flags |= FD_CLOEXEC;
157 result = fcntl (fileno_unlocked (ent->stream), F_SETFD,
158 flags);
163 if (result < 0)
165 /* Something went wrong. Close the stream and return a
166 failure. */
167 fclose (ent->stream);
168 ent->stream = NULL;
169 status = NSS_STATUS_UNAVAIL;
171 else
172 /* We take care of locking ourself. */
173 __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
176 return status;
180 static enum nss_status
181 internal_endgrent (ent_t *ent)
183 if (ent->stream != NULL)
185 fclose (ent->stream);
186 ent->stream = NULL;
189 if (ent->blacklist.data != NULL)
191 ent->blacklist.current = 1;
192 ent->blacklist.data[0] = '|';
193 ent->blacklist.data[1] = '\0';
195 else
196 ent->blacklist.current = 0;
198 return NSS_STATUS_SUCCESS;
201 /* This function checks, if the user is a member of this group and if
202 yes, add the group id to the list. */
203 static void
204 check_and_add_group (const char *user, gid_t group, long int *start,
205 long int *size, gid_t **groupsp, long int limit,
206 struct group *grp)
208 gid_t *groups = *groupsp;
209 char **member;
211 /* Don't add main group to list of groups. */
212 if (grp->gr_gid == group)
213 return;
215 for (member = grp->gr_mem; *member != NULL; ++member)
216 if (strcmp (*member, user) == 0)
218 /* Matches user. Insert this group. */
219 if (*start == *size)
221 /* Need a bigger buffer. */
222 gid_t *newgroups;
223 long int newsize;
225 if (limit > 0 && *size == limit)
226 /* We reached the maximum. */
227 return;
229 if (limit <= 0)
230 newsize = 2 * *size;
231 else
232 newsize = MIN (limit, 2 * *size);
234 newgroups = realloc (groups, newsize * sizeof (*groups));
235 if (newgroups == NULL)
236 return;
237 *groupsp = groups = newgroups;
238 *size = newsize;
241 groups[*start] = grp->gr_gid;
242 *start += 1;
244 break;
248 /* Get the next group from NSS (+ entry). If the NSS module supports
249 initgroups_dyn, get all entries at once. */
250 static enum nss_status
251 getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
252 gid_t group, long int *start, long int *size,
253 gid_t **groupsp, long int limit, int *errnop)
255 enum nss_status status;
256 struct group grpbuf;
258 /* if this module does not support getgrent_r and initgroups_dyn,
259 abort. We cannot find the needed group entries. */
260 if (nss_getgrent_r == NULL && nss_initgroups_dyn == NULL)
261 return NSS_STATUS_UNAVAIL;
263 /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
264 If this function is not supported, step through the whole group
265 database with getgrent_r. */
266 if (nss_initgroups_dyn && nss_getgrgid_r)
268 long int mystart = 0;
269 long int mysize = limit <= 0 ? *size : limit;
270 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
272 if (mygroups == NULL)
273 return NSS_STATUS_TRYAGAIN;
275 /* For every gid in the list we get from the NSS module,
276 get the whole group entry. We need to do this, since we
277 need the group name to check if it is in the blacklist.
278 In worst case, this is as twice as slow as stepping with
279 getgrent_r through the whole group database. But for large
280 group databases this is faster, since the user can only be
281 in a limited number of groups. */
282 if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
283 limit, errnop) == NSS_STATUS_SUCCESS)
285 /* A temporary buffer. We use the normal buffer, until we find
286 an entry, for which this buffer is to small. In this case, we
287 overwrite the pointer with one to a bigger buffer. */
288 char *tmpbuf = buffer;
289 size_t tmplen = buflen;
290 int i;
292 for (i = 0; i < mystart; i++)
294 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf, tmpbuf,
295 tmplen,
296 errnop)) == NSS_STATUS_TRYAGAIN
297 && *errnop == ERANGE)
298 if (tmpbuf == buffer)
300 tmplen *= 2;
301 tmpbuf = __alloca (tmplen);
303 else
304 tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen);
306 if (__builtin_expect (status != NSS_STATUS_NOTFOUND, 1))
308 if (__builtin_expect (status != NSS_STATUS_SUCCESS, 0))
310 free (mygroups);
311 return status;
314 if (!in_blacklist (grpbuf.gr_name,
315 strlen (grpbuf.gr_name), ent))
316 check_and_add_group (user, group, start, size, groupsp,
317 limit, &grpbuf);
321 free (mygroups);
323 return NSS_STATUS_NOTFOUND;
326 free (mygroups);
329 /* If we come here, the NSS module does not support initgroups_dyn
330 and we have to step through the whole list ourself. */
333 if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
334 NSS_STATUS_SUCCESS)
335 return status;
337 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
339 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
340 return NSS_STATUS_SUCCESS;
343 static enum nss_status
344 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
345 gid_t group, long int *start, long int *size,
346 gid_t **groupsp, long int limit, int *errnop)
348 struct parser_data *data = (void *) buffer;
349 struct group grpbuf;
351 if (!ent->files)
352 return getgrent_next_nss (ent, buffer, buflen, user, group,
353 start, size, groupsp, limit, errnop);
355 while (1)
357 fpos_t pos;
358 int parse_res = 0;
359 char *p;
363 /* We need at least 3 characters for one line. */
364 if (__builtin_expect (buflen < 3, 0))
366 erange:
367 *errnop = ERANGE;
368 return NSS_STATUS_TRYAGAIN;
371 fgetpos (ent->stream, &pos);
372 buffer[buflen - 1] = '\xff';
373 p = fgets_unlocked (buffer, buflen, ent->stream);
374 if (p == NULL && feof_unlocked (ent->stream))
375 return NSS_STATUS_NOTFOUND;
377 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
379 erange_reset:
380 fsetpos (ent->stream, &pos);
381 goto erange;
384 /* Terminate the line for any case. */
385 buffer[buflen - 1] = '\0';
387 /* Skip leading blanks. */
388 while (isspace (*p))
389 ++p;
391 while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
392 /* Parse the line. If it is invalid, loop to
393 get the next line of the file to parse. */
394 !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
395 errnop)));
397 if (__builtin_expect (parse_res == -1, 0))
398 /* The parser ran out of space. */
399 goto erange_reset;
401 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
402 /* This is a real entry. */
403 break;
405 /* -group */
406 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
407 && grpbuf.gr_name[1] != '@')
409 blacklist_store_name (&grpbuf.gr_name[1], ent);
410 continue;
413 /* +group */
414 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
415 && grpbuf.gr_name[1] != '@')
417 if (in_blacklist (&grpbuf.gr_name[1],
418 strlen (&grpbuf.gr_name[1]), ent))
419 continue;
420 /* Store the group in the blacklist for the "+" at the end of
421 /etc/group */
422 blacklist_store_name (&grpbuf.gr_name[1], ent);
423 if (nss_getgrnam_r == NULL)
424 return NSS_STATUS_UNAVAIL;
425 else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer,
426 buflen, errnop) != NSS_STATUS_SUCCESS)
427 continue;
429 check_and_add_group (user, group, start, size, groupsp,
430 limit, &grpbuf);
432 return NSS_STATUS_SUCCESS;
435 /* +:... */
436 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
438 ent->files = FALSE;
439 return getgrent_next_nss (ent, buffer, buflen, user, group,
440 start, size, groupsp, limit, errnop);
444 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
446 return NSS_STATUS_SUCCESS;
450 enum nss_status
451 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
452 long int *size, gid_t **groupsp, long int limit,
453 int *errnop)
455 size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
456 char *tmpbuf;
457 enum nss_status status;
458 ent_t intern = { TRUE, NULL, {NULL, 0, 0} };
460 status = internal_setgrent (&intern);
461 if (status != NSS_STATUS_SUCCESS)
462 return status;
464 tmpbuf = __alloca (buflen);
468 while ((status = internal_getgrent_r (&intern, tmpbuf, buflen,
469 user, group, start, size,
470 groupsp, limit, errnop))
471 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
472 tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
474 while (status == NSS_STATUS_SUCCESS);
476 internal_endgrent (&intern);
478 return NSS_STATUS_SUCCESS;
482 /* Support routines for remembering -@netgroup and -user entries.
483 The names are stored in a single string with `|' as separator. */
484 static void
485 blacklist_store_name (const char *name, ent_t *ent)
487 int namelen = strlen (name);
488 char *tmp;
490 /* First call, setup cache. */
491 if (ent->blacklist.size == 0)
493 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
494 ent->blacklist.data = malloc (ent->blacklist.size);
495 if (ent->blacklist.data == NULL)
496 return;
497 ent->blacklist.data[0] = '|';
498 ent->blacklist.data[1] = '\0';
499 ent->blacklist.current = 1;
501 else
503 if (in_blacklist (name, namelen, ent))
504 return; /* no duplicates */
506 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
508 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
509 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
510 if (tmp == NULL)
512 free (ent->blacklist.data);
513 ent->blacklist.size = 0;
514 return;
516 ent->blacklist.data = tmp;
520 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
521 *tmp++ = '|';
522 *tmp = '\0';
523 ent->blacklist.current += namelen + 1;
525 return;
528 /* returns TRUE if ent->blacklist contains name, else FALSE */
529 static bool_t
530 in_blacklist (const char *name, int namelen, ent_t *ent)
532 char buf[namelen + 3];
533 char *cp;
535 if (ent->blacklist.data == NULL)
536 return FALSE;
538 buf[0] = '|';
539 cp = stpcpy (&buf[1], name);
540 *cp++ = '|';
541 *cp = '\0';
542 return strstr (ent->blacklist.data, buf) != NULL;