(CFLAGS-tst-align.c): Add -mpreferred-stack-boundary=4.
[glibc.git] / nis / nss_compat / compat-initgroups.c
blob9574ea7c0bee7a3662dc960f348f4065628b0d96
1 /* Copyright (C) 1998-2003, 2004 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_setgrent) (int stayopen);
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);
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_t files;
73 FILE *stream;
74 struct blacklist_t blacklist;
76 typedef struct ent_t ent_t;
79 /* Prototypes for local functions. */
80 static void blacklist_store_name (const char *, ent_t *);
81 static int in_blacklist (const char *, int, ent_t *);
83 /* Initialize the NSS interface/functions. The calling function must
84 hold the lock. */
85 static void
86 init_nss_interface (void)
88 __libc_lock_lock (lock);
90 /* Retest. */
91 if (ni == NULL
92 && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
94 nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
95 nss_setgrent = __nss_lookup_function (ni, "setgrent");
96 nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
97 nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
98 nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
99 nss_endgrent = __nss_lookup_function (ni, "endgrent");
102 __libc_lock_unlock (lock);
105 static enum nss_status
106 internal_setgrent (ent_t *ent)
108 enum nss_status status = NSS_STATUS_SUCCESS;
110 ent->files = TRUE;
112 if (ni == NULL)
113 init_nss_interface ();
115 if (ent->blacklist.data != NULL)
117 ent->blacklist.current = 1;
118 ent->blacklist.data[0] = '|';
119 ent->blacklist.data[1] = '\0';
121 else
122 ent->blacklist.current = 0;
124 ent->stream = fopen ("/etc/group", "rm");
126 if (ent->stream == NULL)
127 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
128 else
130 /* We have to make sure the file is `closed on exec'. */
131 int result, flags;
133 result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
134 if (result >= 0)
136 flags |= FD_CLOEXEC;
137 result = fcntl (fileno_unlocked (ent->stream), F_SETFD, flags);
139 if (result < 0)
141 /* Something went wrong. Close the stream and return a
142 failure. */
143 fclose (ent->stream);
144 ent->stream = NULL;
145 status = NSS_STATUS_UNAVAIL;
147 else
148 /* We take care of locking ourself. */
149 __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
152 return status;
156 static enum nss_status
157 internal_endgrent (ent_t *ent)
159 if (ent->stream != NULL)
161 fclose (ent->stream);
162 ent->stream = NULL;
165 if (ent->blacklist.data != NULL)
167 ent->blacklist.current = 1;
168 ent->blacklist.data[0] = '|';
169 ent->blacklist.data[1] = '\0';
171 else
172 ent->blacklist.current = 0;
174 return NSS_STATUS_SUCCESS;
177 /* This function checks, if the user is a member of this group and if
178 yes, add the group id to the list. */
179 static void
180 check_and_add_group (const char *user, gid_t group, long int *start,
181 long int *size, gid_t **groupsp, long int limit,
182 struct group *grp)
184 gid_t *groups = *groupsp;
185 char **member;
187 /* Don't add main group to list of groups. */
188 if (grp->gr_gid == group)
189 return;
191 for (member = grp->gr_mem; *member != NULL; ++member)
192 if (strcmp (*member, user) == 0)
194 /* Matches user. Insert this group. */
195 if (*start == *size)
197 /* Need a bigger buffer. */
198 gid_t *newgroups;
199 long int newsize;
201 if (limit > 0 && *size == limit)
202 /* We reached the maximum. */
203 return;
205 if (limit <= 0)
206 newsize = 2 * *size;
207 else
208 newsize = MIN (limit, 2 * *size);
210 newgroups = realloc (groups, newsize * sizeof (*groups));
211 if (newgroups == NULL)
212 return;
213 *groupsp = groups = newgroups;
214 *size = newsize;
217 groups[*start] = grp->gr_gid;
218 *start += 1;
220 break;
224 /* Get the next group from NSS (+ entry). If the NSS module supports
225 initgroups_dyn, get all entries at once. */
226 static enum nss_status
227 getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
228 gid_t group, long int *start, long int *size,
229 gid_t **groupsp, long int limit, int *errnop)
231 enum nss_status status;
232 struct group grpbuf;
234 /* if this module does not support getgrent_r and initgroups_dyn,
235 abort. We cannot find the needed group entries. */
236 if (nss_getgrent_r == NULL && nss_initgroups_dyn == NULL)
237 return NSS_STATUS_UNAVAIL;
239 /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
240 If this function is not supported, step through the whole group
241 database with getgrent_r. */
242 if (nss_initgroups_dyn && nss_getgrgid_r)
244 long int mystart = 0;
245 long int mysize = limit <= 0 ? *size : limit;
246 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
248 if (mygroups == NULL)
249 return NSS_STATUS_TRYAGAIN;
251 /* For every gid in the list we get from the NSS module,
252 get the whole group entry. We need to do this, since we
253 need the group name to check if it is in the blacklist.
254 In worst case, this is as twice as slow as stepping with
255 getgrent_r through the whole group database. But for large
256 group databases this is faster, since the user can only be
257 in a limited number of groups. */
258 if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
259 limit, errnop) == NSS_STATUS_SUCCESS)
261 /* A temporary buffer. We use the normal buffer, until we find
262 an entry, for which this buffer is to small. In this case, we
263 overwrite the pointer with one to a bigger buffer. */
264 char *tmpbuf = buffer;
265 size_t tmplen = buflen;
266 int i;
268 for (i = 0; i < mystart; i++)
270 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf, tmpbuf,
271 tmplen,
272 errnop)) == NSS_STATUS_TRYAGAIN
273 && *errnop == ERANGE)
274 if (tmpbuf == buffer)
276 tmplen *= 2;
277 tmpbuf = __alloca (tmplen);
279 else
280 tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen);
282 if (!in_blacklist (grpbuf.gr_name,
283 strlen (grpbuf.gr_name), ent))
284 check_and_add_group (user, group, start, size, groupsp,
285 limit, &grpbuf);
288 free (mygroups);
290 return NSS_STATUS_NOTFOUND;
293 free (mygroups);
296 /* If we come here, the NSS module does not support initgroups_dyn
297 and we have to step through the whole list ourself. */
300 if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
301 NSS_STATUS_SUCCESS)
302 return status;
304 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
306 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
307 return NSS_STATUS_SUCCESS;
310 static enum nss_status
311 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
312 gid_t group, long int *start, long int *size,
313 gid_t **groupsp, long int limit, int *errnop)
315 struct parser_data *data = (void *) buffer;
316 struct group grpbuf;
318 if (!ent->files)
319 return getgrent_next_nss (ent, buffer, buflen, user, group,
320 start, size, groupsp, limit, errnop);
322 while (1)
324 fpos_t pos;
325 int parse_res = 0;
326 char *p;
330 /* We need at least 3 characters for one line. */
331 if (__builtin_expect (buflen < 3, 0))
333 erange:
334 *errnop = ERANGE;
335 return NSS_STATUS_TRYAGAIN;
338 fgetpos (ent->stream, &pos);
339 buffer[buflen - 1] = '\xff';
340 p = fgets_unlocked (buffer, buflen, ent->stream);
341 if (p == NULL && feof_unlocked (ent->stream))
342 return NSS_STATUS_NOTFOUND;
344 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
346 erange_reset:
347 fsetpos (ent->stream, &pos);
348 goto erange;
351 /* Terminate the line for any case. */
352 buffer[buflen - 1] = '\0';
354 /* Skip leading blanks. */
355 while (isspace (*p))
356 ++p;
358 while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
359 /* Parse the line. If it is invalid, loop to
360 get the next line of the file to parse. */
361 !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
362 errnop)));
364 if (__builtin_expect (parse_res == -1, 0))
365 /* The parser ran out of space. */
366 goto erange_reset;
368 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
369 /* This is a real entry. */
370 break;
372 /* -group */
373 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
374 && grpbuf.gr_name[1] != '@')
376 blacklist_store_name (&grpbuf.gr_name[1], ent);
377 continue;
380 /* +group */
381 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
382 && grpbuf.gr_name[1] != '@')
384 if (in_blacklist (&grpbuf.gr_name[1],
385 strlen (&grpbuf.gr_name[1]), ent))
386 continue;
387 /* Store the group in the blacklist for the "+" at the end of
388 /etc/group */
389 blacklist_store_name (&grpbuf.gr_name[1], ent);
390 if (nss_getgrnam_r == NULL)
391 return NSS_STATUS_UNAVAIL;
392 else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer,
393 buflen, errnop) != NSS_STATUS_SUCCESS)
394 continue;
396 check_and_add_group (user, group, start, size, groupsp,
397 limit, &grpbuf);
399 return NSS_STATUS_SUCCESS;
402 /* +:... */
403 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
405 ent->files = FALSE;
406 return getgrent_next_nss (ent, buffer, buflen, user, group,
407 start, size, groupsp, limit, errnop);
411 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
413 return NSS_STATUS_SUCCESS;
417 enum nss_status
418 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
419 long int *size, gid_t **groupsp, long int limit,
420 int *errnop)
422 size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
423 char *tmpbuf;
424 enum nss_status status;
425 ent_t intern = { TRUE, NULL, {NULL, 0, 0} };
427 status = internal_setgrent (&intern);
428 if (status != NSS_STATUS_SUCCESS)
429 return status;
431 tmpbuf = __alloca (buflen);
435 while ((status = internal_getgrent_r (&intern, tmpbuf, buflen,
436 user, group, start, size,
437 groupsp, limit, errnop))
438 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
439 tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
441 while (status == NSS_STATUS_SUCCESS);
443 internal_endgrent (&intern);
445 return NSS_STATUS_SUCCESS;
449 /* Support routines for remembering -@netgroup and -user entries.
450 The names are stored in a single string with `|' as separator. */
451 static void
452 blacklist_store_name (const char *name, ent_t *ent)
454 int namelen = strlen (name);
455 char *tmp;
457 /* First call, setup cache. */
458 if (ent->blacklist.size == 0)
460 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
461 ent->blacklist.data = malloc (ent->blacklist.size);
462 if (ent->blacklist.data == NULL)
463 return;
464 ent->blacklist.data[0] = '|';
465 ent->blacklist.data[1] = '\0';
466 ent->blacklist.current = 1;
468 else
470 if (in_blacklist (name, namelen, ent))
471 return; /* no duplicates */
473 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
475 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
476 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
477 if (tmp == NULL)
479 free (ent->blacklist.data);
480 ent->blacklist.size = 0;
481 return;
483 ent->blacklist.data = tmp;
487 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
488 *tmp++ = '|';
489 *tmp = '\0';
490 ent->blacklist.current += namelen + 1;
492 return;
495 /* returns TRUE if ent->blacklist contains name, else FALSE */
496 static bool_t
497 in_blacklist (const char *name, int namelen, ent_t *ent)
499 char buf[namelen + 3];
500 char *cp;
502 if (ent->blacklist.data == NULL)
503 return FALSE;
505 buf[0] = '|';
506 cp = stpcpy (&buf[1], name);
507 *cp++ = '|';
508 *cp = '\0';
509 return strstr (ent->blacklist.data, buf) != NULL;