2.5-18.1
[glibc.git] / nis / nss_compat / compat-initgroups.c
blob70403a0785edf711108a8c6e1a3b0141db1140e9
1 /* Copyright (C) 1998-2003, 2004, 2006 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>
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_getgrent_r) (struct group * grp, char *buffer,
46 size_t buflen, int *errnop);
48 /* Protect global state against multiple changers. */
49 __libc_lock_define_initialized (static, lock)
52 /* Get the declaration of the parser function. */
53 #define ENTNAME grent
54 #define STRUCTURE group
55 #define EXTERN_PARSER
56 #include <nss/nss_files/files-parse.c>
58 /* Structure for remembering -group members ... */
59 #define BLACKLIST_INITIAL_SIZE 512
60 #define BLACKLIST_INCREMENT 256
61 struct blacklist_t
63 char *data;
64 int current;
65 int size;
68 struct ent_t
70 bool_t files;
71 FILE *stream;
72 struct blacklist_t blacklist;
74 typedef struct ent_t ent_t;
77 /* Prototypes for local functions. */
78 static void blacklist_store_name (const char *, ent_t *);
79 static int in_blacklist (const char *, int, ent_t *);
81 /* Initialize the NSS interface/functions. The calling function must
82 hold the lock. */
83 static void
84 init_nss_interface (void)
86 __libc_lock_lock (lock);
88 /* Retest. */
89 if (ni == NULL
90 && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
92 nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
93 nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
94 nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
95 nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
98 __libc_lock_unlock (lock);
101 static enum nss_status
102 internal_setgrent (ent_t *ent)
104 enum nss_status status = NSS_STATUS_SUCCESS;
106 ent->files = TRUE;
108 if (ni == NULL)
109 init_nss_interface ();
111 if (ent->blacklist.data != NULL)
113 ent->blacklist.current = 1;
114 ent->blacklist.data[0] = '|';
115 ent->blacklist.data[1] = '\0';
117 else
118 ent->blacklist.current = 0;
120 ent->stream = fopen ("/etc/group", "rm");
122 if (ent->stream == NULL)
123 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
124 else
126 /* We have to make sure the file is `closed on exec'. */
127 int result, flags;
129 result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
130 if (result >= 0)
132 flags |= FD_CLOEXEC;
133 result = fcntl (fileno_unlocked (ent->stream), F_SETFD, flags);
135 if (result < 0)
137 /* Something went wrong. Close the stream and return a
138 failure. */
139 fclose (ent->stream);
140 ent->stream = NULL;
141 status = NSS_STATUS_UNAVAIL;
143 else
144 /* We take care of locking ourself. */
145 __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
148 return status;
152 static enum nss_status
153 internal_endgrent (ent_t *ent)
155 if (ent->stream != NULL)
157 fclose (ent->stream);
158 ent->stream = NULL;
161 if (ent->blacklist.data != NULL)
163 ent->blacklist.current = 1;
164 ent->blacklist.data[0] = '|';
165 ent->blacklist.data[1] = '\0';
167 else
168 ent->blacklist.current = 0;
170 return NSS_STATUS_SUCCESS;
173 /* This function checks, if the user is a member of this group and if
174 yes, add the group id to the list. */
175 static void
176 check_and_add_group (const char *user, gid_t group, long int *start,
177 long int *size, gid_t **groupsp, long int limit,
178 struct group *grp)
180 gid_t *groups = *groupsp;
181 char **member;
183 /* Don't add main group to list of groups. */
184 if (grp->gr_gid == group)
185 return;
187 for (member = grp->gr_mem; *member != NULL; ++member)
188 if (strcmp (*member, user) == 0)
190 /* Matches user. Insert this group. */
191 if (*start == *size)
193 /* Need a bigger buffer. */
194 gid_t *newgroups;
195 long int newsize;
197 if (limit > 0 && *size == limit)
198 /* We reached the maximum. */
199 return;
201 if (limit <= 0)
202 newsize = 2 * *size;
203 else
204 newsize = MIN (limit, 2 * *size);
206 newgroups = realloc (groups, newsize * sizeof (*groups));
207 if (newgroups == NULL)
208 return;
209 *groupsp = groups = newgroups;
210 *size = newsize;
213 groups[*start] = grp->gr_gid;
214 *start += 1;
216 break;
220 /* Get the next group from NSS (+ entry). If the NSS module supports
221 initgroups_dyn, get all entries at once. */
222 static enum nss_status
223 getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
224 gid_t group, long int *start, long int *size,
225 gid_t **groupsp, long int limit, int *errnop)
227 enum nss_status status;
228 struct group grpbuf;
230 /* if this module does not support getgrent_r and initgroups_dyn,
231 abort. We cannot find the needed group entries. */
232 if (nss_getgrent_r == NULL && nss_initgroups_dyn == NULL)
233 return NSS_STATUS_UNAVAIL;
235 /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
236 If this function is not supported, step through the whole group
237 database with getgrent_r. */
238 if (nss_initgroups_dyn && nss_getgrgid_r)
240 long int mystart = 0;
241 long int mysize = limit <= 0 ? *size : limit;
242 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
244 if (mygroups == NULL)
245 return NSS_STATUS_TRYAGAIN;
247 /* For every gid in the list we get from the NSS module,
248 get the whole group entry. We need to do this, since we
249 need the group name to check if it is in the blacklist.
250 In worst case, this is as twice as slow as stepping with
251 getgrent_r through the whole group database. But for large
252 group databases this is faster, since the user can only be
253 in a limited number of groups. */
254 if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
255 limit, errnop) == NSS_STATUS_SUCCESS)
257 /* A temporary buffer. We use the normal buffer, until we find
258 an entry, for which this buffer is to small. In this case, we
259 overwrite the pointer with one to a bigger buffer. */
260 char *tmpbuf = buffer;
261 size_t tmplen = buflen;
262 int i;
264 for (i = 0; i < mystart; i++)
266 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf, tmpbuf,
267 tmplen,
268 errnop)) == NSS_STATUS_TRYAGAIN
269 && *errnop == ERANGE)
270 if (tmpbuf == buffer)
272 tmplen *= 2;
273 tmpbuf = __alloca (tmplen);
275 else
276 tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen);
278 if (!in_blacklist (grpbuf.gr_name,
279 strlen (grpbuf.gr_name), ent))
280 check_and_add_group (user, group, start, size, groupsp,
281 limit, &grpbuf);
284 free (mygroups);
286 return NSS_STATUS_NOTFOUND;
289 free (mygroups);
292 /* If we come here, the NSS module does not support initgroups_dyn
293 and we have to step through the whole list ourself. */
296 if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
297 NSS_STATUS_SUCCESS)
298 return status;
300 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
302 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
303 return NSS_STATUS_SUCCESS;
306 static enum nss_status
307 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
308 gid_t group, long int *start, long int *size,
309 gid_t **groupsp, long int limit, int *errnop)
311 struct parser_data *data = (void *) buffer;
312 struct group grpbuf;
314 if (!ent->files)
315 return getgrent_next_nss (ent, buffer, buflen, user, group,
316 start, size, groupsp, limit, errnop);
318 while (1)
320 fpos_t pos;
321 int parse_res = 0;
322 char *p;
326 /* We need at least 3 characters for one line. */
327 if (__builtin_expect (buflen < 3, 0))
329 erange:
330 *errnop = ERANGE;
331 return NSS_STATUS_TRYAGAIN;
334 fgetpos (ent->stream, &pos);
335 buffer[buflen - 1] = '\xff';
336 p = fgets_unlocked (buffer, buflen, ent->stream);
337 if (p == NULL && feof_unlocked (ent->stream))
338 return NSS_STATUS_NOTFOUND;
340 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
342 erange_reset:
343 fsetpos (ent->stream, &pos);
344 goto erange;
347 /* Terminate the line for any case. */
348 buffer[buflen - 1] = '\0';
350 /* Skip leading blanks. */
351 while (isspace (*p))
352 ++p;
354 while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
355 /* Parse the line. If it is invalid, loop to
356 get the next line of the file to parse. */
357 !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
358 errnop)));
360 if (__builtin_expect (parse_res == -1, 0))
361 /* The parser ran out of space. */
362 goto erange_reset;
364 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
365 /* This is a real entry. */
366 break;
368 /* -group */
369 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
370 && grpbuf.gr_name[1] != '@')
372 blacklist_store_name (&grpbuf.gr_name[1], ent);
373 continue;
376 /* +group */
377 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
378 && grpbuf.gr_name[1] != '@')
380 if (in_blacklist (&grpbuf.gr_name[1],
381 strlen (&grpbuf.gr_name[1]), ent))
382 continue;
383 /* Store the group in the blacklist for the "+" at the end of
384 /etc/group */
385 blacklist_store_name (&grpbuf.gr_name[1], ent);
386 if (nss_getgrnam_r == NULL)
387 return NSS_STATUS_UNAVAIL;
388 else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer,
389 buflen, errnop) != NSS_STATUS_SUCCESS)
390 continue;
392 check_and_add_group (user, group, start, size, groupsp,
393 limit, &grpbuf);
395 return NSS_STATUS_SUCCESS;
398 /* +:... */
399 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
401 ent->files = FALSE;
402 return getgrent_next_nss (ent, buffer, buflen, user, group,
403 start, size, groupsp, limit, errnop);
407 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
409 return NSS_STATUS_SUCCESS;
413 enum nss_status
414 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
415 long int *size, gid_t **groupsp, long int limit,
416 int *errnop)
418 size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
419 char *tmpbuf;
420 enum nss_status status;
421 ent_t intern = { TRUE, NULL, {NULL, 0, 0} };
423 status = internal_setgrent (&intern);
424 if (status != NSS_STATUS_SUCCESS)
425 return status;
427 tmpbuf = __alloca (buflen);
431 while ((status = internal_getgrent_r (&intern, tmpbuf, buflen,
432 user, group, start, size,
433 groupsp, limit, errnop))
434 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
435 tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
437 while (status == NSS_STATUS_SUCCESS);
439 internal_endgrent (&intern);
441 return NSS_STATUS_SUCCESS;
445 /* Support routines for remembering -@netgroup and -user entries.
446 The names are stored in a single string with `|' as separator. */
447 static void
448 blacklist_store_name (const char *name, ent_t *ent)
450 int namelen = strlen (name);
451 char *tmp;
453 /* First call, setup cache. */
454 if (ent->blacklist.size == 0)
456 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
457 ent->blacklist.data = malloc (ent->blacklist.size);
458 if (ent->blacklist.data == NULL)
459 return;
460 ent->blacklist.data[0] = '|';
461 ent->blacklist.data[1] = '\0';
462 ent->blacklist.current = 1;
464 else
466 if (in_blacklist (name, namelen, ent))
467 return; /* no duplicates */
469 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
471 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
472 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
473 if (tmp == NULL)
475 free (ent->blacklist.data);
476 ent->blacklist.size = 0;
477 return;
479 ent->blacklist.data = tmp;
483 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
484 *tmp++ = '|';
485 *tmp = '\0';
486 ent->blacklist.current += namelen + 1;
488 return;
491 /* returns TRUE if ent->blacklist contains name, else FALSE */
492 static bool_t
493 in_blacklist (const char *name, int namelen, ent_t *ent)
495 char buf[namelen + 3];
496 char *cp;
498 if (ent->blacklist.data == NULL)
499 return FALSE;
501 buf[0] = '|';
502 cp = stpcpy (&buf[1], name);
503 *cp++ = '|';
504 *cp = '\0';
505 return strstr (ent->blacklist.data, buf) != NULL;