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