support_become_root: Enable file creation in user namespaces
[glibc.git] / nss / nss_compat / compat-initgroups.c
blobc1a9301a3b0c3ee007510646153eac70227e643c
1 /* Copyright (C) 1998-2017 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, see
17 <http://www.gnu.org/licenses/>. */
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>
33 static service_user *ni;
34 /* Type of the lookup function. */
35 static enum nss_status (*nss_initgroups_dyn) (const char *, gid_t,
36 long int *, long int *,
37 gid_t **, long int, int *);
38 static enum nss_status (*nss_getgrnam_r) (const char *name,
39 struct group * grp, char *buffer,
40 size_t buflen, int *errnop);
41 static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
42 char *buffer, size_t buflen,
43 int *errnop);
44 static enum nss_status (*nss_setgrent) (int stayopen);
45 static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
46 size_t buflen, int *errnop);
47 static enum nss_status (*nss_endgrent) (void);
49 /* Protect global state against multiple changers. */
50 __libc_lock_define_initialized (static, lock)
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>
59 /* Structure for remembering -group members ... */
60 #define BLACKLIST_INITIAL_SIZE 512
61 #define BLACKLIST_INCREMENT 256
62 struct blacklist_t
64 char *data;
65 int current;
66 int size;
69 struct ent_t
71 bool files;
72 bool need_endgrent;
73 bool skip_initgroups_dyn;
74 FILE *stream;
75 struct blacklist_t blacklist;
77 typedef struct ent_t ent_t;
79 /* Prototypes for local functions. */
80 static void blacklist_store_name (const char *, ent_t *);
81 static bool 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_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");
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", "rme");
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);
132 return status;
136 static enum nss_status
137 internal_endgrent (ent_t *ent)
139 if (ent->stream != NULL)
141 fclose (ent->stream);
142 ent->stream = NULL;
145 if (ent->blacklist.data != NULL)
147 ent->blacklist.current = 1;
148 ent->blacklist.data[0] = '|';
149 ent->blacklist.data[1] = '\0';
151 else
152 ent->blacklist.current = 0;
154 if (ent->need_endgrent && nss_endgrent != NULL)
155 nss_endgrent ();
157 return NSS_STATUS_SUCCESS;
160 /* Add new group record. */
161 static void
162 add_group (long int *start, long int *size, gid_t **groupsp, long int limit,
163 gid_t gid)
165 gid_t *groups = *groupsp;
167 /* Matches user. Insert this group. */
168 if (__glibc_unlikely (*start == *size))
170 /* Need a bigger buffer. */
171 gid_t *newgroups;
172 long int newsize;
174 if (limit > 0 && *size == limit)
175 /* We reached the maximum. */
176 return;
178 if (limit <= 0)
179 newsize = 2 * *size;
180 else
181 newsize = MIN (limit, 2 * *size);
183 newgroups = realloc (groups, newsize * sizeof (*groups));
184 if (newgroups == NULL)
185 return;
186 *groupsp = groups = newgroups;
187 *size = newsize;
190 groups[*start] = gid;
191 *start += 1;
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. */
197 static int
198 check_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)
202 char **member;
204 /* Don't add main group to list of groups. */
205 if (grp->gr_gid == group)
206 return 0;
208 for (member = grp->gr_mem; *member != NULL; ++member)
209 if (strcmp (*member, user) == 0)
211 add_group (start, size, groupsp, limit, grp->gr_gid);
212 return 0;
215 return 1;
218 /* Get the next group from NSS (+ entry). If the NSS module supports
219 initgroups_dyn, get all entries at once. */
220 static enum nss_status
221 getgrent_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)
225 enum nss_status status;
226 struct group grpbuf;
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)
233 long int mystart = 0;
234 long int mysize = limit <= 0 ? *size : limit;
235 gid_t *mygroups = malloc (mysize * sizeof (gid_t));
237 if (mygroups == NULL)
238 return NSS_STATUS_TRYAGAIN;
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)
250 status = NSS_STATUS_NOTFOUND;
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
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 bool use_malloc = false;
266 for (int i = 0; i < mystart; i++)
268 while ((status = nss_getgrgid_r (mygroups[i], &grpbuf,
269 tmpbuf, tmplen, errnop))
270 == NSS_STATUS_TRYAGAIN
271 && *errnop == ERANGE)
273 if (__libc_use_alloca (tmplen * 2))
275 if (tmpbuf == buffer)
277 tmplen *= 2;
278 tmpbuf = __alloca (tmplen);
280 else
281 tmpbuf = extend_alloca (tmpbuf, tmplen, tmplen * 2);
283 else
285 tmplen *= 2;
286 char *newbuf = realloc (use_malloc ? tmpbuf : NULL, tmplen);
288 if (newbuf == NULL)
290 status = NSS_STATUS_TRYAGAIN;
291 goto done;
293 use_malloc = true;
294 tmpbuf = newbuf;
298 if (__builtin_expect (status != NSS_STATUS_NOTFOUND, 1))
300 if (__builtin_expect (status != NSS_STATUS_SUCCESS, 0))
301 goto done;
303 if (!in_blacklist (grpbuf.gr_name,
304 strlen (grpbuf.gr_name), ent)
305 && check_and_add_group (user, group, start, size,
306 groupsp, limit, &grpbuf))
308 if (nss_setgrent != NULL)
310 nss_setgrent (1);
311 ent->need_endgrent = true;
313 ent->skip_initgroups_dyn = true;
315 goto iter;
320 status = NSS_STATUS_NOTFOUND;
322 done:
323 if (use_malloc)
324 free (tmpbuf);
327 free (mygroups);
329 return status;
332 free (mygroups);
335 /* If we come here, the NSS module does not support initgroups_dyn
336 or we were confronted with a split group. In these cases we have
337 to step through the whole list ourself. */
338 iter:
341 if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) !=
342 NSS_STATUS_SUCCESS)
343 break;
345 while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
347 if (status == NSS_STATUS_SUCCESS)
348 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
350 return status;
353 static enum nss_status
354 internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user,
355 gid_t group, long int *start, long int *size,
356 gid_t **groupsp, long int limit, int *errnop)
358 struct parser_data *data = (void *) buffer;
359 struct group grpbuf;
361 if (!ent->files)
362 return getgrent_next_nss (ent, buffer, buflen, user, group,
363 start, size, groupsp, limit, errnop);
365 while (1)
367 fpos_t pos;
368 int parse_res = 0;
369 char *p;
373 /* We need at least 3 characters for one line. */
374 if (__glibc_unlikely (buflen < 3))
376 erange:
377 *errnop = ERANGE;
378 return NSS_STATUS_TRYAGAIN;
381 fgetpos (ent->stream, &pos);
382 buffer[buflen - 1] = '\xff';
383 p = fgets_unlocked (buffer, buflen, ent->stream);
384 if (p == NULL && feof_unlocked (ent->stream))
385 return NSS_STATUS_NOTFOUND;
387 if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
389 erange_reset:
390 fsetpos (ent->stream, &pos);
391 goto erange;
394 /* Terminate the line for any case. */
395 buffer[buflen - 1] = '\0';
397 /* Skip leading blanks. */
398 while (isspace (*p))
399 ++p;
401 while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */
402 /* Parse the line. If it is invalid, loop to
403 get the next line of the file to parse. */
404 !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen,
405 errnop)));
407 if (__glibc_unlikely (parse_res == -1))
408 /* The parser ran out of space. */
409 goto erange_reset;
411 if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
412 /* This is a real entry. */
413 break;
415 /* -group */
416 if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
417 && grpbuf.gr_name[1] != '@')
419 blacklist_store_name (&grpbuf.gr_name[1], ent);
420 continue;
423 /* +group */
424 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
425 && grpbuf.gr_name[1] != '@')
427 if (in_blacklist (&grpbuf.gr_name[1],
428 strlen (&grpbuf.gr_name[1]), ent))
429 continue;
430 /* Store the group in the blacklist for the "+" at the end of
431 /etc/group */
432 blacklist_store_name (&grpbuf.gr_name[1], ent);
433 if (nss_getgrnam_r == NULL)
434 return NSS_STATUS_UNAVAIL;
435 else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer,
436 buflen, errnop) != NSS_STATUS_SUCCESS)
437 continue;
439 check_and_add_group (user, group, start, size, groupsp,
440 limit, &grpbuf);
442 return NSS_STATUS_SUCCESS;
445 /* +:... */
446 if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
448 /* If the selected module does not support getgrent_r or
449 initgroups_dyn, abort. We cannot find the needed group
450 entries. */
451 if (nss_initgroups_dyn == NULL || nss_getgrgid_r == NULL)
453 if (nss_setgrent != NULL)
455 nss_setgrent (1);
456 ent->need_endgrent = true;
458 ent->skip_initgroups_dyn = true;
460 if (nss_getgrent_r == NULL)
461 return NSS_STATUS_UNAVAIL;
464 ent->files = false;
466 return getgrent_next_nss (ent, buffer, buflen, user, group,
467 start, size, groupsp, limit, errnop);
471 check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
473 return NSS_STATUS_SUCCESS;
477 enum nss_status
478 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
479 long int *size, gid_t **groupsp, long int limit,
480 int *errnop)
482 enum nss_status status;
483 ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
485 status = internal_setgrent (&intern);
486 if (status != NSS_STATUS_SUCCESS)
487 return status;
489 struct scratch_buffer tmpbuf;
490 scratch_buffer_init (&tmpbuf);
494 while ((status = internal_getgrent_r (&intern, tmpbuf.data, tmpbuf.length,
495 user, group, start, size,
496 groupsp, limit, errnop))
497 == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
498 if (!scratch_buffer_grow (&tmpbuf))
499 goto done;
501 while (status == NSS_STATUS_SUCCESS);
503 status = NSS_STATUS_SUCCESS;
505 done:
506 scratch_buffer_free (&tmpbuf);
508 internal_endgrent (&intern);
510 return status;
514 /* Support routines for remembering -@netgroup and -user entries.
515 The names are stored in a single string with `|' as separator. */
516 static void
517 blacklist_store_name (const char *name, ent_t *ent)
519 int namelen = strlen (name);
520 char *tmp;
522 /* First call, setup cache. */
523 if (ent->blacklist.size == 0)
525 ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
526 ent->blacklist.data = malloc (ent->blacklist.size);
527 if (ent->blacklist.data == NULL)
528 return;
529 ent->blacklist.data[0] = '|';
530 ent->blacklist.data[1] = '\0';
531 ent->blacklist.current = 1;
533 else
535 if (in_blacklist (name, namelen, ent))
536 return; /* no duplicates */
538 if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
540 ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
541 tmp = realloc (ent->blacklist.data, ent->blacklist.size);
542 if (tmp == NULL)
544 free (ent->blacklist.data);
545 ent->blacklist.size = 0;
546 return;
548 ent->blacklist.data = tmp;
552 tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
553 *tmp++ = '|';
554 *tmp = '\0';
555 ent->blacklist.current += namelen + 1;
557 return;
560 /* Return whether ent->blacklist contains name. */
561 static bool
562 in_blacklist (const char *name, int namelen, ent_t *ent)
564 char buf[namelen + 3];
565 char *cp;
567 if (ent->blacklist.data == NULL)
568 return false;
570 buf[0] = '|';
571 cp = stpcpy (&buf[1], name);
572 *cp++ = '|';
573 *cp = '\0';
574 return strstr (ent->blacklist.data, buf) != NULL;