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