2.5-18.1
[jlayton/glibc.git] / nis / nss_compat / compat-initgroups.c
1 /* Copyright (C) 1998-2003, 2004, 2006 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998.
4
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.
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
13    Lesser General Public License for more details.
14
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.  */
19
20 #include <alloca.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <grp.h>
25 #include <nss.h>
26 #include <stdio_ext.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <rpc/types.h>
30 #include <sys/param.h>
31 #include <nsswitch.h>
32 #include <bits/libc-lock.h>
33
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,
44                                           int *errnop);
45 static enum nss_status (*nss_getgrent_r) (struct group * grp, char *buffer,
46                                           size_t buflen, int *errnop);
47
48 /* Protect global state against multiple changers.  */
49 __libc_lock_define_initialized (static, lock)
50
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
62 {
63   char *data;
64   int current;
65   int size;
66 };
67
68 struct ent_t
69 {
70   bool_t files;
71   FILE *stream;
72   struct blacklist_t blacklist;
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
81 /* Initialize the NSS interface/functions. The calling function must
82    hold the lock.  */
83 static void
84 init_nss_interface (void)
85 {
86   __libc_lock_lock (lock);
87
88   /* Retest.  */
89   if (ni == NULL
90       && __nss_database_lookup ("group_compat", NULL, "nis", &ni) >= 0)
91     {
92       nss_initgroups_dyn = __nss_lookup_function (ni, "initgroups_dyn");
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");
96     }
97
98   __libc_lock_unlock (lock);
99 }
100
101 static enum nss_status
102 internal_setgrent (ent_t *ent)
103 {
104   enum nss_status status = NSS_STATUS_SUCCESS;
105
106   ent->files = TRUE;
107
108   if (ni == NULL)
109     init_nss_interface ();
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
120   ent->stream = fopen ("/etc/group", "rm");
121
122   if (ent->stream == NULL)
123     status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
124   else
125     {
126       /* We have to make sure the file is  `closed on exec'.  */
127       int result, flags;
128
129       result = flags = fcntl (fileno_unlocked (ent->stream), F_GETFD, 0);
130       if (result >= 0)
131         {
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;
142         }
143       else
144         /* We take care of locking ourself.  */
145         __fsetlocking (ent->stream, FSETLOCKING_BYCALLER);
146     }
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
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
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)
179 {
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;
200
201             if (limit <= 0)
202               newsize = 2 * *size;
203             else
204               newsize = MIN (limit, 2 * *size);
205
206             newgroups = realloc (groups, newsize * sizeof (*groups));
207             if (newgroups == NULL)
208               return;
209             *groupsp = groups = newgroups;
210             *size = newsize;
211           }
212
213         groups[*start] = grp->gr_gid;
214         *start += 1;
215
216         break;
217       }
218 }
219
220 /* Get the next group from NSS  (+ entry). If the NSS module supports
221    initgroups_dyn, get all entries at once.  */
222 static enum nss_status
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)
226 {
227   enum nss_status status;
228   struct group grpbuf;
229
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)
239     {
240       long int mystart = 0;
241       long int mysize = limit <= 0 ? *size : limit;
242       gid_t *mygroups = malloc (mysize * sizeof (gid_t));
243
244       if (mygroups == NULL)
245         return NSS_STATUS_TRYAGAIN;
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.  */
254       if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
255                               limit, errnop) == NSS_STATUS_SUCCESS)
256         {
257           /* A temporary buffer. We use the normal buffer, until we find
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             {
266               while ((status = nss_getgrgid_r (mygroups[i], &grpbuf, tmpbuf,
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             }
283
284           free (mygroups);
285
286           return NSS_STATUS_NOTFOUND;
287         }
288
289       free (mygroups);
290     }
291
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;
299     }
300   while (in_blacklist (grpbuf.gr_name, strlen (grpbuf.gr_name), ent));
301
302   check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
303   return NSS_STATUS_SUCCESS;
304 }
305
306 static enum nss_status
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)
310 {
311   struct parser_data *data = (void *) buffer;
312   struct group grpbuf;
313
314   if (!ent->files)
315     return getgrent_next_nss (ent, buffer, buflen, user, group,
316                               start, size, groupsp, limit, errnop);
317
318   while (1)
319     {
320       fpos_t pos;
321       int parse_res = 0;
322       char *p;
323
324       do
325         {
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
334           fgetpos (ent->stream, &pos);
335           buffer[buflen - 1] = '\xff';
336           p = fgets_unlocked (buffer, buflen, ent->stream);
337           if (p == NULL && feof_unlocked (ent->stream))
338             return NSS_STATUS_NOTFOUND;
339
340           if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
341             {
342             erange_reset:
343               fsetpos (ent->stream, &pos);
344               goto erange;
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         }
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,
358                                                    errnop)));
359
360       if (__builtin_expect (parse_res == -1, 0))
361         /* The parser ran out of space.  */
362         goto erange_reset;
363
364       if (grpbuf.gr_name[0] != '+' && grpbuf.gr_name[0] != '-')
365         /* This is a real entry.  */
366         break;
367
368       /* -group */
369       if (grpbuf.gr_name[0] == '-' && grpbuf.gr_name[1] != '\0'
370           && grpbuf.gr_name[1] != '@')
371         {
372           blacklist_store_name (&grpbuf.gr_name[1], ent);
373           continue;
374         }
375
376       /* +group */
377       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] != '\0'
378           && grpbuf.gr_name[1] != '@')
379         {
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
384              /etc/group */
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;
396         }
397
398       /* +:... */
399       if (grpbuf.gr_name[0] == '+' && grpbuf.gr_name[1] == '\0')
400         {
401           ent->files = FALSE;
402           return getgrent_next_nss (ent, buffer, buflen, user, group,
403                                     start, size, groupsp, limit, errnop);
404         }
405     }
406
407   check_and_add_group (user, group, start, size, groupsp, limit, &grpbuf);
408
409   return NSS_STATUS_SUCCESS;
410 }
411
412
413 enum nss_status
414 _nss_compat_initgroups_dyn (const char *user, gid_t group, long int *start,
415                             long int *size, gid_t **groupsp, long int limit,
416                             int *errnop)
417 {
418   size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
419   char *tmpbuf;
420   enum nss_status status;
421   ent_t intern = { TRUE, NULL, {NULL, 0, 0} };
422
423   status = internal_setgrent (&intern);
424   if (status != NSS_STATUS_SUCCESS)
425     return status;
426
427   tmpbuf = __alloca (buflen);
428
429   do
430     {
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);
436     }
437   while (status == NSS_STATUS_SUCCESS);
438
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
453   /* First call, setup cache.  */
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);
503   *cp++ = '|';
504   *cp = '\0';
505   return strstr (ent->blacklist.data, buf) != NULL;
506 }