3e876d889d5a5be924e1ad450a7720f056795c3b
[debian/sudo] / plugins / sudoers / pwutil.c
1 /*
2  * Copyright (c) 1996, 1998-2005, 2007-2012
3  *      Todd C. Miller <Todd.Miller@courtesan.com>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  *
17  * Sponsored in part by the Defense Advanced Research Projects
18  * Agency (DARPA) and Air Force Research Laboratory, Air Force
19  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
20  */
21
22 #include <config.h>
23
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <sys/param.h>
27 #include <stdio.h>
28 #ifdef STDC_HEADERS
29 # include <stdlib.h>
30 # include <stddef.h>
31 #else
32 # ifdef HAVE_STDLIB_H
33 #  include <stdlib.h>
34 # endif
35 #endif /* STDC_HEADERS */
36 #ifdef HAVE_STRING_H
37 # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
38 #  include <memory.h>
39 # endif
40 # include <string.h>
41 #endif /* HAVE_STRING_H */
42 #ifdef HAVE_STRINGS_H
43 # include <strings.h>
44 #endif /* HAVE_STRINGS_H */
45 #ifdef HAVE_UNISTD_H
46 # include <unistd.h>
47 #endif /* HAVE_UNISTD_H */
48 #ifdef HAVE_SETAUTHDB
49 # include <usersec.h>
50 #endif /* HAVE_SETAUTHDB */
51 #ifdef HAVE_UTMPX_H
52 # include <utmpx.h>
53 #else
54 # include <utmp.h>
55 #endif /* HAVE_UTMPX_H */
56 #include <limits.h>
57 #include <pwd.h>
58 #include <grp.h>
59
60 #include "sudoers.h"
61 #include "redblack.h"
62
63 /*
64  * The passwd and group caches.
65  */
66 static struct rbtree *pwcache_byuid, *pwcache_byname;
67 static struct rbtree *grcache_bygid, *grcache_byname;
68 static struct rbtree *grlist_cache;
69
70 static int  cmp_pwuid(const void *, const void *);
71 static int  cmp_pwnam(const void *, const void *);
72 static int  cmp_grgid(const void *, const void *);
73
74 #define cmp_grnam       cmp_pwnam
75
76 #define ptr_to_item(p) ((struct cache_item *)((char *)p - offsetof(struct cache_item_##p, p)))
77
78 /*
79  * Generic cache element.
80  */
81 struct cache_item {
82     unsigned int refcnt;
83     /* key */
84     union {
85         uid_t uid;
86         gid_t gid;
87         char *name;
88     } k;
89     /* datum */
90     union {
91         struct passwd *pw;
92         struct group *gr;
93         struct group_list *grlist;
94     } d;
95 };
96
97 /*
98  * Container structs to simpify size and offset calculations and guarantee
99  * proper aligment of struct passwd, group and group_list.
100  */
101 struct cache_item_pw {
102     struct cache_item cache;
103     struct passwd pw;
104 };
105
106 struct cache_item_gr {
107     struct cache_item cache;
108     struct group gr;
109 };
110
111 struct cache_item_grlist {
112     struct cache_item cache;
113     struct group_list grlist;
114     /* actually bigger */
115 };
116
117 /*
118  * Compare by uid.
119  */
120 static int
121 cmp_pwuid(const void *v1, const void *v2)
122 {
123     const struct cache_item *ci1 = (const struct cache_item *) v1;
124     const struct cache_item *ci2 = (const struct cache_item *) v2;
125     return ci1->k.uid - ci2->k.uid;
126 }
127
128 /*
129  * Compare by user name.
130  */
131 static int
132 cmp_pwnam(const void *v1, const void *v2)
133 {
134     const struct cache_item *ci1 = (const struct cache_item *) v1;
135     const struct cache_item *ci2 = (const struct cache_item *) v2;
136     return strcmp(ci1->k.name, ci2->k.name);
137 }
138
139 #define FIELD_SIZE(src, name, size)                     \
140 do {                                                    \
141         if (src->name) {                                \
142                 size = strlen(src->name) + 1;           \
143                 total += size;                          \
144         }                                               \
145 } while (0)
146
147 #define FIELD_COPY(src, dst, name, size)                \
148 do {                                                    \
149         if (src->name) {                                \
150                 memcpy(cp, src->name, size);            \
151                 dst->name = cp;                         \
152                 cp += size;                             \
153         }                                               \
154 } while (0)
155
156 /*
157  * Dynamically allocate space for a struct item plus the key and data
158  * elements.  If name is non-NULL it is used as the key, else the
159  * uid is the key.  Fills in datum from struct password.
160  */
161 static struct cache_item *
162 make_pwitem(const struct passwd *pw, const char *name)
163 {
164     char *cp;
165     const char *pw_shell;
166     size_t nsize, psize, csize, gsize, dsize, ssize, total;
167     struct cache_item_pw *pwitem;
168     struct passwd *newpw;
169     debug_decl(make_pwitem, SUDO_DEBUG_NSS)
170
171     /* If shell field is empty, expand to _PATH_BSHELL. */
172     pw_shell = (pw->pw_shell == NULL || pw->pw_shell[0] == '\0')
173         ? _PATH_BSHELL : pw->pw_shell;
174
175     /* Allocate in one big chunk for easy freeing. */
176     nsize = psize = csize = gsize = dsize = ssize = 0;
177     total = sizeof(*pwitem);
178     FIELD_SIZE(pw, pw_name, nsize);
179     FIELD_SIZE(pw, pw_passwd, psize);
180 #ifdef HAVE_LOGIN_CAP_H
181     FIELD_SIZE(pw, pw_class, csize);
182 #endif
183     FIELD_SIZE(pw, pw_gecos, gsize);
184     FIELD_SIZE(pw, pw_dir, dsize);
185     /* Treat shell specially since we expand "" -> _PATH_BSHELL */
186     ssize = strlen(pw_shell) + 1;
187     total += ssize;
188     if (name != NULL)
189         total += strlen(name) + 1;
190
191     /* Allocate space for struct item, struct passwd and the strings. */
192     pwitem = ecalloc(1, total);
193     newpw = &pwitem->pw;
194
195     /*
196      * Copy in passwd contents and make strings relative to space
197      * at the end of the struct.
198      */
199     memcpy(newpw, pw, sizeof(*pw));
200     cp = (char *)(pwitem + 1);
201     FIELD_COPY(pw, newpw, pw_name, nsize);
202     FIELD_COPY(pw, newpw, pw_passwd, psize);
203 #ifdef HAVE_LOGIN_CAP_H
204     FIELD_COPY(pw, newpw, pw_class, csize);
205 #endif
206     FIELD_COPY(pw, newpw, pw_gecos, gsize);
207     FIELD_COPY(pw, newpw, pw_dir, dsize);
208     /* Treat shell specially since we expand "" -> _PATH_BSHELL */
209     memcpy(cp, pw_shell, ssize);
210     newpw->pw_shell = cp;
211     cp += ssize;
212
213     /* Set key and datum. */
214     if (name != NULL) {
215         memcpy(cp, name, strlen(name) + 1);
216         pwitem->cache.k.name = cp;
217     } else {
218         pwitem->cache.k.uid = pw->pw_uid;
219     }
220     pwitem->cache.d.pw = newpw;
221     pwitem->cache.refcnt = 1;
222
223     debug_return_ptr(&pwitem->cache);
224 }
225
226 void
227 sudo_pw_addref(struct passwd *pw)
228 {
229     debug_decl(sudo_pw_addref, SUDO_DEBUG_NSS)
230     ptr_to_item(pw)->refcnt++;
231     debug_return;
232 }
233
234 static void
235 sudo_pw_delref_item(void *v)
236 {
237     struct cache_item *item = v;
238     debug_decl(sudo_pw_delref_item, SUDO_DEBUG_NSS)
239
240     if (--item->refcnt == 0)
241         efree(item);
242
243     debug_return;
244 }
245
246 void
247 sudo_pw_delref(struct passwd *pw)
248 {
249     debug_decl(sudo_pw_delref, SUDO_DEBUG_NSS)
250     sudo_pw_delref_item(ptr_to_item(pw));
251     debug_return;
252 }
253
254 /*
255  * Get a password entry by uid and allocate space for it.
256  */
257 struct passwd *
258 sudo_getpwuid(uid_t uid)
259 {
260     struct cache_item key, *item;
261     struct rbnode *node;
262     debug_decl(sudo_getpwuid, SUDO_DEBUG_NSS)
263
264     key.k.uid = uid;
265     if ((node = rbfind(pwcache_byuid, &key)) != NULL) {
266         item = (struct cache_item *) node->data;
267         goto done;
268     }
269     /*
270      * Cache passwd db entry if it exists or a negative response if not.
271      */
272 #ifdef HAVE_SETAUTHDB
273     aix_setauthdb(IDtouser(uid));
274 #endif
275     if ((key.d.pw = getpwuid(uid)) != NULL) {
276         item = make_pwitem(key.d.pw, NULL);
277         if (rbinsert(pwcache_byuid, item) != NULL)
278             errorx(1, _("unable to cache uid %u (%s), already exists"),
279                 (unsigned int) uid, item->d.pw->pw_name);
280     } else {
281         item = ecalloc(1, sizeof(*item));
282         item->refcnt = 1;
283         item->k.uid = uid;
284         /* item->d.pw = NULL; */
285         if (rbinsert(pwcache_byuid, item) != NULL)
286             errorx(1, _("unable to cache uid %u, already exists"),
287                 (unsigned int) uid);
288     }
289 #ifdef HAVE_SETAUTHDB
290     aix_restoreauthdb();
291 #endif
292 done:
293     item->refcnt++;
294     debug_return_ptr(item->d.pw);
295 }
296
297 /*
298  * Get a password entry by name and allocate space for it.
299  */
300 struct passwd *
301 sudo_getpwnam(const char *name)
302 {
303     struct cache_item key, *item;
304     struct rbnode *node;
305     size_t len;
306     debug_decl(sudo_getpwnam, SUDO_DEBUG_NSS)
307
308     key.k.name = (char *) name;
309     if ((node = rbfind(pwcache_byname, &key)) != NULL) {
310         item = (struct cache_item *) node->data;
311         goto done;
312     }
313     /*
314      * Cache passwd db entry if it exists or a negative response if not.
315      */
316 #ifdef HAVE_SETAUTHDB
317     aix_setauthdb((char *) name);
318 #endif
319     if ((key.d.pw = getpwnam(name)) != NULL) {
320         item = make_pwitem(key.d.pw, name);
321         if (rbinsert(pwcache_byname, item) != NULL)
322             errorx(1, _("unable to cache user %s, already exists"), name);
323     } else {
324         len = strlen(name) + 1;
325         item = ecalloc(1, sizeof(*item) + len);
326         item->refcnt = 1;
327         item->k.name = (char *) item + sizeof(*item);
328         memcpy(item->k.name, name, len);
329         /* item->d.pw = NULL; */
330         if (rbinsert(pwcache_byname, item) != NULL)
331             errorx(1, _("unable to cache user %s, already exists"), name);
332     }
333 #ifdef HAVE_SETAUTHDB
334     aix_restoreauthdb();
335 #endif
336 done:
337     item->refcnt++;
338     debug_return_ptr(item->d.pw);
339 }
340
341 /*
342  * Take a user, uid and gid and return a faked up passwd struct.
343  */
344 struct passwd *
345 sudo_fakepwnamid(const char *user, uid_t uid, gid_t gid)
346 {
347     struct cache_item_pw *pwitem;
348     struct passwd *pw;
349     struct rbnode *node;
350     size_t len, namelen;
351     int i;
352     debug_decl(sudo_fakepwnam, SUDO_DEBUG_NSS)
353
354     namelen = strlen(user);
355     len = sizeof(*pwitem) + namelen + 1 /* pw_name */ +
356         sizeof("*") /* pw_passwd */ + sizeof("") /* pw_gecos */ +
357         sizeof("/") /* pw_dir */ + sizeof(_PATH_BSHELL);
358
359     for (i = 0; i < 2; i++) {
360         pwitem = ecalloc(1, len);
361         pw = &pwitem->pw;
362         pw->pw_uid = uid;
363         pw->pw_gid = gid;
364         pw->pw_name = (char *)(pwitem + 1);
365         memcpy(pw->pw_name, user, namelen + 1);
366         pw->pw_passwd = pw->pw_name + namelen + 1;
367         memcpy(pw->pw_passwd, "*", 2);
368         pw->pw_gecos = pw->pw_passwd + 2;
369         pw->pw_gecos[0] = '\0';
370         pw->pw_dir = pw->pw_gecos + 1;
371         memcpy(pw->pw_dir, "/", 2);
372         pw->pw_shell = pw->pw_dir + 2;
373         memcpy(pw->pw_shell, _PATH_BSHELL, sizeof(_PATH_BSHELL));
374
375         pwitem->cache.refcnt = 1;
376         pwitem->cache.d.pw = pw;
377         if (i == 0) {
378             /* Store by uid, overwriting cached version. */
379             pwitem->cache.k.uid = pw->pw_uid;
380             if ((node = rbinsert(pwcache_byuid, &pwitem->cache)) != NULL) {
381                 sudo_pw_delref_item(node->data);
382                 node->data = &pwitem->cache;
383             }
384         } else {
385             /* Store by name, overwriting cached version. */
386             pwitem->cache.k.name = pw->pw_name;
387             if ((node = rbinsert(pwcache_byname, &pwitem->cache)) != NULL) {
388                 sudo_pw_delref_item(node->data);
389                 node->data = &pwitem->cache;
390             }
391         }
392     }
393     pwitem->cache.refcnt++;
394     debug_return_ptr(pw);
395 }
396
397 /*
398  * Take a uid in string form "#123" and return a faked up passwd struct.
399  */
400 struct passwd *
401 sudo_fakepwnam(const char *user, gid_t gid)
402 {
403     uid_t uid;
404
405     uid = (uid_t) atoi(user + 1);
406     return sudo_fakepwnamid(user, uid, gid);
407 }
408
409 void
410 sudo_setpwent(void)
411 {
412     debug_decl(sudo_setpwent, SUDO_DEBUG_NSS)
413
414     setpwent();
415     if (pwcache_byuid == NULL)
416         pwcache_byuid = rbcreate(cmp_pwuid);
417     if (pwcache_byname == NULL)
418         pwcache_byname = rbcreate(cmp_pwnam);
419
420     debug_return;
421 }
422
423 void
424 sudo_freepwcache(void)
425 {
426     debug_decl(sudo_freepwcache, SUDO_DEBUG_NSS)
427
428     if (pwcache_byuid != NULL) {
429         rbdestroy(pwcache_byuid, sudo_pw_delref_item);
430         pwcache_byuid = NULL;
431     }
432     if (pwcache_byname != NULL) {
433         rbdestroy(pwcache_byname, sudo_pw_delref_item);
434         pwcache_byname = NULL;
435     }
436
437     debug_return;
438 }
439
440 void
441 sudo_endpwent(void)
442 {
443     debug_decl(sudo_endpwent, SUDO_DEBUG_NSS)
444
445     endpwent();
446     sudo_freepwcache();
447
448     debug_return;
449 }
450
451 /*
452  * Compare by gid.
453  */
454 static int
455 cmp_grgid(const void *v1, const void *v2)
456 {
457     const struct cache_item *ci1 = (const struct cache_item *) v1;
458     const struct cache_item *ci2 = (const struct cache_item *) v2;
459     return ci1->k.gid - ci2->k.gid;
460 }
461
462 /*
463  * Dynamically allocate space for a struct item plus the key and data
464  * elements.  If name is non-NULL it is used as the key, else the
465  * gid is the key.  Fills in datum from struct group.
466  */
467 static struct cache_item *
468 make_gritem(const struct group *gr, const char *name)
469 {
470     char *cp;
471     size_t nsize, psize, nmem, total, len;
472     struct cache_item_gr *gritem;
473     struct group *newgr;
474     debug_decl(make_gritem, SUDO_DEBUG_NSS)
475
476     /* Allocate in one big chunk for easy freeing. */
477     nsize = psize = nmem = 0;
478     total = sizeof(*gritem);
479     FIELD_SIZE(gr, gr_name, nsize);
480     FIELD_SIZE(gr, gr_passwd, psize);
481     if (gr->gr_mem) {
482         for (nmem = 0; gr->gr_mem[nmem] != NULL; nmem++)
483             total += strlen(gr->gr_mem[nmem]) + 1;
484         nmem++;
485         total += sizeof(char *) * nmem;
486     }
487     if (name != NULL)
488         total += strlen(name) + 1;
489
490     gritem = ecalloc(1, total);
491
492     /*
493      * Copy in group contents and make strings relative to space
494      * at the end of the buffer.  Note that gr_mem must come
495      * immediately after struct group to guarantee proper alignment.
496      */
497     newgr = &gritem->gr;
498     memcpy(newgr, gr, sizeof(*gr));
499     cp = (char *)(gritem + 1);
500     if (gr->gr_mem) {
501         newgr->gr_mem = (char **)cp;
502         cp += sizeof(char *) * nmem;
503         for (nmem = 0; gr->gr_mem[nmem] != NULL; nmem++) {
504             len = strlen(gr->gr_mem[nmem]) + 1;
505             memcpy(cp, gr->gr_mem[nmem], len);
506             newgr->gr_mem[nmem] = cp;
507             cp += len;
508         }
509         newgr->gr_mem[nmem] = NULL;
510     }
511     FIELD_COPY(gr, newgr, gr_passwd, psize);
512     FIELD_COPY(gr, newgr, gr_name, nsize);
513
514     /* Set key and datum. */
515     if (name != NULL) {
516         memcpy(cp, name, strlen(name) + 1);
517         gritem->cache.k.name = cp;
518     } else {
519         gritem->cache.k.gid = gr->gr_gid;
520     }
521     gritem->cache.d.gr = newgr;
522     gritem->cache.refcnt = 1;
523
524     debug_return_ptr(&gritem->cache);
525 }
526
527 #ifdef HAVE_UTMPX_H
528 # define GROUPNAME_LEN  (sizeof((struct utmpx *)0)->ut_user + 1)
529 #else
530 # ifdef HAVE_STRUCT_UTMP_UT_USER
531 #  define GROUPNAME_LEN (sizeof((struct utmp *)0)->ut_user + 1)
532 # else
533 #  define GROUPNAME_LEN (sizeof((struct utmp *)0)->ut_name + 1)
534 # endif
535 #endif /* HAVE_UTMPX_H */
536
537 /*
538  * Dynamically allocate space for a struct item plus the key and data
539  * elements.  Fills in datum from the groups and gids arrays.
540  */
541 static struct cache_item *
542 make_grlist_item(const char *user, GETGROUPS_T *gids, int ngids)
543 {
544     char *cp;
545     size_t i, nsize, ngroups, total, len;
546     struct cache_item_grlist *grlitem;
547     struct group_list *grlist;
548     struct group *grp;
549     debug_decl(make_grlist_item, SUDO_DEBUG_NSS)
550
551 #ifdef HAVE_SETAUTHDB
552     aix_setauthdb((char *) user);
553 #endif
554
555     /* Allocate in one big chunk for easy freeing. */
556     nsize = strlen(user) + 1;
557     total = sizeof(*grlitem) + nsize;
558     total += sizeof(char *) * ngids;
559     total += sizeof(gid_t *) * ngids;
560     total += GROUPNAME_LEN * ngids;
561
562 again:
563     grlitem = ecalloc(1, total);
564
565     /*
566      * Copy in group list and make pointers relative to space
567      * at the end of the buffer.  Note that the groups array must come
568      * immediately after struct group to guarantee proper alignment.
569      */
570     grlist = &grlitem->grlist;
571     cp = (char *)(grlitem + 1);
572     grlist->groups = (char **)cp;
573     cp += sizeof(char *) * ngids;
574     grlist->gids = (gid_t *)cp;
575     cp += sizeof(gid_t) * ngids;
576
577     /* Set key and datum. */
578     memcpy(cp, user, nsize);
579     grlitem->cache.k.name = cp;
580     grlitem->cache.d.grlist = grlist;
581     grlitem->cache.refcnt = 1;
582     cp += nsize;
583
584     /*
585      * Store group IDs.
586      */
587     for (i = 0; i < ngids; i++)
588         grlist->gids[i] = gids[i];
589     grlist->ngids = ngids;
590
591     /*
592      * Resolve and store group names by ID.
593      */
594     ngroups = 0;
595     for (i = 0; i < ngids; i++) {
596         if ((grp = sudo_getgrgid(gids[i])) != NULL) {
597             len = strlen(grp->gr_name) + 1;
598             if (cp - (char *)grlitem + len > total) {
599                 total += len + GROUPNAME_LEN;
600                 efree(grlitem);
601                 sudo_gr_delref(grp);
602                 goto again;
603             }
604             memcpy(cp, grp->gr_name, len);
605             grlist->groups[ngroups++] = cp;
606             cp += len;
607             sudo_gr_delref(grp);
608         }
609     }
610     grlist->ngroups = ngroups;
611
612 #ifdef HAVE_SETAUTHDB
613     aix_restoreauthdb();
614 #endif
615
616     debug_return_ptr(&grlitem->cache);
617 }
618
619 void
620 sudo_gr_addref(struct group *gr)
621 {
622     debug_decl(sudo_gr_addref, SUDO_DEBUG_NSS)
623     ptr_to_item(gr)->refcnt++;
624     debug_return;
625 }
626
627 static void
628 sudo_gr_delref_item(void *v)
629 {
630     struct cache_item *item = v;
631     debug_decl(sudo_gr_delref_item, SUDO_DEBUG_NSS)
632
633     if (--item->refcnt == 0)
634         efree(item);
635
636     debug_return;
637 }
638
639 void
640 sudo_gr_delref(struct group *gr)
641 {
642     debug_decl(sudo_gr_delref, SUDO_DEBUG_NSS)
643     sudo_gr_delref_item(ptr_to_item(gr));
644     debug_return;
645 }
646
647 /*
648  * Get a group entry by gid and allocate space for it.
649  */
650 struct group *
651 sudo_getgrgid(gid_t gid)
652 {
653     struct cache_item key, *item;
654     struct rbnode *node;
655     debug_decl(sudo_getgrgid, SUDO_DEBUG_NSS)
656
657     key.k.gid = gid;
658     if ((node = rbfind(grcache_bygid, &key)) != NULL) {
659         item = (struct cache_item *) node->data;
660         goto done;
661     }
662     /*
663      * Cache group db entry if it exists or a negative response if not.
664      */
665     if ((key.d.gr = getgrgid(gid)) != NULL) {
666         item = make_gritem(key.d.gr, NULL);
667         if (rbinsert(grcache_bygid, item) != NULL)
668             errorx(1, _("unable to cache gid %u (%s), already exists"),
669                 (unsigned int) gid, key.d.gr->gr_name);
670     } else {
671         item = ecalloc(1, sizeof(*item));
672         item->refcnt = 1;
673         item->k.gid = gid;
674         /* item->d.gr = NULL; */
675         if (rbinsert(grcache_bygid, item) != NULL)
676             errorx(1, _("unable to cache gid %u, already exists"),
677                 (unsigned int) gid);
678     }
679 done:
680     item->refcnt++;
681     debug_return_ptr(item->d.gr);
682 }
683
684 /*
685  * Get a group entry by name and allocate space for it.
686  */
687 struct group *
688 sudo_getgrnam(const char *name)
689 {
690     struct cache_item key, *item;
691     struct rbnode *node;
692     size_t len;
693     debug_decl(sudo_getgrnam, SUDO_DEBUG_NSS)
694
695     key.k.name = (char *) name;
696     if ((node = rbfind(grcache_byname, &key)) != NULL) {
697         item = (struct cache_item *) node->data;
698         goto done;
699     }
700     /*
701      * Cache group db entry if it exists or a negative response if not.
702      */
703     if ((key.d.gr = getgrnam(name)) != NULL) {
704         item = make_gritem(key.d.gr, name);
705         if (rbinsert(grcache_byname, item) != NULL)
706             errorx(1, _("unable to cache group %s, already exists"), name);
707     } else {
708         len = strlen(name) + 1;
709         item = ecalloc(1, sizeof(*item) + len);
710         item->refcnt = 1;
711         item->k.name = (char *) item + sizeof(*item);
712         memcpy(item->k.name, name, len);
713         /* item->d.gr = NULL; */
714         if (rbinsert(grcache_byname, item) != NULL)
715             errorx(1, _("unable to cache group %s, already exists"), name);
716     }
717 done:
718     item->refcnt++;
719     debug_return_ptr(item->d.gr);
720 }
721
722 /*
723  * Take a gid in string form "#123" and return a faked up group struct.
724  */
725 struct group *
726 sudo_fakegrnam(const char *group)
727 {
728     struct cache_item_gr *gritem;
729     struct group *gr;
730     struct rbnode *node;
731     size_t len, namelen;
732     int i;
733     debug_decl(sudo_fakegrnam, SUDO_DEBUG_NSS)
734
735     namelen = strlen(group);
736     len = sizeof(*gritem) + namelen + 1;
737
738     for (i = 0; i < 2; i++) {
739         gritem = ecalloc(1, len);
740         gr = &gritem->gr;
741         gr->gr_gid = (gid_t) atoi(group + 1);
742         gr->gr_name = (char *)(gritem + 1);
743         memcpy(gr->gr_name, group, namelen + 1);
744
745         gritem->cache.refcnt = 1;
746         gritem->cache.d.gr = gr;
747         if (i == 0) {
748             /* Store by gid, overwriting cached version. */
749             gritem->cache.k.gid = gr->gr_gid;
750             if ((node = rbinsert(grcache_bygid, &gritem->cache)) != NULL) {
751                 sudo_gr_delref_item(node->data);
752                 node->data = &gritem->cache;
753             }
754         } else {
755             /* Store by name, overwriting cached version. */
756             gritem->cache.k.name = gr->gr_name;
757             if ((node = rbinsert(grcache_byname, &gritem->cache)) != NULL) {
758                 sudo_gr_delref_item(node->data);
759                 node->data = &gritem->cache;
760             }
761         }
762     }
763     gritem->cache.refcnt++;
764     debug_return_ptr(gr);
765 }
766
767 void
768 sudo_grlist_addref(struct group_list *grlist)
769 {
770     debug_decl(sudo_gr_addref, SUDO_DEBUG_NSS)
771     ptr_to_item(grlist)->refcnt++;
772     debug_return;
773 }
774
775 static void
776 sudo_grlist_delref_item(void *v)
777 {
778     struct cache_item *item = v;
779     debug_decl(sudo_gr_delref_item, SUDO_DEBUG_NSS)
780
781     if (--item->refcnt == 0)
782         efree(item);
783
784     debug_return;
785 }
786
787 void
788 sudo_grlist_delref(struct group_list *grlist)
789 {
790     debug_decl(sudo_gr_delref, SUDO_DEBUG_NSS)
791     sudo_grlist_delref_item(ptr_to_item(grlist));
792     debug_return;
793 }
794
795 void
796 sudo_setgrent(void)
797 {
798     debug_decl(sudo_setgrent, SUDO_DEBUG_NSS)
799
800     setgrent();
801     if (grcache_bygid == NULL)
802         grcache_bygid = rbcreate(cmp_grgid);
803     if (grcache_byname == NULL)
804         grcache_byname = rbcreate(cmp_grnam);
805     if (grlist_cache == NULL)
806         grlist_cache = rbcreate(cmp_grnam);
807
808     debug_return;
809 }
810
811 void
812 sudo_freegrcache(void)
813 {
814     debug_decl(sudo_freegrcache, SUDO_DEBUG_NSS)
815
816     if (grcache_bygid != NULL) {
817         rbdestroy(grcache_bygid, sudo_gr_delref_item);
818         grcache_bygid = NULL;
819     }
820     if (grcache_byname != NULL) {
821         rbdestroy(grcache_byname, sudo_gr_delref_item);
822         grcache_byname = NULL;
823     }
824     if (grlist_cache != NULL) {
825         rbdestroy(grlist_cache, sudo_grlist_delref_item);
826         grlist_cache = NULL;
827     }
828
829     debug_return;
830 }
831
832 void
833 sudo_endgrent(void)
834 {
835     debug_decl(sudo_endgrent, SUDO_DEBUG_NSS)
836
837     endgrent();
838     sudo_freegrcache();
839
840     debug_return;
841 }
842
843 struct group_list *
844 sudo_get_grlist(struct passwd *pw)
845 {
846     struct cache_item key, *item;
847     struct rbnode *node;
848     size_t len;
849     GETGROUPS_T *gids;
850     int ngids;
851     debug_decl(sudo_get_grlist, SUDO_DEBUG_NSS)
852
853     key.k.name = pw->pw_name;
854     if ((node = rbfind(grlist_cache, &key)) != NULL) {
855         item = (struct cache_item *) node->data;
856         goto done;
857     }
858     /*
859      * Cache group db entry if it exists or a negative response if not.
860      * Use gids list from front-end if possible, otherwise getgrouplist().
861      */
862     if (pw == sudo_user.pw && sudo_user.gids != NULL) {
863         gids = user_gids;
864         ngids = user_ngids;
865         user_gids = NULL;
866         user_ngids = 0;
867     } else {
868 #if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX)
869         ngids = (int)sysconf(_SC_NGROUPS_MAX) * 2;
870         if (ngids < 0)
871 #endif
872             ngids = NGROUPS_MAX * 2;
873         gids = emalloc2(ngids, sizeof(GETGROUPS_T));
874         if (getgrouplist(pw->pw_name, pw->pw_gid, gids, &ngids) == -1) {
875             efree(gids);
876             gids = emalloc2(ngids, sizeof(GETGROUPS_T));
877             if (getgrouplist(pw->pw_name, pw->pw_gid, gids, &ngids) == -1) {
878                 efree(gids);
879                 debug_return_ptr(NULL);
880             }
881         }
882     }
883     if (ngids > 0) {
884         if ((item = make_grlist_item(pw->pw_name, gids, ngids)) == NULL)
885             errorx(1, "unable to parse group list for %s", pw->pw_name);
886         efree(gids);
887         if (rbinsert(grlist_cache, item) != NULL)
888             errorx(1, "unable to cache group list for %s, already exists",
889                 pw->pw_name);
890     } else {
891         /* Should not happen. */
892         len = strlen(pw->pw_name) + 1;
893         item = ecalloc(1, sizeof(*item) + len);
894         item->refcnt = 1;
895         item->k.name = (char *) item + sizeof(*item);
896         memcpy(item->k.name, pw->pw_name, len);
897         /* item->d.grlist = NULL; */
898         if (rbinsert(grlist_cache, item) != NULL)
899             errorx(1, "unable to cache group list for %s, already exists",
900                 pw->pw_name);
901     }
902 done:
903     item->refcnt++;
904     debug_return_ptr(item->d.grlist);
905 }
906
907 bool
908 user_in_group(struct passwd *pw, const char *group)
909 {
910     struct group_list *grlist;
911     struct group *grp = NULL;
912     int i;
913     bool matched = false;
914     debug_decl(user_in_group, SUDO_DEBUG_NSS)
915
916     if ((grlist = sudo_get_grlist(pw)) != NULL) {
917         /*
918          * If it could be a sudo-style group ID check gids first.
919          */
920         if (group[0] == '#') {
921             gid_t gid = atoi(group + 1);
922             if (gid == pw->pw_gid) {
923                 matched = true;
924                 goto done;
925             }
926             for (i = 0; i < grlist->ngids; i++) {
927                 if (gid == grlist->gids[i]) {
928                     matched = true;
929                     goto done;
930                 }
931             }
932         }
933
934         /*
935          * Next check the supplementary group vector.
936          * It usually includes the password db group too.
937          */
938         for (i = 0; i < grlist->ngroups; i++) {
939             if (strcasecmp(group, grlist->groups[i]) == 0) {
940                 matched = true;
941                 goto done;
942             }
943         }
944
945         /* Finally check against user's primary (passwd file) group. */
946         if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) {
947             if (strcasecmp(group, grp->gr_name) == 0) {
948                 matched = true;
949                 goto done;
950             }
951         }
952 done:
953         if (grp != NULL)
954             sudo_gr_delref(grp);
955         sudo_grlist_delref(grlist);
956     }
957     debug_return_bool(matched);
958 }