1/* Copyright (C) 1998-2016 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
4
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.
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
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <http://www.gnu.org/licenses/>. */
18
19#include <ctype.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <grp.h>
23#include <nss.h>
24#include <stdio_ext.h>
25#include <string.h>
26#include <unistd.h>
27#include <rpc/types.h>
28#include <sys/param.h>
29#include <nsswitch.h>
30#include <libc-lock.h>
31#include <kernel-features.h>
32#include <scratch_buffer.h>
33
34static service_user *ni;
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 *);
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);
45static enum nss_status (*nss_setgrent) (int stayopen);
46static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
47 size_t buflen, int *errnop);
48static enum nss_status (*nss_endgrent) (void);
49
50/* Protect global state against multiple changers. */
51__libc_lock_define_initialized (static, lock)
52
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
64{
65 char *data;
66 int current;
67 int size;
68};
69
70struct ent_t
71{
72 bool files;
73 bool need_endgrent;
74 bool skip_initgroups_dyn;
75 FILE *stream;
76 struct blacklist_t blacklist;
77};
78typedef struct ent_t ent_t;
79
80
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
94/* Prototypes for local functions. */
95static void blacklist_store_name (const char *, ent_t *);
96static int in_blacklist (const char *, int, ent_t *);
97
98/* Initialize the NSS interface/functions. The calling function must
99 hold the lock. */
100static void
101init_nss_interface (void)
102{
103 __libc_lock_lock (lock);
104
105 /* Retest. */
106 if (ni == NULL
107 && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
108 {
109 nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
110 nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r");
111 nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r");
112 nss_setgrent = __nss_lookup_function (ni, "setgrent");
113 nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r");
114 nss_endgrent = __nss_lookup_function (ni, "endgrent");
115 }
116
117 __libc_lock_unlock (lock);
118}
119
120static enum nss_status
121internal_setgrent (ent_t *ent)
122{
123 enum nss_status status = NSS_STATUS_SUCCESS;
124
125 ent->files = true;
126
127 if (ni == NULL)
128 init_nss_interface ();
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
139 ent->stream = fopen ("/etc/group", "rme");
140
141 if (ent->stream == NULL)
142 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
143 else
144 {
145 /* We have to make sure the file is `closed on exec'. */
146 int result = 0;
147
148 if (__compat_have_cloexec <= 0)
149 {
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 }
166 }
167
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;
175 }
176 else
177 /* We take care of locking ourself. */
178 __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
179 }
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
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
203 if (ent->need_endgrent && nss_endgrent != NULL)
204 nss_endgrent ();
205
206 return NSS_STATUS_SUCCESS;
207}
208
209/* Add new group record. */
210static void
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. */
217 if (__glibc_unlikely (*start == *size))
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
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)
250{
251 char **member;
252
253 /* Don't add main group to list of groups. */
254 if (grp->gr_gid == group)
255 return 0;
256
257 for (member = grp->gr_mem; *member != NULL; ++member)
258 if (strcmp (*member, user) == 0)
259 {
260 add_group (start, size, groupsp, limit, grp->gr_gid);
261 return 0;
262 }
263
264 return 1;
265}
266
267/* Get the next group from NSS (+ entry). If the NSS module supports
268 initgroups_dyn, get all entries at once. */
269static enum nss_status
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)
273{
274 enum nss_status status;
275 struct group grpbuf;
276
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. */
280 if (! ent->skip_initgroups_dyn)
281 {
282 long int mystart = 0;
283 long int mysize = limit <= 0 ? *size : limit;
284 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
285
286 if (mygroups == NULL)
287 return NSS_STATUS_TRYAGAIN;
288
289 /* For every gid in the list we get from the NSS module,
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. */
296 if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
297 limit, errnop) == NSS_STATUS_SUCCESS)
298 {
299 status = NSS_STATUS_NOTFOUND;
300
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
307 {
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;
313 bool use_malloc = false;
314
315 for (int i = 0; i < mystart; i++)
316 {
317 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf,
318 tmpbuf, tmplen, errnop))
319 == NSS_STATUS_TRYAGAIN
320 && *errnop == ERANGE)
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 }
346
347 if (__builtin_expect (status != NSS_STATUS_NOTFOUND, 1))
348 {
349 if (__builtin_expect (status != NSS_STATUS_SUCCESS, 0))
350 goto done;
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 }
366 }
367 }
368
369 status = NSS_STATUS_NOTFOUND;
370
371 done:
372 if (use_malloc)
373 free (tmpbuf);
374 }
375
376 free (mygroups);
377
378 return status;
379 }
380
381 free (mygroups);
382 }
383
384 /* If we come here, the NSS module does not support initgroups_dyn
385 or we were confronted with a split group. In these cases we have
386 to step through the whole list ourself. */
387 iter:
388 do
389 {
390 if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
391 NSS_STATUS_SUCCESS)
392 break;
393 }
394 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
395
396 if (status == NSS_STATUS_SUCCESS)
397 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
398
399 return status;
400}
401
402static enum nss_status
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)
406{
407 struct parser_data *data = (void *) buffer;
408 struct group grpbuf;
409
410 if (!ent->files)
411 return getgrent_next_nss (ent, buffer, buflen, user, group,
412 start, size, groupsp, limit, errnop);
413
414 while (1)
415 {
416 fpos_t pos;
417 int parse_res = 0;
418 char *p;
419
420 do
421 {
422 /* We need at least 3 characters for one line. */
423 if (__glibc_unlikely (buflen < 3))
424 {
425 erange:
426 *errnop = ERANGE;
427 return NSS_STATUS_TRYAGAIN;
428 }
429
430 fgetpos (ent->stream, &pos);
431 buffer[buflen - 1] = '\xff';
432 p = fgets_unlocked (buffer, buflen, ent->stream);
433 if (p == NULL && feof_unlocked (ent->stream))
434 return NSS_STATUS_NOTFOUND;
435
436 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
437 {
438 erange_reset:
439 fsetpos (ent->stream, &pos);
440 goto erange;
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 }
450 while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
451 /* Parse the line. If it is invalid, loop to
452 get the next line of the file to parse. */
453 !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
454 errnop)));
455
456 if (__glibc_unlikely (parse_res == -1))
457 /* The parser ran out of space. */
458 goto erange_reset;
459
460 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
461 /* This is a real entry. */
462 break;
463
464 /* -group */
465 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
466 && grpbuf.gr_name[1] != '@')
467 {
468 blacklist_store_name (&grpbuf.gr_name[1], ent);
469 continue;
470 }
471
472 /* +group */
473 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
474 && grpbuf.gr_name[1] != '@')
475 {
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
480 /etc/group */
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;
492 }
493
494 /* +:... */
495 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
496 {
497 /* If the selected module does not support getgrent_r or
498 initgroups_dyn, abort. We cannot find the needed group
499 entries. */
500 if (nss_initgroups_dyn == NULL || nss_getgrgid_r == NULL)
501 {
502 if (nss_setgrent != NULL)
503 {
504 nss_setgrent (1);
505 ent->need_endgrent = true;
506 }
507 ent->skip_initgroups_dyn = true;
508
509 if (nss_getgrent_r == NULL)
510 return NSS_STATUS_UNAVAIL;
511 }
512
513 ent->files = false;
514
515 return getgrent_next_nss (ent, buffer, buflen, user, group,
516 start, size, groupsp, limit, errnop);
517 }
518 }
519
520 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
521
522 return NSS_STATUS_SUCCESS;
523}
524
525
526enum nss_status
527_nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
528 long int *size, gid_t **groupsp, long int limit,
529 int *errnop)
530{
531 enum nss_status status;
532 ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
533
534 status = internal_setgrent (&intern);
535 if (status != NSS_STATUS_SUCCESS)
536 return status;
537
538 struct scratch_buffer tmpbuf;
539 scratch_buffer_init (&tmpbuf);
540
541 do
542 {
543 while ((status = internal_getgrent_r (&intern, tmpbuf.data, tmpbuf.length,
544 user, group, start, size,
545 groupsp, limit, errnop))
546 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
547 if (!scratch_buffer_grow (&tmpbuf))
548 goto done;
549 }
550 while (status == NSS_STATUS_SUCCESS);
551
552 status = NSS_STATUS_SUCCESS;
553
554 done:
555 scratch_buffer_free (&tmpbuf);
556
557 internal_endgrent (&intern);
558
559 return status;
560}
561
562
563/* Support routines for remembering -@netgroup and -user entries.
564 The names are stored in a single string with `|' as separator. */
565static void
566blacklist_store_name (const char *name, ent_t *ent)
567{
568 int namelen = strlen (name);
569 char *tmp;
570
571 /* First call, setup cache. */
572 if (ent->blacklist.size == 0)
573 {
574 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
575 ent->blacklist.data = malloc (ent->blacklist.size);
576 if (ent->blacklist.data == NULL)
577 return;
578 ent->blacklist.data[0] = '|';
579 ent->blacklist.data[1] = '\0';
580 ent->blacklist.current = 1;
581 }
582 else
583 {
584 if (in_blacklist (name, namelen, ent))
585 return; /* no duplicates */
586
587 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
588 {
589 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
590 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
591 if (tmp == NULL)
592 {
593 free (ent->blacklist.data);
594 ent->blacklist.size = 0;
595 return;
596 }
597 ent->blacklist.data = tmp;
598 }
599 }
600
601 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
602 *tmp++ = '|';
603 *tmp = '\0';
604 ent->blacklist.current += namelen + 1;
605
606 return;
607}
608
609/* returns TRUE if ent->blacklist contains name, else FALSE */
610static bool_t
611in_blacklist (const char *name, int namelen, ent_t *ent)
612{
613 char buf[namelen + 3];
614 char *cp;
615
616 if (ent->blacklist.data == NULL)
617 return FALSE;
618
619 buf[0] = '|';
620 cp = stpcpy (&buf[1], name);
621 *cp++ = '|';
622 *cp = '\0';
623 return strstr (ent->blacklist.data, buf) != NULL;
624}
625