]> git.ipfire.org Git - thirdparty/glibc.git/blame - nis/nss_compat/compat-initgroups.c
Use glibc_likely instead __builtin_expect.
[thirdparty/glibc.git] / nis / nss_compat / compat-initgroups.c
CommitLineData
d4697bc9 1/* Copyright (C) 1998-2014 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 15 You should have received a copy of the GNU Lesser General Public
59ba27a6
PE
16 License along with the GNU C Library; if not, see
17 <http://www.gnu.org/licenses/>. */
899d423e 18
bbca27a4
UD
19#include <alloca.h>
20#include <ctype.h>
899d423e
UD
21#include <errno.h>
22#include <fcntl.h>
899d423e 23#include <grp.h>
bbca27a4
UD
24#include <nss.h>
25#include <stdio_ext.h>
899d423e
UD
26#include <string.h>
27#include <unistd.h>
bbca27a4 28#include <rpc/types.h>
7603ea28 29#include <sys/param.h>
899d423e 30#include <nsswitch.h>
bbca27a4 31#include <bits/libc-lock.h>
cc783763 32#include <kernel-features.h>
899d423e 33
fc9f33e3 34static service_user *ni;
bbca27a4
UD
35/* Type of the lookup function. */
36static enum nss_status (*nss_initgroups_dyn) (const char *, gid_t,
37 long int *, long int *,
38 gid_t **, long int, int *);
bbca27a4
UD
39static enum nss_status (*nss_getgrnam_r) (const char *name,
40 struct group * grp, char *buffer,
41 size_t buflen, int *errnop);
42static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
43 char *buffer, size_t buflen,
44 int *errnop);
ccab6d8f 45static enum nss_status (*nss_setgrent) (int stayopen);
bbca27a4
UD
46static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
47 size_t buflen, int *errnop);
ccab6d8f 48static enum nss_status (*nss_endgrent) (void);
bbca27a4
UD
49
50/* Protect global state against multiple changers. */
51__libc_lock_define_initialized (static, lock)
52
899d423e
UD
53
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>
59
60/* Structure for remembering -group members ... */
61#define BLACKLIST_INITIAL_SIZE 512
62#define BLACKLIST_INCREMENT 256
63struct blacklist_t
e36b0b57 64{
bbca27a4
UD
65 char *data;
66 int current;
67 int size;
e36b0b57
UD
68};
69
899d423e 70struct ent_t
bbca27a4 71{
ccab6d8f
UD
72 bool files;
73 bool need_endgrent;
74 bool skip_initgroups_dyn;
bbca27a4
UD
75 FILE *stream;
76 struct blacklist_t blacklist;
899d423e
UD
77};
78typedef struct ent_t ent_t;
79
80
cc783763
UD
81/* Positive if O_CLOEXEC is supported, negative if it is not supported,
82 zero if it is still undecided. This variable is shared with the
83 other compat functions. */
84#ifdef __ASSUME_O_CLOEXEC
85# define __compat_have_cloexec 1
86#else
87# ifdef O_CLOEXEC
88extern int __compat_have_cloexec;
89# else
90# define __compat_have_cloexec -1
91# endif
92#endif
93
899d423e
UD
94/* Prototypes for local functions. */
95static void blacklist_store_name (const char *, ent_t *);
96static int in_blacklist (const char *, int, ent_t *);
97
bbca27a4
UD
98/* Initialize the NSS interface/functions. The calling function must
99 hold the lock. */
100static void
101init_nss_interface (void)
899d423e 102{
bbca27a4 103 __libc_lock_lock (lock);
899d423e 104
bbca27a4
UD
105 /* Retest. */
106 if (ni == NULL
107 && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
899d423e 108 {
bbca27a4 109 nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
bbca27a4
UD
110 nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
111 nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
ccab6d8f 112 nss_setgrent = __nss_lookup_function (ni, "setgrent");
bbca27a4 113 nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
ccab6d8f 114 nss_endgrent = __nss_lookup_function (ni, "endgrent");
899d423e
UD
115 }
116
bbca27a4 117 __libc_lock_unlock (lock);
899d423e
UD
118}
119
120static enum nss_status
121internal_setgrent (ent_t *ent)
122{
123 enum nss_status status = NSS_STATUS_SUCCESS;
124
ccab6d8f 125 ent->files = true;
899d423e 126
bbca27a4
UD
127 if (ni == NULL)
128 init_nss_interface ();
899d423e
UD
129
130 if (ent->blacklist.data != NULL)
131 {
132 ent->blacklist.current = 1;
133 ent->blacklist.data[0] = '|';
134 ent->blacklist.data[1] = '\0';
135 }
136 else
137 ent->blacklist.current = 0;
138
cc783763 139 ent->stream = fopen ("/etc/group", "rme");
bbca27a4 140
899d423e 141 if (ent->stream == NULL)
bbca27a4
UD
142 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
143 else
899d423e 144 {
bbca27a4 145 /* We have to make sure the file is `closed on exec'. */
cc783763 146 int result = 0;
899d423e 147
cc783763 148 if (__compat_have_cloexec <= 0)
899d423e 149 {
cc783763
UD
150 int flags;
151 result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
152 if (result >= 0)
153 {
154#if defined O_CLOEXEC && !defined __ASSUME_O_CLOEXEC
155 if (__compat_have_cloexec == 0)
156 __compat_have_cloexec = (flags & FD_CLOEXEC) ? 1 : -1;
157
158 if (__compat_have_cloexec < 0)
159#endif
160 {
161 flags |= FD_CLOEXEC;
162 result = fcntl (fileno_unlocked (ent->stream), F_SETFD,
163 flags);
164 }
165 }
bbca27a4 166 }
cc783763 167
bbca27a4
UD
168 if (result < 0)
169 {
170 /* Something went wrong. Close the stream and return a
171 failure. */
172 fclose (ent->stream);
173 ent->stream = NULL;
174 status = NSS_STATUS_UNAVAIL;
899d423e 175 }
bbca27a4
UD
176 else
177 /* We take care of locking ourself. */
178 __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
899d423e 179 }
899d423e
UD
180
181 return status;
182}
183
184
185static enum nss_status
186internal_endgrent (ent_t *ent)
187{
188 if (ent->stream != NULL)
189 {
190 fclose (ent->stream);
191 ent->stream = NULL;
192 }
193
899d423e
UD
194 if (ent->blacklist.data != NULL)
195 {
196 ent->blacklist.current = 1;
197 ent->blacklist.data[0] = '|';
198 ent->blacklist.data[1] = '\0';
199 }
200 else
201 ent->blacklist.current = 0;
202
ccab6d8f
UD
203 if (ent->need_endgrent && nss_endgrent != NULL)
204 nss_endgrent ();
205
899d423e
UD
206 return NSS_STATUS_SUCCESS;
207}
208
ccab6d8f 209/* Add new group record. */
bbca27a4 210static void
ccab6d8f
UD
211add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
212 gid_t gid)
213{
214 gid_t *groups = *groupsp;
215
216 /* Matches user. Insert this group. */
a1ffb40e 217 if (__glibc_unlikely (*start == *size))
ccab6d8f
UD
218 {
219 /* Need a bigger buffer. */
220 gid_t *newgroups;
221 long int newsize;
222
223 if (limit > 0 && *size == limit)
224 /* We reached the maximum. */
225 return;
226
227 if (limit <= 0)
228 newsize = 2 * *size;
229 else
230 newsize = MIN (limit, 2 * *size);
231
232 newgroups = realloc (groups, newsize * sizeof (*groups));
233 if (newgroups == NULL)
234 return;
235 *groupsp = groups = newgroups;
236 *size = newsize;
237 }
238
239 groups[*start] = gid;
240 *start += 1;
241}
242
243/* This function checks, if the user is a member of this group and if
244 yes, add the group id to the list. Return nonzero is we couldn't
245 handle the group because the user is not in the member list. */
246static int
bbca27a4
UD
247check_and_add_group (const char *user, gid_t group, long int *start,
248 long int *size, gid_t **groupsp, long int limit,
249 struct group *grp)
899d423e 250{
bbca27a4
UD
251 char **member;
252
253 /* Don't add main group to list of groups. */
254 if (grp->gr_gid == group)
ccab6d8f 255 return 0;
bbca27a4
UD
256
257 for (member = grp->gr_mem; *member != NULL; ++member)
258 if (strcmp (*member, user) == 0)
259 {
ccab6d8f
UD
260 add_group (start, size, groupsp, limit, grp->gr_gid);
261 return 0;
bbca27a4 262 }
ccab6d8f
UD
263
264 return 1;
899d423e
UD
265}
266
3426e770 267/* Get the next group from NSS (+ entry). If the NSS module supports
bbca27a4 268 initgroups_dyn, get all entries at once. */
899d423e 269static enum nss_status
bbca27a4
UD
270getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user,
271 gid_t group, long int *start, long int *size,
272 gid_t **groupsp, long int limit, int *errnop)
899d423e 273{
bbca27a4
UD
274 enum nss_status status;
275 struct group grpbuf;
899d423e 276
bbca27a4
UD
277 /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
278 If this function is not supported, step through the whole group
279 database with getgrent_r. */
ccab6d8f 280 if (! ent->skip_initgroups_dyn)
899d423e 281 {
3426e770 282 long int mystart = 0;
8583671d 283 long int mysize = limit <= 0 ? *size : limit;
afd7b703 284 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
3426e770 285
afd7b703 286 if (mygroups == NULL)
3426e770 287 return NSS_STATUS_TRYAGAIN;
bbca27a4
UD
288
289 /* For every gid in the list we get from the NSS module,
eaca7569
UD
290 get the whole group entry. We need to do this, since we
291 need the group name to check if it is in the blacklist.
292 In worst case, this is as twice as slow as stepping with
293 getgrent_r through the whole group database. But for large
294 group databases this is faster, since the user can only be
295 in a limited number of groups. */
afd7b703 296 if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
bbca27a4 297 limit, errnop) == NSS_STATUS_SUCCESS)
899d423e 298 {
984a4237
JL
299 status = NSS_STATUS_NOTFOUND;
300
ccab6d8f
UD
301 /* If there is no blacklist we can trust the underlying
302 initgroups implementation. */
303 if (ent->blacklist.current <= 1)
304 for (int i = 0; i < mystart; i++)
305 add_group (start, size, groupsp, limit, mygroups[i]);
306 else
bbca27a4 307 {
ccab6d8f
UD
308 /* A temporary buffer. We use the normal buffer, until we find
309 an entry, for which this buffer is to small. In this case, we
310 overwrite the pointer with one to a bigger buffer. */
311 char *tmpbuf = buffer;
312 size_t tmplen = buflen;
984a4237 313 bool use_malloc = false;
ccab6d8f
UD
314
315 for (int i = 0; i < mystart; i++)
85883123 316 {
ccab6d8f
UD
317 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf,
318 tmpbuf, tmplen, errnop))
319 == NSS_STATUS_TRYAGAIN
320 && *errnop == ERANGE)
984a4237
JL
321 {
322 if (__libc_use_alloca (tmplen * 2))
323 {
324 if (tmpbuf == buffer)
325 {
326 tmplen *= 2;
327 tmpbuf = __alloca (tmplen);
328 }
329 else
330 tmpbuf = extend_alloca (tmpbuf, tmplen, tmplen * 2);
331 }
332 else
333 {
334 tmplen *= 2;
335 char *newbuf = realloc (use_malloc ? tmpbuf : NULL, tmplen);
336
337 if (newbuf == NULL)
338 {
339 status = NSS_STATUS_TRYAGAIN;
340 goto done;
341 }
342 use_malloc = true;
343 tmpbuf = newbuf;
344 }
345 }
ccab6d8f
UD
346
347 if (__builtin_expect (status != NSS_STATUS_NOTFOUND, 1))
85883123 348 {
ccab6d8f 349 if (__builtin_expect (status != NSS_STATUS_SUCCESS, 0))
984a4237 350 goto done;
ccab6d8f
UD
351
352 if (!in_blacklist (grpbuf.gr_name,
353 strlen (grpbuf.gr_name), ent)
354 && check_and_add_group (user, group, start, size,
355 groupsp, limit, &grpbuf))
356 {
357 if (nss_setgrent != NULL)
358 {
359 nss_setgrent (1);
360 ent->need_endgrent = true;
361 }
362 ent->skip_initgroups_dyn = true;
363
364 goto iter;
365 }
85883123 366 }
85883123 367 }
984a4237
JL
368
369 status = NSS_STATUS_NOTFOUND;
370
371 done:
372 if (use_malloc)
373 free (tmpbuf);
bbca27a4 374 }
3426e770 375
afd7b703 376 free (mygroups);
3426e770 377
984a4237 378 return status;
899d423e 379 }
3426e770 380
afd7b703 381 free (mygroups);
bbca27a4 382 }
899d423e 383
bbca27a4 384 /* If we come here, the NSS module does not support initgroups_dyn
ccab6d8f
UD
385 or we were confronted with a split group. In these cases we have
386 to step through the whole list ourself. */
387 iter:
bbca27a4
UD
388 do
389 {
390 if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
391 NSS_STATUS_SUCCESS)
ccab6d8f 392 break;
899d423e 393 }
bbca27a4 394 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
899d423e 395
ccab6d8f
UD
396 if (status == NSS_STATUS_SUCCESS)
397 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
398
399 return status;
899d423e
UD
400}
401
899d423e 402static enum nss_status
bbca27a4
UD
403internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
404 gid_t group, long int *start, long int *size,
405 gid_t **groupsp, long int limit, int *errnop)
899d423e
UD
406{
407 struct parser_data *data = (void *) buffer;
bbca27a4 408 struct group grpbuf;
899d423e 409
bbca27a4
UD
410 if (!ent->files)
411 return getgrent_next_nss (ent, buffer, buflen, user, group,
412 start, size, groupsp, limit, errnop);
899d423e 413
899d423e
UD
414 while (1)
415 {
416 fpos_t pos;
417 int parse_res = 0;
418 char *p;
419
420 do
421 {
20f8e666 422 /* We need at least 3 characters for one line. */
a1ffb40e 423 if (__glibc_unlikely (buflen < 3))
20f8e666
UD
424 {
425 erange:
426 *errnop = ERANGE;
427 return NSS_STATUS_TRYAGAIN;
428 }
429
899d423e
UD
430 fgetpos (ent->stream, &pos);
431 buffer[buflen - 1] = '\xff';
bbca27a4
UD
432 p = fgets_unlocked (buffer, buflen, ent->stream);
433 if (p == NULL && feof_unlocked (ent->stream))
34816665
UD
434 return NSS_STATUS_NOTFOUND;
435
20f8e666 436 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
899d423e 437 {
20f8e666 438 erange_reset:
899d423e 439 fsetpos (ent->stream, &pos);
20f8e666 440 goto erange;
899d423e
UD
441 }
442
443 /* Terminate the line for any case. */
444 buffer[buflen - 1] = '\0';
445
446 /* Skip leading blanks. */
447 while (isspace (*p))
448 ++p;
449 }
bbca27a4
UD
450 while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
451 /* Parse the line. If it is invalid, loop to
eaca7569 452 get the next line of the file to parse. */
bbca27a4 453 !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
899d423e
UD
454 errnop)));
455
a1ffb40e 456 if (__glibc_unlikely (parse_res == -1))
20f8e666
UD
457 /* The parser ran out of space. */
458 goto erange_reset;
899d423e 459
bbca27a4 460 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
899d423e
UD
461 /* This is a real entry. */
462 break;
463
464 /* -group */
bbca27a4
UD
465 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
466 && grpbuf.gr_name[1] != '@')
899d423e 467 {
bbca27a4 468 blacklist_store_name (&grpbuf.gr_name[1], ent);
899d423e
UD
469 continue;
470 }
471
472 /* +group */
bbca27a4
UD
473 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
474 && grpbuf.gr_name[1] != '@')
899d423e 475 {
bbca27a4
UD
476 if (in_blacklist (&grpbuf.gr_name[1],
477 strlen (&grpbuf.gr_name[1]), ent))
478 continue;
479 /* Store the group in the blacklist for the "+" at the end of
899d423e 480 /etc/group */
bbca27a4
UD
481 blacklist_store_name (&grpbuf.gr_name[1], ent);
482 if (nss_getgrnam_r == NULL)
483 return NSS_STATUS_UNAVAIL;
484 else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer,
485 buflen, errnop) != NSS_STATUS_SUCCESS)
486 continue;
487
488 check_and_add_group (user, group, start, size, groupsp,
489 limit, &grpbuf);
490
491 return NSS_STATUS_SUCCESS;
899d423e
UD
492 }
493
494 /* +:... */
bbca27a4 495 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
899d423e 496 {
ccab6d8f
UD
497 /* If the selected module does not support getgrent_r or
498 initgroups_dyn, abort. We cannot find the needed group
499 entries. */
3056dcdb
UD
500 if (nss_initgroups_dyn == NULL || nss_getgrgid_r == NULL)
501 {
502 if (nss_setgrent != NULL)
eaca7569 503 {
3056dcdb
UD
504 nss_setgrent (1);
505 ent->need_endgrent = true;
506 }
507 ent->skip_initgroups_dyn = true;
3056dcdb 508
eaca7569
UD
509 if (nss_getgrent_r == NULL)
510 return NSS_STATUS_UNAVAIL;
511 }
ccab6d8f
UD
512
513 ent->files = false;
514
bbca27a4
UD
515 return getgrent_next_nss (ent, buffer, buflen, user, group,
516 start, size, groupsp, limit, errnop);
899d423e
UD
517 }
518 }
519
bbca27a4
UD
520 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
521
899d423e
UD
522 return NSS_STATUS_SUCCESS;
523}
524
525
899d423e 526enum nss_status
cf9e9ad9 527_nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
7603ea28
UD
528 long int *size, gid_t **groupsp, long int limit,
529 int *errnop)
899d423e 530{
899d423e
UD
531 size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
532 char *tmpbuf;
533 enum nss_status status;
ccab6d8f 534 ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
984a4237 535 bool use_malloc = false;
899d423e
UD
536
537 status = internal_setgrent (&intern);
538 if (status != NSS_STATUS_SUCCESS)
539 return status;
540
541 tmpbuf = __alloca (buflen);
542
543 do
544 {
bbca27a4
UD
545 while ((status = internal_getgrent_r (&intern, tmpbuf, buflen,
546 user, group, start, size,
547 groupsp, limit, errnop))
548 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
984a4237
JL
549 if (__libc_use_alloca (buflen * 2))
550 tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
551 else
552 {
553 buflen *= 2;
554 char *newbuf = realloc (use_malloc ? tmpbuf : NULL, buflen);
555 if (newbuf == NULL)
556 {
557 status = NSS_STATUS_TRYAGAIN;
558 goto done;
559 }
560 use_malloc = true;
561 tmpbuf = newbuf;
562 }
899d423e
UD
563 }
564 while (status == NSS_STATUS_SUCCESS);
565
984a4237
JL
566 status = NSS_STATUS_SUCCESS;
567
568 done:
569 if (use_malloc)
570 free (tmpbuf);
571
899d423e
UD
572 internal_endgrent (&intern);
573
984a4237 574 return status;
899d423e
UD
575}
576
577
578/* Support routines for remembering -@netgroup and -user entries.
579 The names are stored in a single string with `|' as separator. */
580static void
581blacklist_store_name (const char *name, ent_t *ent)
582{
583 int namelen = strlen (name);
584 char *tmp;
585
bbca27a4 586 /* First call, setup cache. */
899d423e
UD
587 if (ent->blacklist.size == 0)
588 {
589 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
590 ent->blacklist.data = malloc (ent->blacklist.size);
591 if (ent->blacklist.data == NULL)
592 return;
593 ent->blacklist.data[0] = '|';
594 ent->blacklist.data[1] = '\0';
595 ent->blacklist.current = 1;
596 }
597 else
598 {
599 if (in_blacklist (name, namelen, ent))
600 return; /* no duplicates */
601
602 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
603 {
604 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
605 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
606 if (tmp == NULL)
607 {
608 free (ent->blacklist.data);
609 ent->blacklist.size = 0;
610 return;
611 }
612 ent->blacklist.data = tmp;
613 }
614 }
615
616 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
617 *tmp++ = '|';
618 *tmp = '\0';
619 ent->blacklist.current += namelen + 1;
620
621 return;
622}
623
624/* returns TRUE if ent->blacklist contains name, else FALSE */
625static bool_t
626in_blacklist (const char *name, int namelen, ent_t *ent)
627{
628 char buf[namelen + 3];
629 char *cp;
630
631 if (ent->blacklist.data == NULL)
632 return FALSE;
633
634 buf[0] = '|';
635 cp = stpcpy (&buf[1], name);
bbca27a4 636 *cp++ = '|';
899d423e
UD
637 *cp = '\0';
638 return strstr (ent->blacklist.data, buf) != NULL;
639}