]> andersk Git - nss_nonlocal.git/blob - nonlocal-group.c
Fix errno saving and restoring
[nss_nonlocal.git] / nonlocal-group.c
1 /*
2  * nonlocal-group.c
3  * group database for nss_nonlocal proxy
4  *
5  * Copyright © 2007–2010 Anders Kaseorg <andersk@mit.edu> and Tim
6  * Abbott <tabbott@mit.edu>
7  *
8  * This file is part of nss_nonlocal.
9  *
10  * nss_nonlocal is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * as published by the Free Software Foundation; either version 2.1 of
13  * the License, or (at your option) any later version.
14  *
15  * nss_nonlocal is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with nss_nonlocal; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23  * 02110-1301  USA
24  */
25
26 #define _GNU_SOURCE
27 #include <sys/types.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <stdint.h>
31 #include <string.h>
32 #include <dlfcn.h>
33 #include <stdio.h>
34 #include <syslog.h>
35 #include <errno.h>
36 #include <grp.h>
37 #include <nss.h>
38 #include "nsswitch-internal.h"
39 #include "nonlocal.h"
40
41 #define MAGIC_NONLOCAL_GROUPNAME "nss-nonlocal-users"
42 #define MAGIC_LOCAL_GROUPNAME "nss-local-users"
43
44
45 enum nss_status
46 _nss_nonlocal_getgrnam_r(const char *name, struct group *grp,
47                          char *buffer, size_t buflen, int *errnop);
48
49 enum nss_status
50 _nss_nonlocal_getgrgid_r(gid_t gid, struct group *grp,
51                          char *buffer, size_t buflen, int *errnop);
52
53
54 static service_user *
55 nss_group_nonlocal_database(void)
56 {
57     static service_user *nip = NULL;
58     if (nip == NULL)
59         __nss_database_lookup("group_nonlocal", NULL, "", &nip);
60
61     return nip;
62 }
63
64
65 enum nss_status
66 check_nonlocal_gid(const char *user, gid_t gid, int *errnop)
67 {
68     static const char *fct_name = "getgrgid_r";
69     static service_user *startp = NULL;
70     static void *fct_start = NULL;
71     enum nss_status status;
72     service_user *nip;
73     union {
74         enum nss_status (*l)(gid_t gid, struct group *grp,
75                              char *buffer, size_t buflen, int *errnop);
76         void *ptr;
77     } fct;
78     struct group gbuf;
79     int old_errno = errno;
80
81     size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
82     char *buf = malloc(buflen);
83     errno = old_errno;
84     if (buf == NULL) {
85         *errnop = ENOMEM;
86         return NSS_STATUS_TRYAGAIN;
87     }
88
89     if (fct_start == NULL &&
90         __nss_group_lookup(&startp, fct_name, &fct_start) != 0) {
91         free(buf);
92         return NSS_STATUS_UNAVAIL;
93     }
94     nip = startp;
95     fct.ptr = fct_start;
96     do {
97     morebuf:
98         if (fct.l == _nss_nonlocal_getgrgid_r)
99             status = NSS_STATUS_NOTFOUND;
100         else
101             status = DL_CALL_FCT(fct.l, (gid, &gbuf, buf, buflen, errnop));
102         if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE) {
103             free(buf);
104             buflen *= 2;
105             buf = malloc(buflen);
106             errno = old_errno;
107             if (buf == NULL) {
108                 *errnop = ENOMEM;
109                 return NSS_STATUS_TRYAGAIN;
110             }
111             goto morebuf;
112         }
113     } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
114
115     if (status == NSS_STATUS_SUCCESS) {
116         syslog(LOG_DEBUG, "nss_nonlocal: removing local group %u (%s) from non-local user %s\n", gbuf.gr_gid, gbuf.gr_name, user);
117         status = NSS_STATUS_NOTFOUND;
118     } else if (status != NSS_STATUS_TRYAGAIN) {
119         status = NSS_STATUS_SUCCESS;
120     }
121
122     free(buf);
123     return status;
124 }
125
126 enum nss_status
127 check_nonlocal_group(const char *user, struct group *grp, int *errnop)
128 {
129     enum nss_status status = NSS_STATUS_SUCCESS;
130     int old_errno = errno;
131     char *end;
132     unsigned long gid;
133
134     errno = 0;
135     gid = strtoul(grp->gr_name, &end, 10);
136     if (errno == 0 && *end == '\0' && (gid_t)gid == gid) {
137         errno = old_errno;
138         status = check_nonlocal_gid(user, gid, errnop);
139     } else
140         errno = old_errno;
141     if (status != NSS_STATUS_SUCCESS)
142         return status;
143
144     return check_nonlocal_gid(user, grp->gr_gid, errnop);
145 }
146
147 enum nss_status
148 get_local_group(const char *name, struct group *grp, char **buffer, int *errnop)
149 {
150     static const char *fct_name = "getgrnam_r";
151     static service_user *startp = NULL;
152     static void *fct_start = NULL;
153     enum nss_status status;
154     service_user *nip;
155     union {
156         enum nss_status (*l)(const char *name, struct group *grp,
157                              char *buffer, size_t buflen, int *errnop);
158         void *ptr;
159     } fct;
160     size_t buflen;
161     int old_errno = errno;
162
163     buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
164     *buffer = malloc(buflen);
165     errno = old_errno;
166     if (*buffer == NULL) {
167         *errnop = ENOMEM;
168         return NSS_STATUS_TRYAGAIN;
169     }
170
171     if (fct_start == NULL &&
172         __nss_group_lookup(&startp, fct_name, &fct_start) != 0) {
173         free(*buffer);
174         *buffer = NULL;
175         return NSS_STATUS_UNAVAIL;
176     }
177     nip = startp;
178     fct.ptr = fct_start;
179     do {
180     morebuf:
181         if (fct.l == _nss_nonlocal_getgrnam_r)
182             status = NSS_STATUS_NOTFOUND;
183         else
184             status = DL_CALL_FCT(fct.l, (name, grp, *buffer, buflen, errnop));
185         if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE) {
186             free(*buffer);
187             buflen *= 2;
188             *buffer = malloc(buflen);
189             errno = old_errno;
190             if (*buffer == NULL) {
191                 *errnop = ENOMEM;
192                 return NSS_STATUS_TRYAGAIN;
193             }
194             goto morebuf;
195         }
196     } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
197
198     if (status != NSS_STATUS_SUCCESS) {
199         free(*buffer);
200         *buffer = NULL;
201     }
202
203     return status;
204 }
205
206 static service_user *grent_nip = NULL;
207 static void *grent_fct_start;
208 static union {
209     enum nss_status (*l)(struct group *grp, char *buffer, size_t buflen,
210                          int *errnop);
211     void *ptr;
212 } grent_fct;
213 static const char *grent_fct_name = "getgrent_r";
214
215 enum nss_status
216 _nss_nonlocal_setgrent(int stayopen)
217 {
218     static const char *fct_name = "setgrent";
219     static void *fct_start = NULL;
220     enum nss_status status;
221     service_user *nip;
222     union {
223         enum nss_status (*l)(int stayopen);
224         void *ptr;
225     } fct;
226
227     nip = nss_group_nonlocal_database();
228     if (nip == NULL)
229         return NSS_STATUS_UNAVAIL;
230     if (fct_start == NULL)
231         fct_start = __nss_lookup_function(nip, fct_name);
232     fct.ptr = fct_start;
233     do {
234         if (fct.ptr == NULL)
235             status = NSS_STATUS_UNAVAIL;
236         else
237             status = DL_CALL_FCT(fct.l, (stayopen));
238     } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
239     if (status != NSS_STATUS_SUCCESS)
240         return status;
241
242     grent_nip = nip;
243     if (grent_fct_start == NULL)
244         grent_fct_start = __nss_lookup_function(nip, grent_fct_name);
245     grent_fct.ptr = grent_fct_start;
246     return NSS_STATUS_SUCCESS;
247 }
248
249 enum nss_status
250 _nss_nonlocal_endgrent(void)
251 {
252     static const char *fct_name = "endgrent";
253     static void *fct_start = NULL;
254     enum nss_status status;
255     service_user *nip;
256     union {
257         enum nss_status (*l)(void);
258         void *ptr;
259     } fct;
260
261     grent_nip = NULL;
262
263     nip = nss_group_nonlocal_database();
264     if (nip == NULL)
265         return NSS_STATUS_UNAVAIL;
266     if (fct_start == NULL)
267         fct_start = __nss_lookup_function(nip, fct_name);
268     fct.ptr = fct_start;
269     do {
270         if (fct.ptr == NULL)
271             status = NSS_STATUS_UNAVAIL;
272         else
273             status = DL_CALL_FCT(fct.l, ());
274     } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
275     return status;
276 }
277
278 enum nss_status
279 _nss_nonlocal_getgrent_r(struct group *grp, char *buffer, size_t buflen,
280                          int *errnop)
281 {
282     enum nss_status status;
283
284     char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV);
285     if (nonlocal_ignore != NULL && nonlocal_ignore[0] != '\0')
286         return NSS_STATUS_UNAVAIL;
287
288     if (grent_nip == NULL) {
289         status = _nss_nonlocal_setgrent(0);
290         if (status != NSS_STATUS_SUCCESS)
291             return status;
292     }
293     do {
294         if (grent_fct.ptr == NULL)
295             status = NSS_STATUS_UNAVAIL;
296         else {
297             int nonlocal_errno;
298             do
299                 status = DL_CALL_FCT(grent_fct.l, (grp, buffer, buflen, errnop));
300             while (status == NSS_STATUS_SUCCESS &&
301                    check_nonlocal_group("(unknown)", grp, &nonlocal_errno) != NSS_STATUS_SUCCESS);
302         }
303         if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
304             return status;
305
306         if (status == NSS_STATUS_SUCCESS)
307             return NSS_STATUS_SUCCESS;
308     } while (__nss_next(&grent_nip, grent_fct_name, &grent_fct.ptr, status, 0) == 0);
309
310     grent_nip = NULL;
311     return NSS_STATUS_NOTFOUND;
312 }
313
314
315 enum nss_status
316 _nss_nonlocal_getgrnam_r(const char *name, struct group *grp,
317                          char *buffer, size_t buflen, int *errnop)
318 {
319     static const char *fct_name = "getgrnam_r";
320     static void *fct_start = NULL;
321     enum nss_status status;
322     service_user *nip;
323     union {
324         enum nss_status (*l)(const char *name, struct group *grp,
325                              char *buffer, size_t buflen, int *errnop);
326         void *ptr;
327     } fct;
328
329     char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV);
330     if (nonlocal_ignore != NULL && nonlocal_ignore[0] != '\0')
331         return NSS_STATUS_UNAVAIL;
332
333     nip = nss_group_nonlocal_database();
334     if (nip == NULL)
335         return NSS_STATUS_UNAVAIL;
336     if (fct_start == NULL)
337         fct_start = __nss_lookup_function(nip, fct_name);
338     fct.ptr = fct_start;
339     do {
340         if (fct.ptr == NULL)
341             status = NSS_STATUS_UNAVAIL;
342         else
343             status = DL_CALL_FCT(fct.l, (name, grp, buffer, buflen, errnop));
344         if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
345             break;
346     } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
347     if (status != NSS_STATUS_SUCCESS)
348         return status;
349
350     if (strcmp(name, grp->gr_name) != 0) {
351         syslog(LOG_ERR, "nss_nonlocal: discarding group %s from lookup for group %s\n", grp->gr_name, name);
352         return NSS_STATUS_NOTFOUND;
353     }
354
355     return check_nonlocal_group(name, grp, errnop);
356 }
357
358 enum nss_status
359 _nss_nonlocal_getgrgid_r(gid_t gid, struct group *grp,
360                          char *buffer, size_t buflen, int *errnop)
361 {
362     static const char *fct_name = "getgrgid_r";
363     static void *fct_start = NULL;
364     enum nss_status status;
365     service_user *nip;
366     union {
367         enum nss_status (*l)(gid_t gid, struct group *grp,
368                              char *buffer, size_t buflen, int *errnop);
369         void *ptr;
370     } fct;
371
372     char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV);
373     if (nonlocal_ignore != NULL && nonlocal_ignore[0] != '\0')
374         return NSS_STATUS_UNAVAIL;
375
376     nip = nss_group_nonlocal_database();
377     if (nip == NULL)
378         return NSS_STATUS_UNAVAIL;
379     if (fct_start == NULL)
380         fct_start = __nss_lookup_function(nip, fct_name);
381     fct.ptr = fct_start;
382     do {
383         if (fct.ptr == NULL)
384             status = NSS_STATUS_UNAVAIL;
385         else
386             status = DL_CALL_FCT(fct.l, (gid, grp, buffer, buflen, errnop));
387         if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
388             break;
389     } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
390     if (status != NSS_STATUS_SUCCESS)
391         return status;
392
393     if (gid != grp->gr_gid) {
394         syslog(LOG_ERR, "nss_nonlocal: discarding gid %d from lookup for gid %d\n", grp->gr_gid, gid);
395         return NSS_STATUS_NOTFOUND;
396     }
397
398     return check_nonlocal_group(grp->gr_name, grp, errnop);
399 }
400
401 enum nss_status
402 _nss_nonlocal_initgroups_dyn(const char *user, gid_t group, long int *start,
403                              long int *size, gid_t **groupsp, long int limit,
404                              int *errnop)
405 {
406     static const char *fct_name = "initgroups_dyn";
407     static void *fct_start = NULL;
408     enum nss_status status;
409     service_user *nip;
410     union {
411         enum nss_status (*l)(const char *user, gid_t group, long int *start,
412                              long int *size, gid_t **groupsp, long int limit,
413                              int *errnop);
414         void *ptr;
415     } fct;
416
417     struct group local_users_group, nonlocal_users_group;
418     gid_t local_users_gid, gid;
419     int is_local = 0;
420     char *buffer;
421     int old_errno;
422     int in, out, i;
423
424     /* Check that the user is a nonlocal user before adding any groups. */
425     status = check_nonlocal_user(user, errnop);
426     if (status == NSS_STATUS_TRYAGAIN)
427         return status;
428     else if (status != NSS_STATUS_SUCCESS)
429         is_local = 1;
430
431     old_errno = errno;
432
433     status = get_local_group(MAGIC_LOCAL_GROUPNAME,
434                              &local_users_group, &buffer, errnop);
435     if (status == NSS_STATUS_SUCCESS) {
436         local_users_gid = local_users_group.gr_gid;
437         free(buffer);
438     } else if (status == NSS_STATUS_TRYAGAIN) {
439         return status;
440     } else {
441         syslog(LOG_WARNING, "nss_nonlocal: Group %s does not exist locally!",
442                MAGIC_LOCAL_GROUPNAME);
443         local_users_gid = -1;
444     }
445
446     if (is_local) {
447         gid = local_users_gid;
448     } else {
449         status = get_local_group(MAGIC_NONLOCAL_GROUPNAME,
450                                  &nonlocal_users_group, &buffer, errnop);
451         if (status == NSS_STATUS_SUCCESS) {
452             gid = nonlocal_users_group.gr_gid;
453             free(buffer);
454         } else if (status == NSS_STATUS_TRYAGAIN) {
455             return status;
456         } else {
457             syslog(LOG_WARNING, "nss_nonlocal: Group %s does not exist locally!",
458                    MAGIC_NONLOCAL_GROUPNAME);
459             gid = -1;
460         }
461     }
462
463     if (gid != -1) {
464         int i;
465         for (i = 0; i < *start; ++i)
466             if ((*groupsp)[i] == gid)
467                 break;
468         if (i >= *start) {
469             if (*start + 1 > *size) {
470                 gid_t *newgroups;
471                 long int newsize = 2 * *size;
472                 if (limit > 0) {
473                     if (*size >= limit)
474                         return NSS_STATUS_SUCCESS;
475                     if (newsize > limit)
476                         newsize = limit;
477                 }
478                 newgroups = realloc(*groupsp, newsize * sizeof((*groupsp)[0]));
479                 errno = old_errno;
480                 if (newgroups == NULL) {
481                     *errnop = ENOMEM;
482                     return NSS_STATUS_TRYAGAIN;
483                 }
484                 *groupsp = newgroups;
485                 *size = newsize;
486             }
487             (*groupsp)[(*start)++] = gid;
488         }
489     }
490
491     if (is_local)
492         return NSS_STATUS_SUCCESS;
493
494     in = out = *start;
495
496     nip = nss_group_nonlocal_database();
497     if (nip == NULL)
498         return NSS_STATUS_UNAVAIL;
499     if (fct_start == NULL)
500         fct_start = __nss_lookup_function(nip, fct_name);
501     fct.ptr = fct_start;
502
503     do {
504         if (fct.ptr == NULL)
505             status = NSS_STATUS_UNAVAIL;
506         else
507             status = DL_CALL_FCT(fct.l, (user, group, start, size, groupsp, limit, errnop));
508         if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
509             break;
510     } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0);
511     if (status != NSS_STATUS_SUCCESS)
512         return status;
513
514     for (; in < *start; ++in) {
515         int nonlocal_errno = *errnop;
516
517         for (i = 0; i < out; ++i)
518             if ((*groupsp)[i] == (*groupsp)[in])
519                 break;
520         if (i < out)
521             continue;
522
523         /* Don't let users get into MAGIC_LOCAL_GROUPNAME from nonlocal reasons. */
524         if (local_users_gid == (*groupsp)[in]) {
525             syslog(LOG_WARNING, "nss_nonlocal: Nonlocal user %s removed from special local users group %s",
526                    user, MAGIC_LOCAL_GROUPNAME);
527             continue;
528         }
529
530         status = check_nonlocal_gid(user, (*groupsp)[in], &nonlocal_errno);
531         if (status == NSS_STATUS_SUCCESS) {
532             (*groupsp)[out++] = (*groupsp)[in];
533         } else if (status == NSS_STATUS_TRYAGAIN) {
534             *start = out;
535             *errnop = nonlocal_errno;
536             return status;
537         }
538     }
539
540     *start = out;
541     return NSS_STATUS_SUCCESS;
542 }
This page took 0.328203 seconds and 5 git commands to generate.