]> git.ipfire.org Git - thirdparty/glibc.git/blame - nis/nss_compat/compat-initgroups.c
(_IO_new_file_fopen): Recognize 'e' flag and set O_CLOEXEC is needed.
[thirdparty/glibc.git] / nis / nss_compat / compat-initgroups.c
CommitLineData
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 34static service_user *ni;
bbca27a4
UD
35/* Type of the lookup function. */
36static enum nss_status (*nss_initgroups_dyn) (const char *, gid_t,
37 long int *, long int *,
38 gid_t **, long int, int *);
bbca27a4
UD
39static enum nss_status (*nss_getgrnam_r) (const char *name,
40 struct group * grp, char *buffer,
41 size_t buflen, int *errnop);
42static enum nss_status (*nss_getgrgid_r) (gid_t gid, struct group * grp,
43 char *buffer, size_t buflen,
44 int *errnop);
45static 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
61struct blacklist_t
e36b0b57 62{
bbca27a4
UD
63 char *data;
64 int current;
65 int size;
e36b0b57
UD
66};
67
899d423e 68struct ent_t
bbca27a4
UD
69{
70 bool_t files;
71 FILE *stream;
72 struct blacklist_t blacklist;
899d423e
UD
73};
74typedef struct ent_t ent_t;
75
76
77/* Prototypes for local functions. */
78static void blacklist_store_name (const char *, ent_t *);
79static 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. */
83static void
84init_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
101static enum nss_status
102internal_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
152static enum nss_status
153internal_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. */
175static void
176check_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 222static enum nss_status
bbca27a4
UD
223getgrent_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 306static enum nss_status
bbca27a4
UD
307internal_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 413enum 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. */
447static void
448blacklist_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 */
492static bool_t
493in_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}