]> git.ipfire.org Git - thirdparty/glibc.git/blame - nis/nss_compat/compat-initgroups.c
Use <> for include of kernel-features.h.
[thirdparty/glibc.git] / nis / nss_compat / compat-initgroups.c
CommitLineData
eaca7569 1/* Copyright (C) 1998-2004,2006,2007,2009,2010 Free Software Foundation, Inc.
899d423e 2 This file is part of the GNU C Library.
3b965a7d 3 Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
899d423e
UD
4
5 The GNU C Library is free software; you can redistribute it and/or
41bdb6e2
AJ
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.
899d423e
UD
9
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
41bdb6e2 13 Lesser General Public License for more details.
899d423e 14
41bdb6e2
AJ
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. */
899d423e 19
bbca27a4
UD
20#include <alloca.h>
21#include <ctype.h>
899d423e
UD
22#include <errno.h>
23#include <fcntl.h>
899d423e 24#include <grp.h>
bbca27a4
UD
25#include <nss.h>
26#include <stdio_ext.h>
899d423e
UD
27#include <string.h>
28#include <unistd.h>
bbca27a4 29#include <rpc/types.h>
7603ea28 30#include <sys/param.h>
899d423e 31#include <nsswitch.h>
bbca27a4 32#include <bits/libc-lock.h>
cc783763 33#include <kernel-features.h>
899d423e 34
fc9f33e3 35static service_user *ni;
bbca27a4
UD
36/* Type of the lookup function. */
37static enum nss_status (*nss_initgroups_dyn) (const char *, gid_t,
38 long int *, long int *,
39 gid_t **, long int, int *);
bbca27a4
UD
40static enum nss_status (*nss_getgrnam_r) (const char *name,
41 struct group * grp, char *buffer,
42 size_t buflen, int *errnop);
43static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
44 char *buffer, size_t buflen,
45 int *errnop);
ccab6d8f 46static enum nss_status (*nss_setgrent) (int stayopen);
bbca27a4
UD
47static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
48 size_t buflen, int *errnop);
ccab6d8f 49static enum nss_status (*nss_endgrent) (void);
bbca27a4
UD
50
51/* Protect global state against multiple changers. */
52__libc_lock_define_initialized (static, lock)
53
899d423e
UD
54
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>
60
61/* Structure for remembering -group members ... */
62#define BLACKLIST_INITIAL_SIZE 512
63#define BLACKLIST_INCREMENT 256
64struct blacklist_t
e36b0b57 65{
bbca27a4
UD
66 char *data;
67 int current;
68 int size;
e36b0b57
UD
69};
70
899d423e 71struct ent_t
bbca27a4 72{
ccab6d8f
UD
73 bool files;
74 bool need_endgrent;
75 bool skip_initgroups_dyn;
bbca27a4
UD
76 FILE *stream;
77 struct blacklist_t blacklist;
899d423e
UD
78};
79typedef struct ent_t ent_t;
80
81
cc783763
UD
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
89extern int __compat_have_cloexec;
90# else
91# define __compat_have_cloexec -1
92# endif
93#endif
94
899d423e
UD
95/* Prototypes for local functions. */
96static void blacklist_store_name (const char *, ent_t *);
97static int in_blacklist (const char *, int, ent_t *);
98
bbca27a4
UD
99/* Initialize the NSS interface/functions. The calling function must
100 hold the lock. */
101static void
102init_nss_interface (void)
899d423e 103{
bbca27a4 104 __libc_lock_lock (lock);
899d423e 105
bbca27a4
UD
106 /* Retest. */
107 if (ni == NULL
108 && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
899d423e 109 {
bbca27a4 110 nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
bbca27a4
UD
111 nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
112 nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
ccab6d8f 113 nss_setgrent = __nss_lookup_function (ni, "setgrent");
bbca27a4 114 nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
ccab6d8f 115 nss_endgrent = __nss_lookup_function (ni, "endgrent");
899d423e
UD
116 }
117
bbca27a4 118 __libc_lock_unlock (lock);
899d423e
UD
119}
120
121static enum nss_status
122internal_setgrent (ent_t *ent)
123{
124 enum nss_status status = NSS_STATUS_SUCCESS;
125
ccab6d8f 126 ent->files = true;
899d423e 127
bbca27a4
UD
128 if (ni == NULL)
129 init_nss_interface ();
899d423e
UD
130
131 if (ent->blacklist.data != NULL)
132 {
133 ent->blacklist.current = 1;
134 ent->blacklist.data[0] = '|';
135 ent->blacklist.data[1] = '\0';
136 }
137 else
138 ent->blacklist.current = 0;
139
cc783763 140 ent->stream = fopen ("/etc/group", "rme");
bbca27a4 141
899d423e 142 if (ent->stream == NULL)
bbca27a4
UD
143 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
144 else
899d423e 145 {
bbca27a4 146 /* We have to make sure the file is `closed on exec'. */
cc783763 147 int result = 0;
899d423e 148
cc783763 149 if (__compat_have_cloexec <= 0)
899d423e 150 {
cc783763
UD
151 int flags;
152 result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
153 if (result >= 0)
154 {
155#if defined O_CLOEXEC && !defined __ASSUME_O_CLOEXEC
156 if (__compat_have_cloexec == 0)
157 __compat_have_cloexec = (flags & FD_CLOEXEC) ? 1 : -1;
158
159 if (__compat_have_cloexec < 0)
160#endif
161 {
162 flags |= FD_CLOEXEC;
163 result = fcntl (fileno_unlocked (ent->stream), F_SETFD,
164 flags);
165 }
166 }
bbca27a4 167 }
cc783763 168
bbca27a4
UD
169 if (result < 0)
170 {
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;
899d423e 176 }
bbca27a4
UD
177 else
178 /* We take care of locking ourself. */
179 __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
899d423e 180 }
899d423e
UD
181
182 return status;
183}
184
185
186static enum nss_status
187internal_endgrent (ent_t *ent)
188{
189 if (ent->stream != NULL)
190 {
191 fclose (ent->stream);
192 ent->stream = NULL;
193 }
194
899d423e
UD
195 if (ent->blacklist.data != NULL)
196 {
197 ent->blacklist.current = 1;
198 ent->blacklist.data[0] = '|';
199 ent->blacklist.data[1] = '\0';
200 }
201 else
202 ent->blacklist.current = 0;
203
ccab6d8f
UD
204 if (ent->need_endgrent && nss_endgrent != NULL)
205 nss_endgrent ();
206
899d423e
UD
207 return NSS_STATUS_SUCCESS;
208}
209
ccab6d8f 210/* Add new group record. */
bbca27a4 211static void
ccab6d8f
UD
212add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
213 gid_t gid)
214{
215 gid_t *groups = *groupsp;
216
217 /* Matches user. Insert this group. */
218 if (__builtin_expect (*start == *size, 0))
219 {
220 /* Need a bigger buffer. */
221 gid_t *newgroups;
222 long int newsize;
223
224 if (limit > 0 && *size == limit)
225 /* We reached the maximum. */
226 return;
227
228 if (limit <= 0)
229 newsize = 2 * *size;
230 else
231 newsize = MIN (limit, 2 * *size);
232
233 newgroups = realloc (groups, newsize * sizeof (*groups));
234 if (newgroups == NULL)
235 return;
236 *groupsp = groups = newgroups;
237 *size = newsize;
238 }
239
240 groups[*start] = gid;
241 *start += 1;
242}
243
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. */
247static int
bbca27a4
UD
248check_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)
899d423e 251{
bbca27a4
UD
252 char **member;
253
254 /* Don't add main group to list of groups. */
255 if (grp->gr_gid == group)
ccab6d8f 256 return 0;
bbca27a4
UD
257
258 for (member = grp->gr_mem; *member != NULL; ++member)
259 if (strcmp (*member, user) == 0)
260 {
ccab6d8f
UD
261 add_group (start, size, groupsp, limit, grp->gr_gid);
262 return 0;
bbca27a4 263 }
ccab6d8f
UD
264
265 return 1;
899d423e
UD
266}
267
3426e770 268/* Get the next group from NSS (+ entry). If the NSS module supports
bbca27a4 269 initgroups_dyn, get all entries at once. */
899d423e 270static enum nss_status
bbca27a4
UD
271getgrent_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)
899d423e 274{
bbca27a4
UD
275 enum nss_status status;
276 struct group grpbuf;
899d423e 277
bbca27a4
UD
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. */
ccab6d8f 281 if (! ent->skip_initgroups_dyn)
899d423e 282 {
3426e770 283 long int mystart = 0;
8583671d 284 long int mysize = limit <= 0 ? *size : limit;
afd7b703 285 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
3426e770 286
afd7b703 287 if (mygroups == NULL)
3426e770 288 return NSS_STATUS_TRYAGAIN;
bbca27a4
UD
289
290 /* For every gid in the list we get from the NSS module,
eaca7569
UD
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. */
afd7b703 297 if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
bbca27a4 298 limit, errnop) == NSS_STATUS_SUCCESS)
899d423e 299 {
ccab6d8f
UD
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
bbca27a4 306 {
ccab6d8f
UD
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;
312
313 for (int i = 0; i < mystart; i++)
85883123 314 {
ccab6d8f
UD
315 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf,
316 tmpbuf, tmplen, errnop))
317 == NSS_STATUS_TRYAGAIN
318 && *errnop == ERANGE)
319 if (tmpbuf == buffer)
320 {
321 tmplen *= 2;
322 tmpbuf = __alloca (tmplen);
323 }
324 else
325 tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen);
326
327 if (__builtin_expect (status != NSS_STATUS_NOTFOUND, 1))
85883123 328 {
ccab6d8f
UD
329 if (__builtin_expect (status != NSS_STATUS_SUCCESS, 0))
330 {
331 free (mygroups);
332 return status;
333 }
334
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))
339 {
340 if (nss_setgrent != NULL)
341 {
342 nss_setgrent (1);
343 ent->need_endgrent = true;
344 }
345 ent->skip_initgroups_dyn = true;
346
347 goto iter;
348 }
85883123 349 }
85883123 350 }
bbca27a4 351 }
3426e770 352
afd7b703 353 free (mygroups);
3426e770 354
bbca27a4 355 return NSS_STATUS_NOTFOUND;
899d423e 356 }
3426e770 357
afd7b703 358 free (mygroups);
bbca27a4 359 }
899d423e 360
bbca27a4 361 /* If we come here, the NSS module does not support initgroups_dyn
ccab6d8f
UD
362 or we were confronted with a split group. In these cases we have
363 to step through the whole list ourself. */
364 iter:
bbca27a4
UD
365 do
366 {
367 if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
368 NSS_STATUS_SUCCESS)
ccab6d8f 369 break;
899d423e 370 }
bbca27a4 371 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
899d423e 372
ccab6d8f
UD
373 if (status == NSS_STATUS_SUCCESS)
374 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
375
376 return status;
899d423e
UD
377}
378
899d423e 379static enum nss_status
bbca27a4
UD
380internal_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)
899d423e
UD
383{
384 struct parser_data *data = (void *) buffer;
bbca27a4 385 struct group grpbuf;
899d423e 386
bbca27a4
UD
387 if (!ent->files)
388 return getgrent_next_nss (ent, buffer, buflen, user, group,
389 start, size, groupsp, limit, errnop);
899d423e 390
899d423e
UD
391 while (1)
392 {
393 fpos_t pos;
394 int parse_res = 0;
395 char *p;
396
397 do
398 {
20f8e666
UD
399 /* We need at least 3 characters for one line. */
400 if (__builtin_expect (buflen < 3, 0))
401 {
402 erange:
403 *errnop = ERANGE;
404 return NSS_STATUS_TRYAGAIN;
405 }
406
899d423e
UD
407 fgetpos (ent->stream, &pos);
408 buffer[buflen - 1] = '\xff';
bbca27a4
UD
409 p = fgets_unlocked (buffer, buflen, ent->stream);
410 if (p == NULL && feof_unlocked (ent->stream))
34816665
UD
411 return NSS_STATUS_NOTFOUND;
412
20f8e666 413 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
899d423e 414 {
20f8e666 415 erange_reset:
899d423e 416 fsetpos (ent->stream, &pos);
20f8e666 417 goto erange;
899d423e
UD
418 }
419
420 /* Terminate the line for any case. */
421 buffer[buflen - 1] = '\0';
422
423 /* Skip leading blanks. */
424 while (isspace (*p))
425 ++p;
426 }
bbca27a4
UD
427 while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
428 /* Parse the line. If it is invalid, loop to
eaca7569 429 get the next line of the file to parse. */
bbca27a4 430 !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
899d423e
UD
431 errnop)));
432
20f8e666
UD
433 if (__builtin_expect (parse_res == -1, 0))
434 /* The parser ran out of space. */
435 goto erange_reset;
899d423e 436
bbca27a4 437 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
899d423e
UD
438 /* This is a real entry. */
439 break;
440
441 /* -group */
bbca27a4
UD
442 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
443 && grpbuf.gr_name[1] != '@')
899d423e 444 {
bbca27a4 445 blacklist_store_name (&grpbuf.gr_name[1], ent);
899d423e
UD
446 continue;
447 }
448
449 /* +group */
bbca27a4
UD
450 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
451 && grpbuf.gr_name[1] != '@')
899d423e 452 {
bbca27a4
UD
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
899d423e 457 /etc/group */
bbca27a4
UD
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;
464
465 check_and_add_group (user, group, start, size, groupsp,
466 limit, &grpbuf);
467
468 return NSS_STATUS_SUCCESS;
899d423e
UD
469 }
470
471 /* +:... */
bbca27a4 472 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
899d423e 473 {
ccab6d8f
UD
474 /* If the selected module does not support getgrent_r or
475 initgroups_dyn, abort. We cannot find the needed group
476 entries. */
3056dcdb
UD
477 if (nss_initgroups_dyn == NULL || nss_getgrgid_r == NULL)
478 {
479 if (nss_setgrent != NULL)
eaca7569 480 {
3056dcdb
UD
481 nss_setgrent (1);
482 ent->need_endgrent = true;
483 }
484 ent->skip_initgroups_dyn = true;
3056dcdb 485
eaca7569
UD
486 if (nss_getgrent_r == NULL)
487 return NSS_STATUS_UNAVAIL;
488 }
ccab6d8f
UD
489
490 ent->files = false;
491
bbca27a4
UD
492 return getgrent_next_nss (ent, buffer, buflen, user, group,
493 start, size, groupsp, limit, errnop);
899d423e
UD
494 }
495 }
496
bbca27a4
UD
497 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
498
899d423e
UD
499 return NSS_STATUS_SUCCESS;
500}
501
502
899d423e 503enum nss_status
cf9e9ad9 504_nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
7603ea28
UD
505 long int *size, gid_t **groupsp, long int limit,
506 int *errnop)
899d423e 507{
899d423e
UD
508 size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
509 char *tmpbuf;
510 enum nss_status status;
ccab6d8f 511 ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
899d423e
UD
512
513 status = internal_setgrent (&intern);
514 if (status != NSS_STATUS_SUCCESS)
515 return status;
516
517 tmpbuf = __alloca (buflen);
518
519 do
520 {
bbca27a4
UD
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);
899d423e
UD
526 }
527 while (status == NSS_STATUS_SUCCESS);
528
899d423e
UD
529 internal_endgrent (&intern);
530
531 return NSS_STATUS_SUCCESS;
532}
533
534
535/* Support routines for remembering -@netgroup and -user entries.
536 The names are stored in a single string with `|' as separator. */
537static void
538blacklist_store_name (const char *name, ent_t *ent)
539{
540 int namelen = strlen (name);
541 char *tmp;
542
bbca27a4 543 /* First call, setup cache. */
899d423e
UD
544 if (ent->blacklist.size == 0)
545 {
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;
553 }
554 else
555 {
556 if (in_blacklist (name, namelen, ent))
557 return; /* no duplicates */
558
559 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
560 {
561 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
562 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
563 if (tmp == NULL)
564 {
565 free (ent->blacklist.data);
566 ent->blacklist.size = 0;
567 return;
568 }
569 ent->blacklist.data = tmp;
570 }
571 }
572
573 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
574 *tmp++ = '|';
575 *tmp = '\0';
576 ent->blacklist.current += namelen + 1;
577
578 return;
579}
580
581/* returns TRUE if ent->blacklist contains name, else FALSE */
582static bool_t
583in_blacklist (const char *name, int namelen, ent_t *ent)
584{
585 char buf[namelen + 3];
586 char *cp;
587
588 if (ent->blacklist.data == NULL)
589 return FALSE;
590
591 buf[0] = '|';
592 cp = stpcpy (&buf[1], name);
bbca27a4 593 *cp++ = '|';
899d423e
UD
594 *cp = '\0';
595 return strstr (ent->blacklist.data, buf) != NULL;
596}