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