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