1 /* Copyright (C) 1998-2004,2006,2007,2009,2010 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
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.
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.
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
26 #include <stdio_ext.h>
29 #include <rpc/types.h>
30 #include <sys/param.h>
32 #include <bits/libc-lock.h>
33 #include <kernel-features.h>
35 static service_user
*ni
;
36 /* Type of the lookup function. */
37 static enum nss_status (*nss_initgroups_dyn
) (const char *, gid_t
,
38 long int *, long int *,
39 gid_t
**, long int, int *);
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
,
46 static enum nss_status (*nss_setgrent
) (int stayopen
);
47 static enum nss_status (*nss_getgrent_r
) (struct group
* grp
, char *buffer
,
48 size_t buflen
, int *errnop
);
49 static enum nss_status (*nss_endgrent
) (void);
51 /* Protect global state against multiple changers. */
52 __libc_lock_define_initialized (static, lock
)
55 /* Get the declaration of the parser function. */
57 #define STRUCTURE group
59 #include <nss/nss_files/files-parse.c>
61 /* Structure for remembering -group members ... */
62 #define BLACKLIST_INITIAL_SIZE 512
63 #define BLACKLIST_INCREMENT 256
75 bool skip_initgroups_dyn
;
77 struct blacklist_t blacklist
;
79 typedef struct ent_t ent_t
;
82 /* Positive if O_CLOEXEC is supported, negative if it is not supported,
83 zero if it is still undecided. This variable is shared with the
84 other compat functions. */
85 #ifdef __ASSUME_O_CLOEXEC
86 # define __compat_have_cloexec 1
89 extern int __compat_have_cloexec
;
91 # define __compat_have_cloexec -1
95 /* Prototypes for local functions. */
96 static void blacklist_store_name (const char *, ent_t
*);
97 static int in_blacklist (const char *, int, ent_t
*);
99 /* Initialize the NSS interface/functions. The calling function must
102 init_nss_interface (void)
104 __libc_lock_lock (lock
);
108 && __nss_database_lookup ("group_compat", NULL
, "nis", &ni
) >= 0)
110 nss_initgroups_dyn
= __nss_lookup_function (ni
, "initgroups_dyn");
111 nss_getgrnam_r
= __nss_lookup_function (ni
, "getgrnam_r");
112 nss_getgrgid_r
= __nss_lookup_function (ni
, "getgrgid_r");
113 nss_setgrent
= __nss_lookup_function (ni
, "setgrent");
114 nss_getgrent_r
= __nss_lookup_function (ni
, "getgrent_r");
115 nss_endgrent
= __nss_lookup_function (ni
, "endgrent");
118 __libc_lock_unlock (lock
);
121 static enum nss_status
122 internal_setgrent (ent_t
*ent
)
124 enum nss_status status
= NSS_STATUS_SUCCESS
;
129 init_nss_interface ();
131 if (ent
->blacklist
.data
!= NULL
)
133 ent
->blacklist
.current
= 1;
134 ent
->blacklist
.data
[0] = '|';
135 ent
->blacklist
.data
[1] = '\0';
138 ent
->blacklist
.current
= 0;
140 ent
->stream
= fopen ("/etc/group", "rme");
142 if (ent
->stream
== NULL
)
143 status
= errno
== EAGAIN
? NSS_STATUS_TRYAGAIN
: NSS_STATUS_UNAVAIL
;
146 /* We have to make sure the file is `closed on exec'. */
149 if (__compat_have_cloexec
<= 0)
152 result
= flags
= fcntl (fileno_unlocked (ent
->stream
), F_GETFD
, 0);
155 #if defined O_CLOEXEC && !defined __ASSUME_O_CLOEXEC
156 if (__compat_have_cloexec
== 0)
157 __compat_have_cloexec
= (flags
& FD_CLOEXEC
) ? 1 : -1;
159 if (__compat_have_cloexec
< 0)
163 result
= fcntl (fileno_unlocked (ent
->stream
), F_SETFD
,
171 /* Something went wrong. Close the stream and return a
173 fclose (ent
->stream
);
175 status
= NSS_STATUS_UNAVAIL
;
178 /* We take care of locking ourself. */
179 __fsetlocking (ent
->stream
, FSETLOCKING_BYCALLER
);
186 static enum nss_status
187 internal_endgrent (ent_t
*ent
)
189 if (ent
->stream
!= NULL
)
191 fclose (ent
->stream
);
195 if (ent
->blacklist
.data
!= NULL
)
197 ent
->blacklist
.current
= 1;
198 ent
->blacklist
.data
[0] = '|';
199 ent
->blacklist
.data
[1] = '\0';
202 ent
->blacklist
.current
= 0;
204 if (ent
->need_endgrent
&& nss_endgrent
!= NULL
)
207 return NSS_STATUS_SUCCESS
;
210 /* Add new group record. */
212 add_group (long int *start
, long int *size
, gid_t
**groupsp
, long int limit
,
215 gid_t
*groups
= *groupsp
;
217 /* Matches user. Insert this group. */
218 if (__builtin_expect (*start
== *size
, 0))
220 /* Need a bigger buffer. */
224 if (limit
> 0 && *size
== limit
)
225 /* We reached the maximum. */
231 newsize
= MIN (limit
, 2 * *size
);
233 newgroups
= realloc (groups
, newsize
* sizeof (*groups
));
234 if (newgroups
== NULL
)
236 *groupsp
= groups
= newgroups
;
240 groups
[*start
] = gid
;
244 /* This function checks, if the user is a member of this group and if
245 yes, add the group id to the list. Return nonzero is we couldn't
246 handle the group because the user is not in the member list. */
248 check_and_add_group (const char *user
, gid_t group
, long int *start
,
249 long int *size
, gid_t
**groupsp
, long int limit
,
254 /* Don't add main group to list of groups. */
255 if (grp
->gr_gid
== group
)
258 for (member
= grp
->gr_mem
; *member
!= NULL
; ++member
)
259 if (strcmp (*member
, user
) == 0)
261 add_group (start
, size
, groupsp
, limit
, grp
->gr_gid
);
268 /* Get the next group from NSS (+ entry). If the NSS module supports
269 initgroups_dyn, get all entries at once. */
270 static enum nss_status
271 getgrent_next_nss (ent_t
*ent
, char *buffer
, size_t buflen
, const char *user
,
272 gid_t group
, long int *start
, long int *size
,
273 gid_t
**groupsp
, long int limit
, int *errnop
)
275 enum nss_status status
;
278 /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
279 If this function is not supported, step through the whole group
280 database with getgrent_r. */
281 if (! ent
->skip_initgroups_dyn
)
283 long int mystart
= 0;
284 long int mysize
= limit
<= 0 ? *size
: limit
;
285 gid_t
*mygroups
= malloc (mysize
* sizeof (gid_t
));
287 if (mygroups
== NULL
)
288 return NSS_STATUS_TRYAGAIN
;
290 /* For every gid in the list we get from the NSS module,
291 get the whole group entry. We need to do this, since we
292 need the group name to check if it is in the blacklist.
293 In worst case, this is as twice as slow as stepping with
294 getgrent_r through the whole group database. But for large
295 group databases this is faster, since the user can only be
296 in a limited number of groups. */
297 if (nss_initgroups_dyn (user
, group
, &mystart
, &mysize
, &mygroups
,
298 limit
, errnop
) == NSS_STATUS_SUCCESS
)
300 /* If there is no blacklist we can trust the underlying
301 initgroups implementation. */
302 if (ent
->blacklist
.current
<= 1)
303 for (int i
= 0; i
< mystart
; i
++)
304 add_group (start
, size
, groupsp
, limit
, mygroups
[i
]);
307 /* A temporary buffer. We use the normal buffer, until we find
308 an entry, for which this buffer is to small. In this case, we
309 overwrite the pointer with one to a bigger buffer. */
310 char *tmpbuf
= buffer
;
311 size_t tmplen
= buflen
;
313 for (int i
= 0; i
< mystart
; i
++)
315 while ((status
= nss_getgrgid_r (mygroups
[i
], &grpbuf
,
316 tmpbuf
, tmplen
, errnop
))
317 == NSS_STATUS_TRYAGAIN
318 && *errnop
== ERANGE
)
319 if (tmpbuf
== buffer
)
322 tmpbuf
= __alloca (tmplen
);
325 tmpbuf
= extend_alloca (tmpbuf
, tmplen
, 2 * tmplen
);
327 if (__builtin_expect (status
!= NSS_STATUS_NOTFOUND
, 1))
329 if (__builtin_expect (status
!= NSS_STATUS_SUCCESS
, 0))
335 if (!in_blacklist (grpbuf
.gr_name
,
336 strlen (grpbuf
.gr_name
), ent
)
337 && check_and_add_group (user
, group
, start
, size
,
338 groupsp
, limit
, &grpbuf
))
340 if (nss_setgrent
!= NULL
)
343 ent
->need_endgrent
= true;
345 ent
->skip_initgroups_dyn
= true;
355 return NSS_STATUS_NOTFOUND
;
361 /* If we come here, the NSS module does not support initgroups_dyn
362 or we were confronted with a split group. In these cases we have
363 to step through the whole list ourself. */
367 if ((status
= nss_getgrent_r (&grpbuf
, buffer
, buflen
, errnop
)) !=
371 while (in_blacklist (grpbuf
.gr_name
, strlen (grpbuf
.gr_name
), ent
));
373 if (status
== NSS_STATUS_SUCCESS
)
374 check_and_add_group (user
, group
, start
, size
, groupsp
, limit
, &grpbuf
);
379 static enum nss_status
380 internal_getgrent_r (ent_t
*ent
, char *buffer
, size_t buflen
, const char *user
,
381 gid_t group
, long int *start
, long int *size
,
382 gid_t
**groupsp
, long int limit
, int *errnop
)
384 struct parser_data
*data
= (void *) buffer
;
388 return getgrent_next_nss (ent
, buffer
, buflen
, user
, group
,
389 start
, size
, groupsp
, limit
, errnop
);
399 /* We need at least 3 characters for one line. */
400 if (__builtin_expect (buflen
< 3, 0))
404 return NSS_STATUS_TRYAGAIN
;
407 fgetpos (ent
->stream
, &pos
);
408 buffer
[buflen
- 1] = '\xff';
409 p
= fgets_unlocked (buffer
, buflen
, ent
->stream
);
410 if (p
== NULL
&& feof_unlocked (ent
->stream
))
411 return NSS_STATUS_NOTFOUND
;
413 if (p
== NULL
|| __builtin_expect (buffer
[buflen
- 1] != '\xff', 0))
416 fsetpos (ent
->stream
, &pos
);
420 /* Terminate the line for any case. */
421 buffer
[buflen
- 1] = '\0';
423 /* Skip leading blanks. */
427 while (*p
== '\0' || *p
== '#' || /* Ignore empty and comment lines. */
428 /* Parse the line. If it is invalid, loop to
429 get the next line of the file to parse. */
430 !(parse_res
= _nss_files_parse_grent (p
, &grpbuf
, data
, buflen
,
433 if (__builtin_expect (parse_res
== -1, 0))
434 /* The parser ran out of space. */
437 if (grpbuf
.gr_name
[0] != '+' && grpbuf
.gr_name
[0] != '-')
438 /* This is a real entry. */
442 if (grpbuf
.gr_name
[0] == '-' && grpbuf
.gr_name
[1] != '\0'
443 && grpbuf
.gr_name
[1] != '@')
445 blacklist_store_name (&grpbuf
.gr_name
[1], ent
);
450 if (grpbuf
.gr_name
[0] == '+' && grpbuf
.gr_name
[1] != '\0'
451 && grpbuf
.gr_name
[1] != '@')
453 if (in_blacklist (&grpbuf
.gr_name
[1],
454 strlen (&grpbuf
.gr_name
[1]), ent
))
456 /* Store the group in the blacklist for the "+" at the end of
458 blacklist_store_name (&grpbuf
.gr_name
[1], ent
);
459 if (nss_getgrnam_r
== NULL
)
460 return NSS_STATUS_UNAVAIL
;
461 else if (nss_getgrnam_r (&grpbuf
.gr_name
[1], &grpbuf
, buffer
,
462 buflen
, errnop
) != NSS_STATUS_SUCCESS
)
465 check_and_add_group (user
, group
, start
, size
, groupsp
,
468 return NSS_STATUS_SUCCESS
;
472 if (grpbuf
.gr_name
[0] == '+' && grpbuf
.gr_name
[1] == '\0')
474 /* If the selected module does not support getgrent_r or
475 initgroups_dyn, abort. We cannot find the needed group
477 if (nss_initgroups_dyn
== NULL
|| nss_getgrgid_r
== NULL
)
479 if (nss_setgrent
!= NULL
)
482 ent
->need_endgrent
= true;
484 ent
->skip_initgroups_dyn
= true;
486 if (nss_getgrent_r
== NULL
)
487 return NSS_STATUS_UNAVAIL
;
492 return getgrent_next_nss (ent
, buffer
, buflen
, user
, group
,
493 start
, size
, groupsp
, limit
, errnop
);
497 check_and_add_group (user
, group
, start
, size
, groupsp
, limit
, &grpbuf
);
499 return NSS_STATUS_SUCCESS
;
504 _nss_compat_initgroups_dyn (const char *user
, gid_t group
, long int *start
,
505 long int *size
, gid_t
**groupsp
, long int limit
,
508 size_t buflen
= sysconf (_SC_GETPW_R_SIZE_MAX
);
510 enum nss_status status
;
511 ent_t intern
= { true, false, false, NULL
, {NULL
, 0, 0} };
513 status
= internal_setgrent (&intern
);
514 if (status
!= NSS_STATUS_SUCCESS
)
517 tmpbuf
= __alloca (buflen
);
521 while ((status
= internal_getgrent_r (&intern
, tmpbuf
, buflen
,
522 user
, group
, start
, size
,
523 groupsp
, limit
, errnop
))
524 == NSS_STATUS_TRYAGAIN
&& *errnop
== ERANGE
)
525 tmpbuf
= extend_alloca (tmpbuf
, buflen
, 2 * buflen
);
527 while (status
== NSS_STATUS_SUCCESS
);
529 internal_endgrent (&intern
);
531 return NSS_STATUS_SUCCESS
;
535 /* Support routines for remembering -@netgroup and -user entries.
536 The names are stored in a single string with `|' as separator. */
538 blacklist_store_name (const char *name
, ent_t
*ent
)
540 int namelen
= strlen (name
);
543 /* First call, setup cache. */
544 if (ent
->blacklist
.size
== 0)
546 ent
->blacklist
.size
= MAX (BLACKLIST_INITIAL_SIZE
, 2 * namelen
);
547 ent
->blacklist
.data
= malloc (ent
->blacklist
.size
);
548 if (ent
->blacklist
.data
== NULL
)
550 ent
->blacklist
.data
[0] = '|';
551 ent
->blacklist
.data
[1] = '\0';
552 ent
->blacklist
.current
= 1;
556 if (in_blacklist (name
, namelen
, ent
))
557 return; /* no duplicates */
559 if (ent
->blacklist
.current
+ namelen
+ 1 >= ent
->blacklist
.size
)
561 ent
->blacklist
.size
+= MAX (BLACKLIST_INCREMENT
, 2 * namelen
);
562 tmp
= realloc (ent
->blacklist
.data
, ent
->blacklist
.size
);
565 free (ent
->blacklist
.data
);
566 ent
->blacklist
.size
= 0;
569 ent
->blacklist
.data
= tmp
;
573 tmp
= stpcpy (ent
->blacklist
.data
+ ent
->blacklist
.current
, name
);
576 ent
->blacklist
.current
+= namelen
+ 1;
581 /* returns TRUE if ent->blacklist contains name, else FALSE */
583 in_blacklist (const char *name
, int namelen
, ent_t
*ent
)
585 char buf
[namelen
+ 3];
588 if (ent
->blacklist
.data
== NULL
)
592 cp
= stpcpy (&buf
[1], name
);
595 return strstr (ent
->blacklist
.data
, buf
) != NULL
;