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 | |
34 | static service_user *ni; |
35 | /* Type of the lookup function. */ |
36 | static enum nss_status (*nss_initgroups_dyn) (const char *, gid_t, |
37 | long int *, long int *, |
38 | gid_t **, long int, int *); |
39 | static enum nss_status (*nss_getgrnam_r) (const char *name, |
40 | struct group * grp, char *buffer, |
41 | size_t buflen, int *errnop); |
42 | static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp, |
43 | char *buffer, size_t buflen, |
44 | int *errnop); |
45 | static enum nss_status (*nss_setgrent) (int stayopen); |
46 | static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer, |
47 | size_t buflen, int *errnop); |
48 | static 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 |
63 | struct blacklist_t |
64 | { |
65 | char *data; |
66 | int current; |
67 | int size; |
68 | }; |
69 | |
70 | struct ent_t |
71 | { |
72 | bool files; |
73 | bool need_endgrent; |
74 | bool skip_initgroups_dyn; |
75 | FILE *stream; |
76 | struct blacklist_t blacklist; |
77 | }; |
78 | typedef 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 |
88 | extern int __compat_have_cloexec; |
89 | # else |
90 | # define __compat_have_cloexec -1 |
91 | # endif |
92 | #endif |
93 | |
94 | /* Prototypes for local functions. */ |
95 | static void blacklist_store_name (const char *, ent_t *); |
96 | static int in_blacklist (const char *, int, ent_t *); |
97 | |
98 | /* Initialize the NSS interface/functions. The calling function must |
99 | hold the lock. */ |
100 | static void |
101 | init_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 | |
120 | static enum nss_status |
121 | internal_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 | |
185 | static enum nss_status |
186 | internal_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. */ |
210 | static void |
211 | add_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. */ |
246 | static int |
247 | check_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. */ |
269 | static enum nss_status |
270 | getgrent_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 | |
402 | static enum nss_status |
403 | internal_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 | |
526 | enum 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. */ |
565 | static void |
566 | blacklist_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 */ |
610 | static bool_t |
611 | in_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 | |