1 | /* Copyright (C) 1998-2018 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 | |
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); |
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 |
62 | struct blacklist_t |
63 | { |
64 | char *data; |
65 | int current; |
66 | int size; |
67 | }; |
68 | |
69 | struct ent_t |
70 | { |
71 | bool files; |
72 | bool need_endgrent; |
73 | bool skip_initgroups_dyn; |
74 | FILE *stream; |
75 | struct blacklist_t blacklist; |
76 | }; |
77 | typedef struct ent_t ent_t; |
78 | |
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 *); |
82 | |
83 | /* Initialize the NSS interface/functions. The calling function must |
84 | hold the lock. */ |
85 | static void |
86 | init_nss_interface (void) |
87 | { |
88 | __libc_lock_lock (lock); |
89 | |
90 | /* Retest. */ |
91 | if (ni == NULL |
92 | && __nss_database_lookup ("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 | |
105 | static enum nss_status |
106 | internal_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 | |
136 | static enum nss_status |
137 | internal_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. */ |
161 | static void |
162 | add_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. */ |
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) |
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. */ |
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) |
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 | bool use_malloc = false; |
265 | |
266 | for (int i = 0; i < mystart; i++) |
267 | { |
268 | while ((status = nss_getgrgid_r (mygroups[i], &grpbuf, |
269 | tmpbuf, tmplen, errnop)) |
270 | == NSS_STATUS_TRYAGAIN |
271 | && *errnop == ERANGE) |
272 | { |
273 | if (__libc_use_alloca (tmplen * 2)) |
274 | { |
275 | if (tmpbuf == buffer) |
276 | { |
277 | tmplen *= 2; |
278 | tmpbuf = __alloca (tmplen); |
279 | } |
280 | else |
281 | tmpbuf = extend_alloca (tmpbuf, tmplen, tmplen * 2); |
282 | } |
283 | else |
284 | { |
285 | tmplen *= 2; |
286 | char *newbuf = realloc (use_malloc ? tmpbuf : NULL, tmplen); |
287 | |
288 | if (newbuf == NULL) |
289 | { |
290 | status = NSS_STATUS_TRYAGAIN; |
291 | goto done; |
292 | } |
293 | use_malloc = true; |
294 | tmpbuf = newbuf; |
295 | } |
296 | } |
297 | |
298 | if (__builtin_expect (status != NSS_STATUS_NOTFOUND, 1)) |
299 | { |
300 | if (__builtin_expect (status != NSS_STATUS_SUCCESS, 0)) |
301 | goto done; |
302 | |
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)) |
307 | { |
308 | if (nss_setgrent != NULL) |
309 | { |
310 | nss_setgrent (1); |
311 | ent->need_endgrent = true; |
312 | } |
313 | ent->skip_initgroups_dyn = true; |
314 | |
315 | goto iter; |
316 | } |
317 | } |
318 | } |
319 | |
320 | status = NSS_STATUS_NOTFOUND; |
321 | |
322 | done: |
323 | if (use_malloc) |
324 | free (tmpbuf); |
325 | } |
326 | |
327 | free (mygroups); |
328 | |
329 | return status; |
330 | } |
331 | |
332 | free (mygroups); |
333 | } |
334 | |
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: |
339 | do |
340 | { |
341 | if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) != |
342 | NSS_STATUS_SUCCESS) |
343 | break; |
344 | } |
345 | while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent)); |
346 | |
347 | if (status == NSS_STATUS_SUCCESS) |
348 | check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf); |
349 | |
350 | return status; |
351 | } |
352 | |
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) |
357 | { |
358 | struct parser_data *data = (void *) buffer; |
359 | struct group grpbuf; |
360 | |
361 | if (!ent->files) |
362 | return getgrent_next_nss (ent, buffer, buflen, user, group, |
363 | start, size, groupsp, limit, errnop); |
364 | |
365 | while (1) |
366 | { |
367 | fpos_t pos; |
368 | int parse_res = 0; |
369 | char *p; |
370 | |
371 | do |
372 | { |
373 | /* We need at least 3 characters for one line. */ |
374 | if (__glibc_unlikely (buflen < 3)) |
375 | { |
376 | erange: |
377 | *errnop = ERANGE; |
378 | return NSS_STATUS_TRYAGAIN; |
379 | } |
380 | |
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; |
386 | |
387 | if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0)) |
388 | { |
389 | erange_reset: |
390 | fsetpos (ent->stream, &pos); |
391 | goto erange; |
392 | } |
393 | |
394 | /* Terminate the line for any case. */ |
395 | buffer[buflen - 1] = '\0'; |
396 | |
397 | /* Skip leading blanks. */ |
398 | while (isspace (*p)) |
399 | ++p; |
400 | } |
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))); |
406 | |
407 | if (__glibc_unlikely (parse_res == -1)) |
408 | /* The parser ran out of space. */ |
409 | goto erange_reset; |
410 | |
411 | if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-') |
412 | /* This is a real entry. */ |
413 | break; |
414 | |
415 | /* -group */ |
416 | if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0' |
417 | && grpbuf.gr_name[1] != '@') |
418 | { |
419 | blacklist_store_name (&grpbuf.gr_name[1], ent); |
420 | continue; |
421 | } |
422 | |
423 | /* +group */ |
424 | if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0' |
425 | && grpbuf.gr_name[1] != '@') |
426 | { |
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; |
438 | |
439 | check_and_add_group (user, group, start, size, groupsp, |
440 | limit, &grpbuf); |
441 | |
442 | return NSS_STATUS_SUCCESS; |
443 | } |
444 | |
445 | /* +:... */ |
446 | if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0') |
447 | { |
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) |
452 | { |
453 | if (nss_setgrent != NULL) |
454 | { |
455 | nss_setgrent (1); |
456 | ent->need_endgrent = true; |
457 | } |
458 | ent->skip_initgroups_dyn = true; |
459 | |
460 | if (nss_getgrent_r == NULL) |
461 | return NSS_STATUS_UNAVAIL; |
462 | } |
463 | |
464 | ent->files = false; |
465 | |
466 | return getgrent_next_nss (ent, buffer, buflen, user, group, |
467 | start, size, groupsp, limit, errnop); |
468 | } |
469 | } |
470 | |
471 | check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf); |
472 | |
473 | return NSS_STATUS_SUCCESS; |
474 | } |
475 | |
476 | |
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) |
481 | { |
482 | enum nss_status status; |
483 | ent_t intern = { true, false, false, NULL, {NULL, 0, 0} }; |
484 | |
485 | status = internal_setgrent (&intern); |
486 | if (status != NSS_STATUS_SUCCESS) |
487 | return status; |
488 | |
489 | struct scratch_buffer tmpbuf; |
490 | scratch_buffer_init (&tmpbuf); |
491 | |
492 | do |
493 | { |
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; |
500 | } |
501 | while (status == NSS_STATUS_SUCCESS); |
502 | |
503 | status = NSS_STATUS_SUCCESS; |
504 | |
505 | done: |
506 | scratch_buffer_free (&tmpbuf); |
507 | |
508 | internal_endgrent (&intern); |
509 | |
510 | return status; |
511 | } |
512 | |
513 | |
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) |
518 | { |
519 | int namelen = strlen (name); |
520 | char *tmp; |
521 | |
522 | /* First call, setup cache. */ |
523 | if (ent->blacklist.size == 0) |
524 | { |
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; |
532 | } |
533 | else |
534 | { |
535 | if (in_blacklist (name, namelen, ent)) |
536 | return; /* no duplicates */ |
537 | |
538 | if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size) |
539 | { |
540 | ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen); |
541 | tmp = realloc (ent->blacklist.data, ent->blacklist.size); |
542 | if (tmp == NULL) |
543 | { |
544 | free (ent->blacklist.data); |
545 | ent->blacklist.size = 0; |
546 | return; |
547 | } |
548 | ent->blacklist.data = tmp; |
549 | } |
550 | } |
551 | |
552 | tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name); |
553 | *tmp++ = '|'; |
554 | *tmp = '\0'; |
555 | ent->blacklist.current += namelen + 1; |
556 | |
557 | return; |
558 | } |
559 | |
560 | /* Return whether ent->blacklist contains name. */ |
561 | static bool |
562 | in_blacklist (const char *name, int namelen, ent_t *ent) |
563 | { |
564 | char buf[namelen + 3]; |
565 | char *cp; |
566 | |
567 | if (ent->blacklist.data == NULL) |
568 | return false; |
569 | |
570 | buf[0] = '|'; |
571 | cp = stpcpy (&buf[1], name); |
572 | *cp++ = '|'; |
573 | *cp = '\0'; |
574 | return strstr (ent->blacklist.data, buf) != NULL; |
575 | } |
576 | |