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 | |
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 | |
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 | |
349 | static enum nss_status |
350 | internal_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 | |
473 | enum 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. */ |
512 | static void |
513 | blacklist_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. */ |
557 | static bool |
558 | in_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 | |