]> andersk Git - nss_nonlocal.git/blob - nonlocal-group.c
Guard one-time initialization with memory barriers
[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 bool grent_initialized = false;
175 static service_user *grent_startp, *grent_nip;
176 static void *grent_fct_start;
177 static union {
178     enum nss_status (*l)(struct group *grp, char *buffer, size_t buflen,
179                          int *errnop);
180     void *ptr;
181 } grent_fct;
182 static const char *grent_fct_name = "getgrent_r";
183
184 enum nss_status
185 _nss_nonlocal_setgrent(int stayopen)
186 {
187     enum nss_status status;
188     const struct walk_nss w = {
189         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "setgrent",
190         .status = &status
191     };
192     const __typeof__(&_nss_nonlocal_setgrent) self = NULL;
193 #define args (stayopen)
194 #include "walk_nss.h"
195 #undef args
196     if (status != NSS_STATUS_SUCCESS)
197         return status;
198
199     if (!grent_initialized) {
200         __nss_group_nonlocal_lookup(&grent_startp, grent_fct_name,
201                                     &grent_fct_start);
202         __sync_synchronize();
203         grent_initialized = true;
204     }
205     grent_nip = grent_startp;
206     grent_fct.ptr = grent_fct_start;
207     return NSS_STATUS_SUCCESS;
208 }
209
210 enum nss_status
211 _nss_nonlocal_endgrent(void)
212 {
213     enum nss_status status;
214     const struct walk_nss w = {
215         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "endgrent",
216         .status = &status
217     };
218     const __typeof__(&_nss_nonlocal_endgrent) self = NULL;
219
220     grent_nip = NULL;
221
222 #define args ()
223 #include "walk_nss.h"
224 #undef args
225     return status;
226 }
227
228 enum nss_status
229 _nss_nonlocal_getgrent_r(struct group *grp, char *buffer, size_t buflen,
230                          int *errnop)
231 {
232     enum nss_status status;
233
234     char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV);
235     if (nonlocal_ignore != NULL && nonlocal_ignore[0] != '\0')
236         return NSS_STATUS_UNAVAIL;
237
238     if (grent_nip == NULL) {
239         status = _nss_nonlocal_setgrent(0);
240         if (status != NSS_STATUS_SUCCESS)
241             return status;
242     }
243     do {
244         if (grent_fct.ptr == NULL)
245             status = NSS_STATUS_UNAVAIL;
246         else {
247             int nonlocal_errno;
248             do
249                 status = DL_CALL_FCT(grent_fct.l, (grp, buffer, buflen, errnop));
250             while (status == NSS_STATUS_SUCCESS &&
251                    check_nonlocal_group("(unknown)", grp, &nonlocal_errno) != NSS_STATUS_SUCCESS);
252         }
253         if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
254             return status;
255
256         if (status == NSS_STATUS_SUCCESS)
257             return NSS_STATUS_SUCCESS;
258     } while (__nss_next(&grent_nip, grent_fct_name, &grent_fct.ptr, status, 0) == 0);
259
260     grent_nip = NULL;
261     return NSS_STATUS_NOTFOUND;
262 }
263
264
265 enum nss_status
266 _nss_nonlocal_getgrnam_r(const char *name, struct group *grp,
267                          char *buffer, size_t buflen, int *errnop)
268 {
269     enum nss_status status;
270     const struct walk_nss w = {
271         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "getgrnam_r",
272         .status = &status, .errnop = errnop
273     };
274     const __typeof__(&_nss_nonlocal_getgrnam_r) self = NULL;
275
276     char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV);
277     if (nonlocal_ignore != NULL && nonlocal_ignore[0] != '\0')
278         return NSS_STATUS_UNAVAIL;
279
280 #define args (name, grp, buffer, buflen, errnop)
281 #include "walk_nss.h"
282 #undef args
283     if (status != NSS_STATUS_SUCCESS)
284         return status;
285
286     if (strcmp(name, grp->gr_name) != 0) {
287         syslog(LOG_ERR, "nss_nonlocal: discarding group %s from lookup for group %s\n", grp->gr_name, name);
288         return NSS_STATUS_NOTFOUND;
289     }
290
291     return check_nonlocal_group(name, grp, errnop);
292 }
293
294 enum nss_status
295 _nss_nonlocal_getgrgid_r(gid_t gid, struct group *grp,
296                          char *buffer, size_t buflen, int *errnop)
297 {
298     enum nss_status status;
299     const struct walk_nss w = {
300         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "getgrgid_r",
301         .status = &status, .errnop = errnop
302     };
303     const __typeof__(&_nss_nonlocal_getgrgid_r) self = NULL;
304
305     char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV);
306     if (nonlocal_ignore != NULL && nonlocal_ignore[0] != '\0')
307         return NSS_STATUS_UNAVAIL;
308
309 #define args (gid, grp, buffer, buflen, errnop)
310 #include "walk_nss.h"
311 #undef args
312     if (status != NSS_STATUS_SUCCESS)
313         return status;
314
315     if (gid != grp->gr_gid) {
316         syslog(LOG_ERR, "nss_nonlocal: discarding gid %d from lookup for gid %d\n", grp->gr_gid, gid);
317         return NSS_STATUS_NOTFOUND;
318     }
319
320     return check_nonlocal_group(grp->gr_name, grp, errnop);
321 }
322
323 static bool
324 add_group(gid_t group, long int *start, long int *size, gid_t **groupsp,
325           long int limit, int *errnop, enum nss_status *status)
326 {
327     int i, old_errno = errno;
328     for (i = 0; i < *start; ++i)
329         if ((*groupsp)[i] == group)
330             return true;
331     if (*start + 1 > *size) {
332         gid_t *newgroups;
333         long int newsize = 2 * *size;
334         if (limit > 0) {
335             if (*size >= limit) {
336                 *status = NSS_STATUS_SUCCESS;
337                 return false;
338             }
339             if (newsize > limit)
340                 newsize = limit;
341         }
342         newgroups = realloc(*groupsp, newsize * sizeof((*groupsp)[0]));
343         errno = old_errno;
344         if (newgroups == NULL) {
345             *errnop = ENOMEM;
346             *status = NSS_STATUS_TRYAGAIN;
347             return false;
348         }
349         *groupsp = newgroups;
350         *size = newsize;
351     }
352     (*groupsp)[(*start)++] = group;
353     return true;
354 }
355
356 enum nss_status
357 _nss_nonlocal_initgroups_dyn(const char *user, gid_t group, long int *start,
358                              long int *size, gid_t **groupsp, long int limit,
359                              int *errnop)
360 {
361     enum nss_status status;
362     const struct walk_nss w = {
363         .lookup = &__nss_group_nonlocal_lookup, .fct_name = "initgroups_dyn",
364         .status = &status, .errnop = errnop
365     };
366     const __typeof__(&_nss_nonlocal_initgroups_dyn) self = NULL;
367
368     struct group local_users_group, nonlocal_users_group;
369     bool is_nonlocal = true;
370     char *buffer;
371     int in, out, i;
372
373     /* Check that the user is a nonlocal user, or a member of the
374      * MAGIC_NONLOCAL_GROUPNAME group, before adding any groups. */
375     status = check_nonlocal_user(user, errnop);
376     if (status == NSS_STATUS_TRYAGAIN) {
377         return status;
378     } else if (status != NSS_STATUS_SUCCESS) {
379         is_nonlocal = false;
380
381         status = get_local_group(MAGIC_LOCAL_GROUPNAME,
382                                  &local_users_group, &buffer, errnop);
383         if (status == NSS_STATUS_SUCCESS) {
384             free(buffer);
385             if (!add_group(local_users_group.gr_gid, start, size, groupsp,
386                            limit, errnop, &status))
387                 return status;
388         } else if (status == NSS_STATUS_TRYAGAIN) {
389             return status;
390         } else {
391             syslog(LOG_WARNING,
392                    "nss_nonlocal: Group %s does not exist locally!",
393                    MAGIC_LOCAL_GROUPNAME);
394         }
395     }
396
397     status = get_local_group(MAGIC_NONLOCAL_GROUPNAME,
398                              &nonlocal_users_group, &buffer, errnop);
399     if (status == NSS_STATUS_SUCCESS) {
400         free(buffer);
401         if (is_nonlocal) {
402             if (!add_group(nonlocal_users_group.gr_gid, start, size, groupsp,
403                            limit, errnop, &status))
404                 return status;
405         } else {
406             int i;
407             for (i = 0; i < *start; ++i) {
408                 if ((*groupsp)[i] == nonlocal_users_group.gr_gid) {
409                     is_nonlocal = true;
410                     break;
411                 }
412             }
413
414             if (is_nonlocal) {
415                 struct passwd pwbuf;
416                 char *buf;
417                 int nonlocal_errno = *errnop;
418                 status = get_nonlocal_passwd(user, &pwbuf, &buf, errnop);
419
420                 if (status == NSS_STATUS_SUCCESS) {
421                     nonlocal_errno = *errnop;
422                     status = check_nonlocal_gid(user, NULL, pwbuf.pw_gid,
423                                                 &nonlocal_errno);
424                     free(buf);
425                 }
426
427                 if (status == NSS_STATUS_SUCCESS) {
428                     if (!add_group(pwbuf.pw_gid, start, size, groupsp, limit,
429                                    errnop, &status))
430                         return status;
431                 } else if (status == NSS_STATUS_TRYAGAIN) {
432                     *errnop = nonlocal_errno;
433                     return status;
434                 }
435             }
436         }
437     } else if (status == NSS_STATUS_TRYAGAIN) {
438         if (is_nonlocal)
439             return status;
440     } else {
441         syslog(LOG_WARNING, "nss_nonlocal: Group %s does not exist locally!",
442                MAGIC_NONLOCAL_GROUPNAME);
443     }
444
445     if (!is_nonlocal)
446         return NSS_STATUS_SUCCESS;
447
448     in = out = *start;
449
450 #define args (user, group, start, size, groupsp, limit, errnop)
451 #include "walk_nss.h"
452 #undef args
453     if (status != NSS_STATUS_SUCCESS)
454         return status;
455
456     for (; in < *start; ++in) {
457         int nonlocal_errno = *errnop;
458
459         for (i = 0; i < out; ++i)
460             if ((*groupsp)[i] == (*groupsp)[in])
461                 break;
462         if (i < out)
463             continue;
464
465         status = check_nonlocal_gid(user, NULL, (*groupsp)[in],
466                                     &nonlocal_errno);
467         if (status == NSS_STATUS_SUCCESS) {
468             (*groupsp)[out++] = (*groupsp)[in];
469         } else if (status == NSS_STATUS_TRYAGAIN) {
470             *start = out;
471             *errnop = nonlocal_errno;
472             return status;
473         }
474     }
475
476     *start = out;
477     return NSS_STATUS_SUCCESS;
478 }
This page took 0.067162 seconds and 5 git commands to generate.