]>
Commit | Line | Data |
---|---|---|
758b9d7e | 1 | /* Copyright (C) 1998-2003, 2004, 2006 Free Software Foundation, Inc. |
899d423e | 2 | This file is part of the GNU C Library. |
3b965a7d | 3 | Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998. |
899d423e UD |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or | |
41bdb6e2 AJ |
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. | |
899d423e UD |
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 | |
41bdb6e2 | 13 | Lesser General Public License for more details. |
899d423e | 14 | |
41bdb6e2 AJ |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, write to the Free | |
17 | Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | |
18 | 02111-1307 USA. */ | |
899d423e | 19 | |
bbca27a4 UD |
20 | #include <alloca.h> |
21 | #include <ctype.h> | |
899d423e UD |
22 | #include <errno.h> |
23 | #include <fcntl.h> | |
899d423e | 24 | #include <grp.h> |
bbca27a4 UD |
25 | #include <nss.h> |
26 | #include <stdio_ext.h> | |
899d423e UD |
27 | #include <string.h> |
28 | #include <unistd.h> | |
bbca27a4 | 29 | #include <rpc/types.h> |
7603ea28 | 30 | #include <sys/param.h> |
899d423e | 31 | #include <nsswitch.h> |
bbca27a4 | 32 | #include <bits/libc-lock.h> |
899d423e | 33 | |
fc9f33e3 | 34 | static service_user *ni; |
bbca27a4 UD |
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 *); | |
bbca27a4 UD |
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_getgrent_r) (struct group * grp, char *buffer, | |
46 | size_t buflen, int *errnop); | |
bbca27a4 UD |
47 | |
48 | /* Protect global state against multiple changers. */ | |
49 | __libc_lock_define_initialized (static, lock) | |
50 | ||
899d423e UD |
51 | |
52 | /* Get the declaration of the parser function. */ | |
53 | #define ENTNAME grent | |
54 | #define STRUCTURE group | |
55 | #define EXTERN_PARSER | |
56 | #include <nss/nss_files/files-parse.c> | |
57 | ||
58 | /* Structure for remembering -group members ... */ | |
59 | #define BLACKLIST_INITIAL_SIZE 512 | |
60 | #define BLACKLIST_INCREMENT 256 | |
61 | struct blacklist_t | |
e36b0b57 | 62 | { |
bbca27a4 UD |
63 | char *data; |
64 | int current; | |
65 | int size; | |
e36b0b57 UD |
66 | }; |
67 | ||
899d423e | 68 | struct ent_t |
bbca27a4 UD |
69 | { |
70 | bool_t files; | |
71 | FILE *stream; | |
72 | struct blacklist_t blacklist; | |
899d423e UD |
73 | }; |
74 | typedef struct ent_t ent_t; | |
75 | ||
76 | ||
77 | /* Prototypes for local functions. */ | |
78 | static void blacklist_store_name (const char *, ent_t *); | |
79 | static int in_blacklist (const char *, int, ent_t *); | |
80 | ||
bbca27a4 UD |
81 | /* Initialize the NSS interface/functions. The calling function must |
82 | hold the lock. */ | |
83 | static void | |
84 | init_nss_interface (void) | |
899d423e | 85 | { |
bbca27a4 | 86 | __libc_lock_lock (lock); |
899d423e | 87 | |
bbca27a4 UD |
88 | /* Retest. */ |
89 | if (ni == NULL | |
90 | && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0) | |
899d423e | 91 | { |
bbca27a4 | 92 | nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn"); |
bbca27a4 UD |
93 | nss_getgrnam_r = __nss_lookup_function (ni, "getgrnam_r"); |
94 | nss_getgrgid_r = __nss_lookup_function (ni, "getgrgid_r"); | |
95 | nss_getgrent_r = __nss_lookup_function (ni, "getgrent_r"); | |
899d423e UD |
96 | } |
97 | ||
bbca27a4 | 98 | __libc_lock_unlock (lock); |
899d423e UD |
99 | } |
100 | ||
101 | static enum nss_status | |
102 | internal_setgrent (ent_t *ent) | |
103 | { | |
104 | enum nss_status status = NSS_STATUS_SUCCESS; | |
105 | ||
bbca27a4 | 106 | ent->files = TRUE; |
899d423e | 107 | |
bbca27a4 UD |
108 | if (ni == NULL) |
109 | init_nss_interface (); | |
899d423e UD |
110 | |
111 | if (ent->blacklist.data != NULL) | |
112 | { | |
113 | ent->blacklist.current = 1; | |
114 | ent->blacklist.data[0] = '|'; | |
115 | ent->blacklist.data[1] = '\0'; | |
116 | } | |
117 | else | |
118 | ent->blacklist.current = 0; | |
119 | ||
bbca27a4 UD |
120 | ent->stream = fopen ("/etc/group", "rm"); |
121 | ||
899d423e | 122 | if (ent->stream == NULL) |
bbca27a4 UD |
123 | status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL; |
124 | else | |
899d423e | 125 | { |
bbca27a4 UD |
126 | /* We have to make sure the file is `closed on exec'. */ |
127 | int result, flags; | |
899d423e | 128 | |
bbca27a4 UD |
129 | result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0); |
130 | if (result >= 0) | |
899d423e | 131 | { |
bbca27a4 UD |
132 | flags |= FD_CLOEXEC; |
133 | result = fcntl (fileno_unlocked (ent->stream), F_SETFD, flags); | |
134 | } | |
135 | if (result < 0) | |
136 | { | |
137 | /* Something went wrong. Close the stream and return a | |
138 | failure. */ | |
139 | fclose (ent->stream); | |
140 | ent->stream = NULL; | |
141 | status = NSS_STATUS_UNAVAIL; | |
899d423e | 142 | } |
bbca27a4 UD |
143 | else |
144 | /* We take care of locking ourself. */ | |
145 | __fsetlocking (ent->stream, FSETLOCKING_BYCALLER); | |
899d423e | 146 | } |
899d423e UD |
147 | |
148 | return status; | |
149 | } | |
150 | ||
151 | ||
152 | static enum nss_status | |
153 | internal_endgrent (ent_t *ent) | |
154 | { | |
155 | if (ent->stream != NULL) | |
156 | { | |
157 | fclose (ent->stream); | |
158 | ent->stream = NULL; | |
159 | } | |
160 | ||
899d423e UD |
161 | if (ent->blacklist.data != NULL) |
162 | { | |
163 | ent->blacklist.current = 1; | |
164 | ent->blacklist.data[0] = '|'; | |
165 | ent->blacklist.data[1] = '\0'; | |
166 | } | |
167 | else | |
168 | ent->blacklist.current = 0; | |
169 | ||
170 | return NSS_STATUS_SUCCESS; | |
171 | } | |
172 | ||
bbca27a4 UD |
173 | /* This function checks, if the user is a member of this group and if |
174 | yes, add the group id to the list. */ | |
175 | static void | |
176 | check_and_add_group (const char *user, gid_t group, long int *start, | |
177 | long int *size, gid_t **groupsp, long int limit, | |
178 | struct group *grp) | |
899d423e | 179 | { |
bbca27a4 UD |
180 | gid_t *groups = *groupsp; |
181 | char **member; | |
182 | ||
183 | /* Don't add main group to list of groups. */ | |
184 | if (grp->gr_gid == group) | |
185 | return; | |
186 | ||
187 | for (member = grp->gr_mem; *member != NULL; ++member) | |
188 | if (strcmp (*member, user) == 0) | |
189 | { | |
190 | /* Matches user. Insert this group. */ | |
191 | if (*start == *size) | |
192 | { | |
193 | /* Need a bigger buffer. */ | |
194 | gid_t *newgroups; | |
195 | long int newsize; | |
196 | ||
197 | if (limit > 0 && *size == limit) | |
198 | /* We reached the maximum. */ | |
199 | return; | |
899d423e | 200 | |
bbca27a4 UD |
201 | if (limit <= 0) |
202 | newsize = 2 * *size; | |
203 | else | |
204 | newsize = MIN (limit, 2 * *size); | |
e36b0b57 | 205 | |
bbca27a4 UD |
206 | newgroups = realloc (groups, newsize * sizeof (*groups)); |
207 | if (newgroups == NULL) | |
208 | return; | |
209 | *groupsp = groups = newgroups; | |
210 | *size = newsize; | |
211 | } | |
899d423e | 212 | |
bbca27a4 UD |
213 | groups[*start] = grp->gr_gid; |
214 | *start += 1; | |
899d423e | 215 | |
bbca27a4 UD |
216 | break; |
217 | } | |
899d423e UD |
218 | } |
219 | ||
3426e770 | 220 | /* Get the next group from NSS (+ entry). If the NSS module supports |
bbca27a4 | 221 | initgroups_dyn, get all entries at once. */ |
899d423e | 222 | static enum nss_status |
bbca27a4 UD |
223 | getgrent_next_nss (ent_t *ent, char *buffer, size_t buflen, const char *user, |
224 | gid_t group, long int *start, long int *size, | |
225 | gid_t **groupsp, long int limit, int *errnop) | |
899d423e | 226 | { |
bbca27a4 UD |
227 | enum nss_status status; |
228 | struct group grpbuf; | |
899d423e | 229 | |
bbca27a4 UD |
230 | /* if this module does not support getgrent_r and initgroups_dyn, |
231 | abort. We cannot find the needed group entries. */ | |
232 | if (nss_getgrent_r == NULL && nss_initgroups_dyn == NULL) | |
233 | return NSS_STATUS_UNAVAIL; | |
234 | ||
235 | /* Try nss_initgroups_dyn if supported. We also need getgrgid_r. | |
236 | If this function is not supported, step through the whole group | |
237 | database with getgrent_r. */ | |
238 | if (nss_initgroups_dyn && nss_getgrgid_r) | |
899d423e | 239 | { |
3426e770 | 240 | long int mystart = 0; |
8583671d | 241 | long int mysize = limit <= 0 ? *size : limit; |
afd7b703 | 242 | gid_t *mygroups = malloc (mysize * sizeof (gid_t)); |
3426e770 | 243 | |
afd7b703 | 244 | if (mygroups == NULL) |
3426e770 | 245 | return NSS_STATUS_TRYAGAIN; |
bbca27a4 UD |
246 | |
247 | /* For every gid in the list we get from the NSS module, | |
248 | get the whole group entry. We need to do this, since we | |
249 | need the group name to check if it is in the blacklist. | |
250 | In worst case, this is as twice as slow as stepping with | |
251 | getgrent_r through the whole group database. But for large | |
252 | group databases this is faster, since the user can only be | |
253 | in a limited number of groups. */ | |
afd7b703 | 254 | if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups, |
bbca27a4 | 255 | limit, errnop) == NSS_STATUS_SUCCESS) |
899d423e | 256 | { |
8583671d | 257 | /* A temporary buffer. We use the normal buffer, until we find |
bbca27a4 UD |
258 | an entry, for which this buffer is to small. In this case, we |
259 | overwrite the pointer with one to a bigger buffer. */ | |
260 | char *tmpbuf = buffer; | |
261 | size_t tmplen = buflen; | |
262 | int i; | |
263 | ||
264 | for (i = 0; i < mystart; i++) | |
265 | { | |
afd7b703 | 266 | while ((status = nss_getgrgid_r (mygroups[i], &grpbuf, tmpbuf, |
bbca27a4 UD |
267 | tmplen, |
268 | errnop)) == NSS_STATUS_TRYAGAIN | |
269 | && *errnop == ERANGE) | |
270 | if (tmpbuf == buffer) | |
271 | { | |
272 | tmplen *= 2; | |
273 | tmpbuf = __alloca (tmplen); | |
274 | } | |
275 | else | |
276 | tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen); | |
277 | ||
278 | if (!in_blacklist (grpbuf.gr_name, | |
279 | strlen (grpbuf.gr_name), ent)) | |
280 | check_and_add_group (user, group, start, size, groupsp, | |
281 | limit, &grpbuf); | |
282 | } | |
3426e770 | 283 | |
afd7b703 | 284 | free (mygroups); |
3426e770 | 285 | |
bbca27a4 | 286 | return NSS_STATUS_NOTFOUND; |
899d423e | 287 | } |
3426e770 | 288 | |
afd7b703 | 289 | free (mygroups); |
bbca27a4 | 290 | } |
899d423e | 291 | |
bbca27a4 UD |
292 | /* If we come here, the NSS module does not support initgroups_dyn |
293 | and we have to step through the whole list ourself. */ | |
294 | do | |
295 | { | |
296 | if ((status = nss_getgrent_r (&grpbuf, buffer, buflen, errnop)) != | |
297 | NSS_STATUS_SUCCESS) | |
298 | return status; | |
899d423e | 299 | } |
bbca27a4 | 300 | while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent)); |
899d423e | 301 | |
bbca27a4 | 302 | check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf); |
899d423e UD |
303 | return NSS_STATUS_SUCCESS; |
304 | } | |
305 | ||
899d423e | 306 | static enum nss_status |
bbca27a4 UD |
307 | internal_getgrent_r (ent_t *ent, char *buffer, size_t buflen, const char *user, |
308 | gid_t group, long int *start, long int *size, | |
309 | gid_t **groupsp, long int limit, int *errnop) | |
899d423e UD |
310 | { |
311 | struct parser_data *data = (void *) buffer; | |
bbca27a4 | 312 | struct group grpbuf; |
899d423e | 313 | |
bbca27a4 UD |
314 | if (!ent->files) |
315 | return getgrent_next_nss (ent, buffer, buflen, user, group, | |
316 | start, size, groupsp, limit, errnop); | |
899d423e | 317 | |
899d423e UD |
318 | while (1) |
319 | { | |
320 | fpos_t pos; | |
321 | int parse_res = 0; | |
322 | char *p; | |
323 | ||
324 | do | |
325 | { | |
20f8e666 UD |
326 | /* We need at least 3 characters for one line. */ |
327 | if (__builtin_expect (buflen < 3, 0)) | |
328 | { | |
329 | erange: | |
330 | *errnop = ERANGE; | |
331 | return NSS_STATUS_TRYAGAIN; | |
332 | } | |
333 | ||
899d423e UD |
334 | fgetpos (ent->stream, &pos); |
335 | buffer[buflen - 1] = '\xff'; | |
bbca27a4 UD |
336 | p = fgets_unlocked (buffer, buflen, ent->stream); |
337 | if (p == NULL && feof_unlocked (ent->stream)) | |
34816665 UD |
338 | return NSS_STATUS_NOTFOUND; |
339 | ||
20f8e666 | 340 | if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0)) |
899d423e | 341 | { |
20f8e666 | 342 | erange_reset: |
899d423e | 343 | fsetpos (ent->stream, &pos); |
20f8e666 | 344 | goto erange; |
899d423e UD |
345 | } |
346 | ||
347 | /* Terminate the line for any case. */ | |
348 | buffer[buflen - 1] = '\0'; | |
349 | ||
350 | /* Skip leading blanks. */ | |
351 | while (isspace (*p)) | |
352 | ++p; | |
353 | } | |
bbca27a4 UD |
354 | while (*p == '\0' || *p == '#' || /* Ignore empty and comment lines. */ |
355 | /* Parse the line. If it is invalid, loop to | |
356 | get the next line of the file to parse. */ | |
357 | !(parse_res = _nss_files_parse_grent (p, &grpbuf, data, buflen, | |
899d423e UD |
358 | errnop))); |
359 | ||
20f8e666 UD |
360 | if (__builtin_expect (parse_res == -1, 0)) |
361 | /* The parser ran out of space. */ | |
362 | goto erange_reset; | |
899d423e | 363 | |
bbca27a4 | 364 | if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-') |
899d423e UD |
365 | /* This is a real entry. */ |
366 | break; | |
367 | ||
368 | /* -group */ | |
bbca27a4 UD |
369 | if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0' |
370 | && grpbuf.gr_name[1] != '@') | |
899d423e | 371 | { |
bbca27a4 | 372 | blacklist_store_name (&grpbuf.gr_name[1], ent); |
899d423e UD |
373 | continue; |
374 | } | |
375 | ||
376 | /* +group */ | |
bbca27a4 UD |
377 | if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0' |
378 | && grpbuf.gr_name[1] != '@') | |
899d423e | 379 | { |
bbca27a4 UD |
380 | if (in_blacklist (&grpbuf.gr_name[1], |
381 | strlen (&grpbuf.gr_name[1]), ent)) | |
382 | continue; | |
383 | /* Store the group in the blacklist for the "+" at the end of | |
899d423e | 384 | /etc/group */ |
bbca27a4 UD |
385 | blacklist_store_name (&grpbuf.gr_name[1], ent); |
386 | if (nss_getgrnam_r == NULL) | |
387 | return NSS_STATUS_UNAVAIL; | |
388 | else if (nss_getgrnam_r (&grpbuf.gr_name[1], &grpbuf, buffer, | |
389 | buflen, errnop) != NSS_STATUS_SUCCESS) | |
390 | continue; | |
391 | ||
392 | check_and_add_group (user, group, start, size, groupsp, | |
393 | limit, &grpbuf); | |
394 | ||
395 | return NSS_STATUS_SUCCESS; | |
899d423e UD |
396 | } |
397 | ||
398 | /* +:... */ | |
bbca27a4 | 399 | if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0') |
899d423e | 400 | { |
bbca27a4 UD |
401 | ent->files = FALSE; |
402 | return getgrent_next_nss (ent, buffer, buflen, user, group, | |
403 | start, size, groupsp, limit, errnop); | |
899d423e UD |
404 | } |
405 | } | |
406 | ||
bbca27a4 UD |
407 | check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf); |
408 | ||
899d423e UD |
409 | return NSS_STATUS_SUCCESS; |
410 | } | |
411 | ||
412 | ||
899d423e | 413 | enum nss_status |
cf9e9ad9 | 414 | _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start, |
7603ea28 UD |
415 | long int *size, gid_t **groupsp, long int limit, |
416 | int *errnop) | |
899d423e | 417 | { |
899d423e UD |
418 | size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX); |
419 | char *tmpbuf; | |
420 | enum nss_status status; | |
bbca27a4 | 421 | ent_t intern = { TRUE, NULL, {NULL, 0, 0} }; |
899d423e UD |
422 | |
423 | status = internal_setgrent (&intern); | |
424 | if (status != NSS_STATUS_SUCCESS) | |
425 | return status; | |
426 | ||
427 | tmpbuf = __alloca (buflen); | |
428 | ||
429 | do | |
430 | { | |
bbca27a4 UD |
431 | while ((status = internal_getgrent_r (&intern, tmpbuf, buflen, |
432 | user, group, start, size, | |
433 | groupsp, limit, errnop)) | |
434 | == NSS_STATUS_TRYAGAIN && *errnop == ERANGE) | |
435 | tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen); | |
899d423e UD |
436 | } |
437 | while (status == NSS_STATUS_SUCCESS); | |
438 | ||
899d423e UD |
439 | internal_endgrent (&intern); |
440 | ||
441 | return NSS_STATUS_SUCCESS; | |
442 | } | |
443 | ||
444 | ||
445 | /* Support routines for remembering -@netgroup and -user entries. | |
446 | The names are stored in a single string with `|' as separator. */ | |
447 | static void | |
448 | blacklist_store_name (const char *name, ent_t *ent) | |
449 | { | |
450 | int namelen = strlen (name); | |
451 | char *tmp; | |
452 | ||
bbca27a4 | 453 | /* First call, setup cache. */ |
899d423e UD |
454 | if (ent->blacklist.size == 0) |
455 | { | |
456 | ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen); | |
457 | ent->blacklist.data = malloc (ent->blacklist.size); | |
458 | if (ent->blacklist.data == NULL) | |
459 | return; | |
460 | ent->blacklist.data[0] = '|'; | |
461 | ent->blacklist.data[1] = '\0'; | |
462 | ent->blacklist.current = 1; | |
463 | } | |
464 | else | |
465 | { | |
466 | if (in_blacklist (name, namelen, ent)) | |
467 | return; /* no duplicates */ | |
468 | ||
469 | if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size) | |
470 | { | |
471 | ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen); | |
472 | tmp = realloc (ent->blacklist.data, ent->blacklist.size); | |
473 | if (tmp == NULL) | |
474 | { | |
475 | free (ent->blacklist.data); | |
476 | ent->blacklist.size = 0; | |
477 | return; | |
478 | } | |
479 | ent->blacklist.data = tmp; | |
480 | } | |
481 | } | |
482 | ||
483 | tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name); | |
484 | *tmp++ = '|'; | |
485 | *tmp = '\0'; | |
486 | ent->blacklist.current += namelen + 1; | |
487 | ||
488 | return; | |
489 | } | |
490 | ||
491 | /* returns TRUE if ent->blacklist contains name, else FALSE */ | |
492 | static bool_t | |
493 | in_blacklist (const char *name, int namelen, ent_t *ent) | |
494 | { | |
495 | char buf[namelen + 3]; | |
496 | char *cp; | |
497 | ||
498 | if (ent->blacklist.data == NULL) | |
499 | return FALSE; | |
500 | ||
501 | buf[0] = '|'; | |
502 | cp = stpcpy (&buf[1], name); | |
bbca27a4 | 503 | *cp++ = '|'; |
899d423e UD |
504 | *cp = '\0'; |
505 | return strstr (ent->blacklist.data, buf) != NULL; | |
506 | } |