1 /* Copyright (C) 1998-2013 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, see
17 <http://www.gnu.org/licenses/>. */
25 #include <stdio_ext.h>
28 #include <rpc/types.h>
29 #include <sys/param.h>
31 #include <bits/libc-lock.h>
32 #include <kernel-features.h>
34 static service_user
*ni
;
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 *);
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
,
45 static enum nss_status (*nss_setgrent
) (int stayopen
);
46 static enum nss_status (*nss_getgrent_r
) (struct group
* grp
, char *buffer
,
47 size_t buflen
, int *errnop
);
48 static enum nss_status (*nss_endgrent
) (void);
50 /* Protect global state against multiple changers. */
51 __libc_lock_define_initialized (static, lock
)
54 /* Get the declaration of the parser function. */
56 #define STRUCTURE group
58 #include <nss/nss_files/files-parse.c>
60 /* Structure for remembering -group members ... */
61 #define BLACKLIST_INITIAL_SIZE 512
62 #define BLACKLIST_INCREMENT 256
74 bool skip_initgroups_dyn
;
76 struct blacklist_t blacklist
;
78 typedef struct ent_t ent_t
;
81 /* Positive if O_CLOEXEC is supported, negative if it is not supported,
82 zero if it is still undecided. This variable is shared with the
83 other compat functions. */
84 #ifdef __ASSUME_O_CLOEXEC
85 # define __compat_have_cloexec 1
88 extern int __compat_have_cloexec
;
90 # define __compat_have_cloexec -1
94 /* Prototypes for local functions. */
95 static void blacklist_store_name (const char *, ent_t
*);
96 static int in_blacklist (const char *, int, ent_t
*);
98 /* Initialize the NSS interface/functions. The calling function must
101 init_nss_interface (void)
103 __libc_lock_lock (lock
);
107 && __nss_database_lookup ("group_compat", NULL
, "nis", &ni
) >= 0)
109 nss_initgroups_dyn
= __nss_lookup_function (ni
, "initgroups_dyn");
110 nss_getgrnam_r
= __nss_lookup_function (ni
, "getgrnam_r");
111 nss_getgrgid_r
= __nss_lookup_function (ni
, "getgrgid_r");
112 nss_setgrent
= __nss_lookup_function (ni
, "setgrent");
113 nss_getgrent_r
= __nss_lookup_function (ni
, "getgrent_r");
114 nss_endgrent
= __nss_lookup_function (ni
, "endgrent");
117 __libc_lock_unlock (lock
);
120 static enum nss_status
121 internal_setgrent (ent_t
*ent
)
123 enum nss_status status
= NSS_STATUS_SUCCESS
;
128 init_nss_interface ();
130 if (ent
->blacklist
.data
!= NULL
)
132 ent
->blacklist
.current
= 1;
133 ent
->blacklist
.data
[0] = '|';
134 ent
->blacklist
.data
[1] = '\0';
137 ent
->blacklist
.current
= 0;
139 ent
->stream
= fopen ("/etc/group", "rme");
141 if (ent
->stream
== NULL
)
142 status
= errno
== EAGAIN
? NSS_STATUS_TRYAGAIN
: NSS_STATUS_UNAVAIL
;
145 /* We have to make sure the file is `closed on exec'. */
148 if (__compat_have_cloexec
<= 0)
151 result
= flags
= fcntl (fileno_unlocked (ent
->stream
), F_GETFD
, 0);
154 #if defined O_CLOEXEC && !defined __ASSUME_O_CLOEXEC
155 if (__compat_have_cloexec
== 0)
156 __compat_have_cloexec
= (flags
& FD_CLOEXEC
) ? 1 : -1;
158 if (__compat_have_cloexec
< 0)
162 result
= fcntl (fileno_unlocked (ent
->stream
), F_SETFD
,
170 /* Something went wrong. Close the stream and return a
172 fclose (ent
->stream
);
174 status
= NSS_STATUS_UNAVAIL
;
177 /* We take care of locking ourself. */
178 __fsetlocking (ent
->stream
, FSETLOCKING_BYCALLER
);
185 static enum nss_status
186 internal_endgrent (ent_t
*ent
)
188 if (ent
->stream
!= NULL
)
190 fclose (ent
->stream
);
194 if (ent
->blacklist
.data
!= NULL
)
196 ent
->blacklist
.current
= 1;
197 ent
->blacklist
.data
[0] = '|';
198 ent
->blacklist
.data
[1] = '\0';
201 ent
->blacklist
.current
= 0;
203 if (ent
->need_endgrent
&& nss_endgrent
!= NULL
)
206 return NSS_STATUS_SUCCESS
;
209 /* Add new group record. */
211 add_group (long int *start
, long int *size
, gid_t
**groupsp
, long int limit
,
214 gid_t
*groups
= *groupsp
;
216 /* Matches user. Insert this group. */
217 if (__builtin_expect (*start
== *size
, 0))
219 /* Need a bigger buffer. */
223 if (limit
> 0 && *size
== limit
)
224 /* We reached the maximum. */
230 newsize
= MIN (limit
, 2 * *size
);
232 newgroups
= realloc (groups
, newsize
* sizeof (*groups
));
233 if (newgroups
== NULL
)
235 *groupsp
= groups
= newgroups
;
239 groups
[*start
] = gid
;
243 /* This function checks, if the user is a member of this group and if
244 yes, add the group id to the list. Return nonzero is we couldn't
245 handle the group because the user is not in the member list. */
247 check_and_add_group (const char *user
, gid_t group
, long int *start
,
248 long int *size
, gid_t
**groupsp
, long int limit
,
253 /* Don't add main group to list of groups. */
254 if (grp
->gr_gid
== group
)
257 for (member
= grp
->gr_mem
; *member
!= NULL
; ++member
)
258 if (strcmp (*member
, user
) == 0)
260 add_group (start
, size
, groupsp
, limit
, grp
->gr_gid
);
267 /* Get the next group from NSS (+ entry). If the NSS module supports
268 initgroups_dyn, get all entries at once. */
269 static enum nss_status
270 getgrent_next_nss (ent_t
*ent
, char *buffer
, size_t buflen
, const char *user
,
271 gid_t group
, long int *start
, long int *size
,
272 gid_t
**groupsp
, long int limit
, int *errnop
)
274 enum nss_status status
;
277 /* Try nss_initgroups_dyn if supported. We also need getgrgid_r.
278 If this function is not supported, step through the whole group
279 database with getgrent_r. */
280 if (! ent
->skip_initgroups_dyn
)
282 long int mystart
= 0;
283 long int mysize
= limit
<= 0 ? *size
: limit
;
284 gid_t
*mygroups
= malloc (mysize
* sizeof (gid_t
));
286 if (mygroups
== NULL
)
287 return NSS_STATUS_TRYAGAIN
;
289 /* For every gid in the list we get from the NSS module,
290 get the whole group entry. We need to do this, since we
291 need the group name to check if it is in the blacklist.
292 In worst case, this is as twice as slow as stepping with
293 getgrent_r through the whole group database. But for large
294 group databases this is faster, since the user can only be
295 in a limited number of groups. */
296 if (nss_initgroups_dyn (user
, group
, &mystart
, &mysize
, &mygroups
,
297 limit
, errnop
) == NSS_STATUS_SUCCESS
)
299 status
= NSS_STATUS_NOTFOUND
;
301 /* If there is no blacklist we can trust the underlying
302 initgroups implementation. */
303 if (ent
->blacklist
.current
<= 1)
304 for (int i
= 0; i
< mystart
; i
++)
305 add_group (start
, size
, groupsp
, limit
, mygroups
[i
]);
308 /* A temporary buffer. We use the normal buffer, until we find
309 an entry, for which this buffer is to small. In this case, we
310 overwrite the pointer with one to a bigger buffer. */
311 char *tmpbuf
= buffer
;
312 size_t tmplen
= buflen
;
313 bool use_malloc
= false;
315 for (int i
= 0; i
< mystart
; i
++)
317 while ((status
= nss_getgrgid_r (mygroups
[i
], &grpbuf
,
318 tmpbuf
, tmplen
, errnop
))
319 == NSS_STATUS_TRYAGAIN
320 && *errnop
== ERANGE
)
322 if (__libc_use_alloca (tmplen
* 2))
324 if (tmpbuf
== buffer
)
327 tmpbuf
= __alloca (tmplen
);
330 tmpbuf
= extend_alloca (tmpbuf
, tmplen
, tmplen
* 2);
335 char *newbuf
= realloc (use_malloc
? tmpbuf
: NULL
, tmplen
);
339 status
= NSS_STATUS_TRYAGAIN
;
347 if (__builtin_expect (status
!= NSS_STATUS_NOTFOUND
, 1))
349 if (__builtin_expect (status
!= NSS_STATUS_SUCCESS
, 0))
352 if (!in_blacklist (grpbuf
.gr_name
,
353 strlen (grpbuf
.gr_name
), ent
)
354 && check_and_add_group (user
, group
, start
, size
,
355 groupsp
, limit
, &grpbuf
))
357 if (nss_setgrent
!= NULL
)
360 ent
->need_endgrent
= true;
362 ent
->skip_initgroups_dyn
= true;
369 status
= NSS_STATUS_NOTFOUND
;
384 /* If we come here, the NSS module does not support initgroups_dyn
385 or we were confronted with a split group. In these cases we have
386 to step through the whole list ourself. */
390 if ((status
= nss_getgrent_r (&grpbuf
, buffer
, buflen
, errnop
)) !=
394 while (in_blacklist (grpbuf
.gr_name
, strlen (grpbuf
.gr_name
), ent
));
396 if (status
== NSS_STATUS_SUCCESS
)
397 check_and_add_group (user
, group
, start
, size
, groupsp
, limit
, &grpbuf
);
402 static enum nss_status
403 internal_getgrent_r (ent_t
*ent
, char *buffer
, size_t buflen
, const char *user
,
404 gid_t group
, long int *start
, long int *size
,
405 gid_t
**groupsp
, long int limit
, int *errnop
)
407 struct parser_data
*data
= (void *) buffer
;
411 return getgrent_next_nss (ent
, buffer
, buflen
, user
, group
,
412 start
, size
, groupsp
, limit
, errnop
);
422 /* We need at least 3 characters for one line. */
423 if (__builtin_expect (buflen
< 3, 0))
427 return NSS_STATUS_TRYAGAIN
;
430 fgetpos (ent
->stream
, &pos
);
431 buffer
[buflen
- 1] = '\xff';
432 p
= fgets_unlocked (buffer
, buflen
, ent
->stream
);
433 if (p
== NULL
&& feof_unlocked (ent
->stream
))
434 return NSS_STATUS_NOTFOUND
;
436 if (p
== NULL
|| __builtin_expect (buffer
[buflen
- 1] != '\xff', 0))
439 fsetpos (ent
->stream
, &pos
);
443 /* Terminate the line for any case. */
444 buffer
[buflen
- 1] = '\0';
446 /* Skip leading blanks. */
450 while (*p
== '\0' || *p
== '#' || /* Ignore empty and comment lines. */
451 /* Parse the line. If it is invalid, loop to
452 get the next line of the file to parse. */
453 !(parse_res
= _nss_files_parse_grent (p
, &grpbuf
, data
, buflen
,
456 if (__builtin_expect (parse_res
== -1, 0))
457 /* The parser ran out of space. */
460 if (grpbuf
.gr_name
[0] != '+' && grpbuf
.gr_name
[0] != '-')
461 /* This is a real entry. */
465 if (grpbuf
.gr_name
[0] == '-' && grpbuf
.gr_name
[1] != '\0'
466 && grpbuf
.gr_name
[1] != '@')
468 blacklist_store_name (&grpbuf
.gr_name
[1], ent
);
473 if (grpbuf
.gr_name
[0] == '+' && grpbuf
.gr_name
[1] != '\0'
474 && grpbuf
.gr_name
[1] != '@')
476 if (in_blacklist (&grpbuf
.gr_name
[1],
477 strlen (&grpbuf
.gr_name
[1]), ent
))
479 /* Store the group in the blacklist for the "+" at the end of
481 blacklist_store_name (&grpbuf
.gr_name
[1], ent
);
482 if (nss_getgrnam_r
== NULL
)
483 return NSS_STATUS_UNAVAIL
;
484 else if (nss_getgrnam_r (&grpbuf
.gr_name
[1], &grpbuf
, buffer
,
485 buflen
, errnop
) != NSS_STATUS_SUCCESS
)
488 check_and_add_group (user
, group
, start
, size
, groupsp
,
491 return NSS_STATUS_SUCCESS
;
495 if (grpbuf
.gr_name
[0] == '+' && grpbuf
.gr_name
[1] == '\0')
497 /* If the selected module does not support getgrent_r or
498 initgroups_dyn, abort. We cannot find the needed group
500 if (nss_initgroups_dyn
== NULL
|| nss_getgrgid_r
== NULL
)
502 if (nss_setgrent
!= NULL
)
505 ent
->need_endgrent
= true;
507 ent
->skip_initgroups_dyn
= true;
509 if (nss_getgrent_r
== NULL
)
510 return NSS_STATUS_UNAVAIL
;
515 return getgrent_next_nss (ent
, buffer
, buflen
, user
, group
,
516 start
, size
, groupsp
, limit
, errnop
);
520 check_and_add_group (user
, group
, start
, size
, groupsp
, limit
, &grpbuf
);
522 return NSS_STATUS_SUCCESS
;
527 _nss_compat_initgroups_dyn (const char *user
, gid_t group
, long int *start
,
528 long int *size
, gid_t
**groupsp
, long int limit
,
531 size_t buflen
= sysconf (_SC_GETPW_R_SIZE_MAX
);
533 enum nss_status status
;
534 ent_t intern
= { true, false, false, NULL
, {NULL
, 0, 0} };
535 bool use_malloc
= false;
537 status
= internal_setgrent (&intern
);
538 if (status
!= NSS_STATUS_SUCCESS
)
541 tmpbuf
= __alloca (buflen
);
545 while ((status
= internal_getgrent_r (&intern
, tmpbuf
, buflen
,
546 user
, group
, start
, size
,
547 groupsp
, limit
, errnop
))
548 == NSS_STATUS_TRYAGAIN
&& *errnop
== ERANGE
)
549 if (__libc_use_alloca (buflen
* 2))
550 tmpbuf
= extend_alloca (tmpbuf
, buflen
, 2 * buflen
);
554 char *newbuf
= realloc (use_malloc
? tmpbuf
: NULL
, buflen
);
557 status
= NSS_STATUS_TRYAGAIN
;
564 while (status
== NSS_STATUS_SUCCESS
);
566 status
= NSS_STATUS_SUCCESS
;
572 internal_endgrent (&intern
);
578 /* Support routines for remembering -@netgroup and -user entries.
579 The names are stored in a single string with `|' as separator. */
581 blacklist_store_name (const char *name
, ent_t
*ent
)
583 int namelen
= strlen (name
);
586 /* First call, setup cache. */
587 if (ent
->blacklist
.size
== 0)
589 ent
->blacklist
.size
= MAX (BLACKLIST_INITIAL_SIZE
, 2 * namelen
);
590 ent
->blacklist
.data
= malloc (ent
->blacklist
.size
);
591 if (ent
->blacklist
.data
== NULL
)
593 ent
->blacklist
.data
[0] = '|';
594 ent
->blacklist
.data
[1] = '\0';
595 ent
->blacklist
.current
= 1;
599 if (in_blacklist (name
, namelen
, ent
))
600 return; /* no duplicates */
602 if (ent
->blacklist
.current
+ namelen
+ 1 >= ent
->blacklist
.size
)
604 ent
->blacklist
.size
+= MAX (BLACKLIST_INCREMENT
, 2 * namelen
);
605 tmp
= realloc (ent
->blacklist
.data
, ent
->blacklist
.size
);
608 free (ent
->blacklist
.data
);
609 ent
->blacklist
.size
= 0;
612 ent
->blacklist
.data
= tmp
;
616 tmp
= stpcpy (ent
->blacklist
.data
+ ent
->blacklist
.current
, name
);
619 ent
->blacklist
.current
+= namelen
+ 1;
624 /* returns TRUE if ent->blacklist contains name, else FALSE */
626 in_blacklist (const char *name
, int namelen
, ent_t
*ent
)
628 char buf
[namelen
+ 3];
631 if (ent
->blacklist
.data
== NULL
)
635 cp
= stpcpy (&buf
[1], name
);
638 return strstr (ent
->blacklist
.data
, buf
) != NULL
;