fdwalk should return 0 on an empty directory
[kopensolaris-gnu/glibc.git] / nis / nss_nis / nis-initgroups.c
1 /* Copyright (C) 1998-2000,2002,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 <grp.h>
24 #include <nss.h>
25 #include <pwd.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <rpcsvc/yp.h>
29 #include <rpcsvc/ypclnt.h>
30 #include <sys/param.h>
31
32 #include "nss-nis.h"
33 #include <libnsl.h>
34
35 /* Get the declaration of the parser function.  */
36 #define ENTNAME grent
37 #define STRUCTURE group
38 #define EXTERN_PARSER
39 #include <nss/nss_files/files-parse.c>
40
41
42 static enum nss_status
43 internal_setgrent (char *domainname, intern_t *intern)
44 {
45   struct ypall_callback ypcb;
46   enum nss_status status;
47
48   ypcb.foreach = _nis_saveit;
49   ypcb.data = (char *) intern;
50   status = yperr2nss (yp_all (domainname, "group.byname", &ypcb));
51
52   /* Mark the last buffer as full.  */
53   if (intern->next != NULL)
54     intern->next->size = intern->offset;
55
56   intern->next = intern->start;
57   intern->offset = 0;
58
59   return status;
60 }
61
62
63 static enum nss_status
64 internal_getgrent_r (struct group *grp, char *buffer, size_t buflen,
65                      int *errnop, intern_t *intern)
66 {
67   if (intern->start == NULL)
68     return NSS_STATUS_NOTFOUND;
69
70   /* Get the next entry until we found a correct one. */
71   int parse_res;
72   do
73     {
74       struct response_t *bucket = intern->next;
75
76       if (__builtin_expect (intern->offset >= bucket->size, 0))
77         {
78           if (bucket->next == NULL)
79             return NSS_STATUS_NOTFOUND;
80
81           /* We look at all the content in the current bucket.  Go on
82              to the next.  */
83           bucket = intern->next = bucket->next;
84           intern->offset = 0;
85         }
86
87       char *p;
88       for (p = &bucket->mem[intern->offset]; isspace (*p); ++p)
89         ++intern->offset;
90
91       size_t len = strlen (p) + 1;
92       if (__builtin_expect (len > buflen, 0))
93         {
94           *errnop = ERANGE;
95           return NSS_STATUS_TRYAGAIN;
96         }
97
98       /* We unfortunately have to copy the data in the user-provided
99          buffer because that buffer might be around for a very long
100          time and the servent structure must remain valid.  If we would
101          rely on the BUCKET memory the next 'setservent' or 'endservent'
102          call would destroy it.
103
104          The important thing is that it is a single NUL-terminated
105          string.  This is what the parsing routine expects.  */
106       p = memcpy (buffer, &bucket->mem[intern->offset], len);
107
108       parse_res = _nss_files_parse_grent (p, grp, (void *) buffer, buflen,
109                                           errnop);
110       if (__builtin_expect (parse_res == -1, 0))
111         return NSS_STATUS_TRYAGAIN;
112
113       intern->offset += len;
114     }
115   while (!parse_res);
116
117   return NSS_STATUS_SUCCESS;
118 }
119
120
121 static int
122 get_uid (const char *user, uid_t *uidp)
123 {
124   size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
125   char *buf = (char *) alloca (buflen);
126
127   while (1)
128     {
129       struct passwd result;
130       struct passwd *resp;
131
132       int r = getpwnam_r (user, &result, buf, buflen, &resp);
133       if (r == 0 && resp != NULL)
134         {
135           *uidp = resp->pw_uid;
136           return 0;
137         }
138
139       if (r != ERANGE)
140         break;
141
142       extend_alloca (buf, buflen, 2 * buflen);
143     }
144
145   return 1;
146 }
147
148
149 static enum nss_status
150 initgroups_netid (uid_t uid, gid_t group, long int *start, long int *size,
151                   gid_t **groupsp, long int limit, int *errnop,
152                   const char *domainname)
153 {
154   /* Prepare the key.  The form is "unix.UID@DOMAIN" with the UID and
155      DOMAIN field filled in appropriately.  */
156   char key[sizeof ("unix.@") + sizeof (uid_t) * 3 + strlen (domainname)];
157   ssize_t keylen = snprintf (key, sizeof (key), "unix.%lu@%s",
158                              (unsigned long int) uid, domainname);
159
160   char *result;
161   int reslen;
162   int yperr = yp_match (domainname, "netid.byname", key, keylen, &result,
163                         &reslen);
164   if (__builtin_expect (yperr != YPERR_SUCCESS, 0))
165     return yperr2nss (yperr);
166
167   /* Parse the result: following the colon is a comma separated list of
168      group IDs.  */
169   char *cp = strchr (result, ':');
170   if (cp == NULL)
171     {
172     errout:
173       free (result);
174       return NSS_STATUS_NOTFOUND;
175     }
176   /* Skip the colon.  */
177   ++cp;
178
179   gid_t *groups = *groupsp;
180   while (*cp != '\0')
181     {
182       char *endp;
183       unsigned long int gid = strtoul (cp, &endp, 0);
184       if (cp == endp)
185         goto errout;
186       if (*endp == ',')
187         ++endp;
188       else if (*endp != '\0')
189         goto errout;
190       cp = endp;
191
192       if (gid == group)
193         /* We do not need this group again.  */
194         continue;
195
196       /* Insert this group.  */
197       if (*start == *size)
198         {
199           /* Need a bigger buffer.  */
200           long int newsize;
201
202           if (limit > 0 && *size == limit)
203             /* We reached the maximum.  */
204             break;
205
206           if (limit <= 0)
207             newsize = 2 * *size;
208           else
209             newsize = MIN (limit, 2 * *size);
210
211           gid_t *newgroups = realloc (groups, newsize * sizeof (*groups));
212           if (newgroups == NULL)
213             goto errout;
214           *groupsp = groups = newgroups;
215           *size = newsize;
216         }
217
218       groups[*start] = gid;
219       *start += 1;
220     }
221
222   free (result);
223
224   return NSS_STATUS_SUCCESS;
225 }
226
227
228 enum nss_status
229 _nss_nis_initgroups_dyn (const char *user, gid_t group, long int *start,
230                          long int *size, gid_t **groupsp, long int limit,
231                          int *errnop)
232 {
233   /* We always need the domain name.  */
234   char *domainname;
235   if (yp_get_default_domain (&domainname))
236     return NSS_STATUS_UNAVAIL;
237
238   /* Check whether we are supposed to use the netid.byname map.  */
239   if (_nsl_default_nss () & NSS_FLAG_NETID_AUTHORITATIVE)
240     {
241       /* We need the user ID.  */
242       uid_t uid;
243
244       if (get_uid (user, &uid) == 0
245           && initgroups_netid (uid, group, start, size, groupsp, limit,
246                                errnop, domainname) == NSS_STATUS_SUCCESS)
247         return NSS_STATUS_SUCCESS;
248     }
249
250   struct group grpbuf, *g;
251   size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
252   char *tmpbuf;
253   enum nss_status status;
254   intern_t intern = { NULL, NULL, 0 };
255   gid_t *groups = *groupsp;
256
257   status = internal_setgrent (domainname, &intern);
258   if (status != NSS_STATUS_SUCCESS)
259     return status;
260
261   tmpbuf = __alloca (buflen);
262
263   do
264     {
265       while ((status =
266               internal_getgrent_r (&grpbuf, tmpbuf, buflen, errnop,
267                                    &intern)) == NSS_STATUS_TRYAGAIN
268              && *errnop == ERANGE)
269         tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
270
271       if (status != NSS_STATUS_SUCCESS)
272         goto done;
273
274
275       g = &grpbuf;
276       if (g->gr_gid != group)
277         {
278           char **m;
279
280           for (m = g->gr_mem; *m != NULL; ++m)
281             if (strcmp (*m, user) == 0)
282               {
283                 /* Matches user.  Insert this group.  */
284                 if (*start == *size)
285                   {
286                     /* Need a bigger buffer.  */
287                     gid_t *newgroups;
288                     long int newsize;
289
290                     if (limit > 0 && *size == limit)
291                       /* We reached the maximum.  */
292                       goto done;
293
294                     if (limit <= 0)
295                       newsize = 2 * *size;
296                     else
297                       newsize = MIN (limit, 2 * *size);
298
299                     newgroups = realloc (groups, newsize * sizeof (*groups));
300                     if (newgroups == NULL)
301                       goto done;
302                     *groupsp = groups = newgroups;
303                     *size = newsize;
304                   }
305
306                 groups[*start] = g->gr_gid;
307                 *start += 1;
308
309                 break;
310               }
311         }
312     }
313   while (status == NSS_STATUS_SUCCESS);
314
315 done:
316   while (intern.start != NULL)
317     {
318       intern.next = intern.start;
319       intern.start = intern.start->next;
320       free (intern.next);
321     }
322
323   return NSS_STATUS_SUCCESS;
324 }