]> andersk Git - nss_nonlocal.git/blob - nonlocal-group.c
b4a72d10f3c49aedc1bfdcbe9c78e156eb60ff7b
[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
28 #include <sys/types.h>
29 #include <dlfcn.h>
30 #include <errno.h>
31 #include <grp.h>
32 #include <nss.h>
33 #include <pwd.h>
34 #include <stdbool.h>
35 #include <stddef.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <syslog.h>
39 #include <unistd.h>
40
41 #include "nsswitch-internal.h"
42 #include "nonlocal.h"
43
44 /*
45  * If the MAGIC_NONLOCAL_GROUPNAME local group exists, then nonlocal
46  * users will be automatically added to it.  Furthermore, if a local
47  * user is added to this group, then that user will inherit any
48  * nonlocal gids from a nonlocal user of the same name, as
49  * supplementary gids.
50  */
51 #define MAGIC_NONLOCAL_GROUPNAME "nss-nonlocal-users"
52
53 /*
54  * If the MAGIC_LOCAL_GROUPNAME local group exists, then local users
55  * will be automatically added to it.
56  */
57 #define MAGIC_LOCAL_GROUPNAME "nss-local-users"
58
59 /*
60  * If the MAGIC_NONLOCAL_USERNAME local user is added to a local
61  * group, then the local group will inherit the nonlocal membership of
62  * a group of the same gid.
63  */
64 #define MAGIC_NONLOCAL_USERNAME "nss-nonlocal-users"
65
66
67 enum nss_status
68 _nss_nonlocal_getgrnam_r(const char *name, struct group *grp,
69                          char *buffer, size_t buflen, int *errnop);
70
71 enum nss_status
72 _nss_nonlocal_getgrgid_r(gid_t gid, struct group *grp,
73                          char *buffer, size_t buflen, int *errnop);
74
75
76 static service_user *__nss_group_nonlocal_database;
77
78 static int
79 internal_function
80 __nss_group_nonlocal_lookup(service_user **ni, const char *fct_name,
81                             void **fctp)
82 {
83     if (__nss_group_nonlocal_database == NULL
84         && __nss_database_lookup("group_nonlocal", NULL, NULL,
85                                  &__nss_group_nonlocal_database) < 0)
86         return -1;
87
88     *ni = __nss_group_nonlocal_database;
89
90     *fctp = __nss_lookup_function(*ni, fct_name);
91     return 0;
92 }
93
94
95 enum nss_status
96 check_nonlocal_gid(const char *user, const char *group, gid_t gid, int *errnop)
97 {
98     enum nss_status status;
99     struct group gbuf;
100     char *buf;
101     size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
102     const struct walk_nss w = {
103         .lookup = &__nss_group_lookup, .fct_name = "getgrgid_r",
104         .status = &status, .errnop = errnop, .buf = &buf, .buflen = &buflen
105     };
106     const __typeof__(&_nss_nonlocal_getgrgid_r) self = &_nss_nonlocal_getgrgid_r;
107 #define args (gid, &gbuf, buf, buflen, errnop)
108 #include "walk_nss.h"
109 #undef args
110
111     if (status == NSS_STATUS_TRYAGAIN)
112         return status;
113     else if (status != NSS_STATUS_SUCCESS)
114         return NSS_STATUS_SUCCESS;
115
116     if (group == NULL || strcmp(gbuf.gr_name, group) == 0) {
117         char *const *mem;
118         for (mem = gbuf.gr_mem; *mem != NULL; mem++)
119             if (strcmp(*mem, MAGIC_NONLOCAL_USERNAME) == 0) {
120                 status = check_nonlocal_user(*mem, errnop);
121                 if (status == NSS_STATUS_TRYAGAIN) {
122                     free(buf);
123                     return status;
124                 } else if (status == NSS_STATUS_NOTFOUND) {
125                     free(buf);
126                     return NSS_STATUS_SUCCESS;
127                 }
128                 break;
129             }
130     }
131
132     syslog(LOG_DEBUG, "nss_nonlocal: removing local group %u (%s) from non-local user %s\n", gbuf.gr_gid, gbuf.gr_name, user);
133     free(buf);
134     return NSS_STATUS_NOTFOUND;
135 }
136
137 enum nss_status
138 check_nonlocal_group(const char *user, struct group *grp, int *errnop)
139 {
140     enum nss_status status = NSS_STATUS_SUCCESS;
141     int old_errno = errno;
142     char *end;
143     unsigned long gid;
144
145     errno = 0;
146     gid = strtoul(grp->gr_name, &end, 10);
147     if (errno == 0 && *end == '\0' && (gid_t)gid == gid) {
148         errno = old_errno;
149         status = check_nonlocal_gid(user, grp->gr_name, gid, errnop);
150     } else
151         errno = old_errno;
152     if (status != NSS_STATUS_SUCCESS)
153         return status;
154
155     return check_nonlocal_gid(user, grp->gr_name, grp->gr_gid, errnop);
156 }
157
158 enum nss_status
159 get_local_group(const char *name, struct group *grp, char **buffer, int *errnop)
160 {
161     enum nss_status status;
162     size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
163     const struct walk_nss w = {
164         .lookup = &__nss_group_lookup, .fct_name = "getgrnam_r",
165         .status = &status, .errnop = errnop, .buf = buffer, .buflen = &buflen
166     };
167     const __typeof__(&_nss_nonlocal_getgrnam_r) self = &_nss_nonlocal_getgrnam_r;
168 #define args (name, grp, *buffer, buflen, errnop)
169 #include "walk_nss.h"
170 #undef args
171     return status;
172 }
173
174 static service_user *grent_startp, *grent_nip;
175 static void *grent_fct_start;
176 static union {
177     enum nss_status (*l)(struct group *grp, char *buffer, size_t buflen,
178                          int *errnop);
179     void *ptr;
180 } grent_fct;
181 static const char *grent_fct_name = "getgrent_r";
182
183 enum nss_status
184 _nss_nonlocal_setgrent(int stayopen)
185 {
186     enum nss_status status;
187     const struct walk_nss w = {
188         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "setgrent",
189         .status = &status
190     };
191     const __typeof__(&_nss_nonlocal_setgrent) self = NULL;
192 #define args (stayopen)
193 #include "walk_nss.h"
194 #undef args
195     if (status != NSS_STATUS_SUCCESS)
196         return status;
197
198     if (grent_fct_start == NULL)
199         __nss_group_nonlocal_lookup(&grent_startp, grent_fct_name,
200                                     &grent_fct_start);
201     grent_nip = grent_startp;
202     grent_fct.ptr = grent_fct_start;
203     return NSS_STATUS_SUCCESS;
204 }
205
206 enum nss_status
207 _nss_nonlocal_endgrent(void)
208 {
209     enum nss_status status;
210     const struct walk_nss w = {
211         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "endgrent",
212         .status = &status
213     };
214     const __typeof__(&_nss_nonlocal_endgrent) self = NULL;
215
216     grent_nip = NULL;
217
218 #define args ()
219 #include "walk_nss.h"
220 #undef args
221     return status;
222 }
223
224 enum nss_status
225 _nss_nonlocal_getgrent_r(struct group *grp, char *buffer, size_t buflen,
226                          int *errnop)
227 {
228     enum nss_status status;
229
230     char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV);
231     if (nonlocal_ignore != NULL && nonlocal_ignore[0] != '\0')
232         return NSS_STATUS_UNAVAIL;
233
234     if (grent_nip == NULL) {
235         status = _nss_nonlocal_setgrent(0);
236         if (status != NSS_STATUS_SUCCESS)
237             return status;
238     }
239     do {
240         if (grent_fct.ptr == NULL)
241             status = NSS_STATUS_UNAVAIL;
242         else {
243             int nonlocal_errno;
244             do
245                 status = DL_CALL_FCT(grent_fct.l, (grp, buffer, buflen, errnop));
246             while (status == NSS_STATUS_SUCCESS &&
247                    check_nonlocal_group("(unknown)", grp, &nonlocal_errno) != NSS_STATUS_SUCCESS);
248         }
249         if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
250             return status;
251
252         if (status == NSS_STATUS_SUCCESS)
253             return NSS_STATUS_SUCCESS;
254     } while (__nss_next(&grent_nip, grent_fct_name, &grent_fct.ptr, status, 0) == 0);
255
256     grent_nip = NULL;
257     return NSS_STATUS_NOTFOUND;
258 }
259
260
261 enum nss_status
262 _nss_nonlocal_getgrnam_r(const char *name, struct group *grp,
263                          char *buffer, size_t buflen, int *errnop)
264 {
265     enum nss_status status;
266     const struct walk_nss w = {
267         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "getgrnam_r",
268         .status = &status, .errnop = errnop
269     };
270     const __typeof__(&_nss_nonlocal_getgrnam_r) self = NULL;
271
272     char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV);
273     if (nonlocal_ignore != NULL && nonlocal_ignore[0] != '\0')
274         return NSS_STATUS_UNAVAIL;
275
276 #define args (name, grp, buffer, buflen, errnop)
277 #include "walk_nss.h"
278 #undef args
279     if (status != NSS_STATUS_SUCCESS)
280         return status;
281
282     if (strcmp(name, grp->gr_name) != 0) {
283         syslog(LOG_ERR, "nss_nonlocal: discarding group %s from lookup for group %s\n", grp->gr_name, name);
284         return NSS_STATUS_NOTFOUND;
285     }
286
287     return check_nonlocal_group(name, grp, errnop);
288 }
289
290 enum nss_status
291 _nss_nonlocal_getgrgid_r(gid_t gid, struct group *grp,
292                          char *buffer, size_t buflen, int *errnop)
293 {
294     enum nss_status status;
295     const struct walk_nss w = {
296         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "getgrgid_r",
297         .status = &status, .errnop = errnop
298     };
299     const __typeof__(&_nss_nonlocal_getgrgid_r) self = NULL;
300
301     char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV);
302     if (nonlocal_ignore != NULL && nonlocal_ignore[0] != '\0')
303         return NSS_STATUS_UNAVAIL;
304
305 #define args (gid, grp, buffer, buflen, errnop)
306 #include "walk_nss.h"
307 #undef args
308     if (status != NSS_STATUS_SUCCESS)
309         return status;
310
311     if (gid != grp->gr_gid) {
312         syslog(LOG_ERR, "nss_nonlocal: discarding gid %d from lookup for gid %d\n", grp->gr_gid, gid);
313         return NSS_STATUS_NOTFOUND;
314     }
315
316     return check_nonlocal_group(grp->gr_name, grp, errnop);
317 }
318
319 static bool
320 add_group(gid_t group, long int *start, long int *size, gid_t **groupsp,
321           long int limit, int *errnop, enum nss_status *status)
322 {
323     int i, old_errno = errno;
324     for (i = 0; i < *start; ++i)
325         if ((*groupsp)[i] == group)
326             return true;
327     if (*start + 1 > *size) {
328         gid_t *newgroups;
329         long int newsize = 2 * *size;
330         if (limit > 0) {
331             if (*size >= limit) {
332                 *status = NSS_STATUS_SUCCESS;
333                 return false;
334             }
335             if (newsize > limit)
336                 newsize = limit;
337         }
338         newgroups = realloc(*groupsp, newsize * sizeof((*groupsp)[0]));
339         errno = old_errno;
340         if (newgroups == NULL) {
341             *errnop = ENOMEM;
342             *status = NSS_STATUS_TRYAGAIN;
343             return false;
344         }
345         *groupsp = newgroups;
346         *size = newsize;
347     }
348     (*groupsp)[(*start)++] = group;
349     return true;
350 }
351
352 enum nss_status
353 _nss_nonlocal_initgroups_dyn(const char *user, gid_t group, long int *start,
354                              long int *size, gid_t **groupsp, long int limit,
355                              int *errnop)
356 {
357     enum nss_status status;
358     const struct walk_nss w = {
359         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "initgroups_dyn",
360         .status = &status, .errnop = errnop
361     };
362     const __typeof__(&_nss_nonlocal_initgroups_dyn) self = NULL;
363
364     struct group local_users_group, nonlocal_users_group;
365     bool is_nonlocal = true;
366     char *buffer;
367     int in, out, i;
368
369     /* Check that the user is a nonlocal user, or a member of the
370      * MAGIC_NONLOCAL_GROUPNAME group, before adding any groups. */
371     status = check_nonlocal_user(user, errnop);
372     if (status == NSS_STATUS_TRYAGAIN) {
373         return status;
374     } else if (status != NSS_STATUS_SUCCESS) {
375         is_nonlocal = false;
376
377         status = get_local_group(MAGIC_LOCAL_GROUPNAME,
378                                  &local_users_group, &buffer, errnop);
379         if (status == NSS_STATUS_SUCCESS) {
380             free(buffer);
381             if (!add_group(local_users_group.gr_gid, start, size, groupsp,
382                            limit, errnop, &status))
383                 return status;
384         } else if (status == NSS_STATUS_TRYAGAIN) {
385             return status;
386         } else {
387             syslog(LOG_WARNING,
388                    "nss_nonlocal: Group %s does not exist locally!",
389                    MAGIC_LOCAL_GROUPNAME);
390         }
391     }
392
393     status = get_local_group(MAGIC_NONLOCAL_GROUPNAME,
394                              &nonlocal_users_group, &buffer, errnop);
395     if (status == NSS_STATUS_SUCCESS) {
396         free(buffer);
397         if (is_nonlocal) {
398             if (!add_group(nonlocal_users_group.gr_gid, start, size, groupsp,
399                            limit, errnop, &status))
400                 return status;
401         } else {
402             int i;
403             for (i = 0; i < *start; ++i) {
404                 if ((*groupsp)[i] == nonlocal_users_group.gr_gid) {
405                     is_nonlocal = true;
406                     break;
407                 }
408             }
409
410             if (is_nonlocal) {
411                 struct passwd pwbuf;
412                 char *buf;
413                 int nonlocal_errno = *errnop;
414                 status = get_nonlocal_passwd(user, &pwbuf, &buf, errnop);
415
416                 if (status == NSS_STATUS_SUCCESS) {
417                     nonlocal_errno = *errnop;
418                     status = check_nonlocal_gid(user, NULL, pwbuf.pw_gid,
419                                                 &nonlocal_errno);
420                     free(buf);
421                 }
422
423                 if (status == NSS_STATUS_SUCCESS) {
424                     if (!add_group(pwbuf.pw_gid, start, size, groupsp, limit,
425                                    errnop, &status))
426                         return status;
427                 } else if (status == NSS_STATUS_TRYAGAIN) {
428                     *errnop = nonlocal_errno;
429                     return status;
430                 }
431             }
432         }
433     } else if (status == NSS_STATUS_TRYAGAIN) {
434         if (is_nonlocal)
435             return status;
436     } else {
437         syslog(LOG_WARNING, "nss_nonlocal: Group %s does not exist locally!",
438                MAGIC_NONLOCAL_GROUPNAME);
439     }
440
441     if (!is_nonlocal)
442         return NSS_STATUS_SUCCESS;
443
444     in = out = *start;
445
446 #define args (user, group, start, size, groupsp, limit, errnop)
447 #include "walk_nss.h"
448 #undef args
449     if (status != NSS_STATUS_SUCCESS)
450         return status;
451
452     for (; in < *start; ++in) {
453         int nonlocal_errno = *errnop;
454
455         for (i = 0; i < out; ++i)
456             if ((*groupsp)[i] == (*groupsp)[in])
457                 break;
458         if (i < out)
459             continue;
460
461         status = check_nonlocal_gid(user, NULL, (*groupsp)[in],
462                                     &nonlocal_errno);
463         if (status == NSS_STATUS_SUCCESS) {
464             (*groupsp)[out++] = (*groupsp)[in];
465         } else if (status == NSS_STATUS_TRYAGAIN) {
466             *start = out;
467             *errnop = nonlocal_errno;
468             return status;
469         }
470     }
471
472     *start = out;
473     return NSS_STATUS_SUCCESS;
474 }
This page took 0.326502 seconds and 3 git commands to generate.