Imported Upstream version 1.8.2
[debian/sudo] / plugins / sudoers / pwutil.c
1 /*
2  * Copyright (c) 1996, 1998-2005, 2007-2011
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) - sizeof(struct cache_item)))
77
78 struct cache_item {
79     unsigned int refcnt;
80     /* key */
81     union {
82         uid_t uid;
83         gid_t gid;
84         char *name;
85     } k;
86     /* datum */
87     union {
88         struct passwd *pw;
89         struct group *gr;
90         struct group_list *grlist;
91     } d;
92 };
93
94 /*
95  * Compare by uid.
96  */
97 static int
98 cmp_pwuid(const void *v1, const void *v2)
99 {
100     const struct cache_item *ci1 = (const struct cache_item *) v1;
101     const struct cache_item *ci2 = (const struct cache_item *) v2;
102     return ci1->k.uid - ci2->k.uid;
103 }
104
105 /*
106  * Compare by user name.
107  */
108 static int
109 cmp_pwnam(const void *v1, const void *v2)
110 {
111     const struct cache_item *ci1 = (const struct cache_item *) v1;
112     const struct cache_item *ci2 = (const struct cache_item *) v2;
113     return strcmp(ci1->k.name, ci2->k.name);
114 }
115
116 #define FIELD_SIZE(src, name, size)                     \
117 do {                                                    \
118         if (src->name) {                                \
119                 size = strlen(src->name) + 1;           \
120                 total += size;                          \
121         }                                               \
122 } while (0)
123
124 #define FIELD_COPY(src, dst, name, size)                \
125 do {                                                    \
126         if (src->name) {                                \
127                 memcpy(cp, src->name, size);            \
128                 dst->name = cp;                         \
129                 cp += size;                             \
130         }                                               \
131 } while (0)
132
133 /*
134  * Dynamically allocate space for a struct item plus the key and data
135  * elements.  If name is non-NULL it is used as the key, else the
136  * uid is the key.  Fills in datum from struct password.
137  *
138  * We would like to fill in the encrypted password too but the
139  * call to the shadow function could overwrite the pw buffer (NIS).
140  */
141 static struct cache_item *
142 make_pwitem(const struct passwd *pw, const char *name)
143 {
144     char *cp;
145     const char *pw_shell;
146     size_t nsize, psize, csize, gsize, dsize, ssize, total;
147     struct cache_item *item;
148     struct passwd *newpw;
149
150     /* If shell field is empty, expand to _PATH_BSHELL. */
151     pw_shell = (pw->pw_shell == NULL || pw->pw_shell[0] == '\0')
152         ? _PATH_BSHELL : pw->pw_shell;
153
154     /* Allocate in one big chunk for easy freeing. */
155     nsize = psize = csize = gsize = dsize = ssize = 0;
156     total = sizeof(struct cache_item) + sizeof(struct passwd);
157     FIELD_SIZE(pw, pw_name, nsize);
158     FIELD_SIZE(pw, pw_passwd, psize);
159 #ifdef HAVE_LOGIN_CAP_H
160     FIELD_SIZE(pw, pw_class, csize);
161 #endif
162     FIELD_SIZE(pw, pw_gecos, gsize);
163     FIELD_SIZE(pw, pw_dir, dsize);
164     /* Treat shell specially since we expand "" -> _PATH_BSHELL */
165     ssize = strlen(pw_shell) + 1;
166     total += ssize;
167     if (name != NULL)
168         total += strlen(name) + 1;
169
170     /* Allocate space for struct item, struct passwd and the strings. */
171     item = emalloc(total);
172     cp = (char *) item + sizeof(struct cache_item);
173
174     /*
175      * Copy in passwd contents and make strings relative to space
176      * at the end of the buffer.
177      */
178     newpw = (struct passwd *) cp;
179     memcpy(newpw, pw, sizeof(struct passwd));
180     cp += sizeof(struct passwd);
181     FIELD_COPY(pw, newpw, pw_name, nsize);
182     FIELD_COPY(pw, newpw, pw_passwd, psize);
183 #ifdef HAVE_LOGIN_CAP_H
184     FIELD_COPY(pw, newpw, pw_class, csize);
185 #endif
186     FIELD_COPY(pw, newpw, pw_gecos, gsize);
187     FIELD_COPY(pw, newpw, pw_dir, dsize);
188     /* Treat shell specially since we expand "" -> _PATH_BSHELL */
189     memcpy(cp, pw_shell, ssize);
190     newpw->pw_shell = cp;
191     cp += ssize;
192
193     /* Set key and datum. */
194     if (name != NULL) {
195         memcpy(cp, name, strlen(name) + 1);
196         item->k.name = cp;
197     } else {
198         item->k.uid = pw->pw_uid;
199     }
200     item->d.pw = newpw;
201     item->refcnt = 1;
202
203     return item;
204 }
205
206 void
207 pw_addref(struct passwd *pw)
208 {
209     ptr_to_item(pw)->refcnt++;
210 }
211
212 static void
213 pw_delref_item(void *v)
214 {
215     struct cache_item *item = v;
216
217     if (--item->refcnt == 0)
218         efree(item);
219 }
220
221 void
222 pw_delref(struct passwd *pw)
223 {
224     pw_delref_item(ptr_to_item(pw));
225 }
226
227 /*
228  * Get a password entry by uid and allocate space for it.
229  * Fills in pw_passwd from shadow file if necessary.
230  */
231 struct passwd *
232 sudo_getpwuid(uid_t uid)
233 {
234     struct cache_item key, *item;
235     struct rbnode *node;
236
237     key.k.uid = uid;
238     if ((node = rbfind(pwcache_byuid, &key)) != NULL) {
239         item = (struct cache_item *) node->data;
240         goto done;
241     }
242     /*
243      * Cache passwd db entry if it exists or a negative response if not.
244      */
245 #ifdef HAVE_SETAUTHDB
246     aix_setauthdb(IDtouser(uid));
247 #endif
248     if ((key.d.pw = getpwuid(uid)) != NULL) {
249         item = make_pwitem(key.d.pw, NULL);
250         if (rbinsert(pwcache_byuid, item) != NULL)
251             errorx(1, _("unable to cache uid %u (%s), already exists"),
252                 (unsigned int) uid, item->d.pw->pw_name);
253     } else {
254         item = emalloc(sizeof(*item));
255         item->refcnt = 1;
256         item->k.uid = uid;
257         item->d.pw = NULL;
258         if (rbinsert(pwcache_byuid, item) != NULL)
259             errorx(1, _("unable to cache uid %u, already exists"),
260                 (unsigned int) uid);
261     }
262 #ifdef HAVE_SETAUTHDB
263     aix_restoreauthdb();
264 #endif
265 done:
266     item->refcnt++;
267     return item->d.pw;
268 }
269
270 /*
271  * Get a password entry by name and allocate space for it.
272  * Fills in pw_passwd from shadow file if necessary.
273  */
274 struct passwd *
275 sudo_getpwnam(const char *name)
276 {
277     struct cache_item key, *item;
278     struct rbnode *node;
279     size_t len;
280
281     key.k.name = (char *) name;
282     if ((node = rbfind(pwcache_byname, &key)) != NULL) {
283         item = (struct cache_item *) node->data;
284         goto done;
285     }
286     /*
287      * Cache passwd db entry if it exists or a negative response if not.
288      */
289 #ifdef HAVE_SETAUTHDB
290     aix_setauthdb((char *) name);
291 #endif
292     if ((key.d.pw = getpwnam(name)) != NULL) {
293         item = make_pwitem(key.d.pw, name);
294         if (rbinsert(pwcache_byname, item) != NULL)
295             errorx(1, _("unable to cache user %s, already exists"), name);
296     } else {
297         len = strlen(name) + 1;
298         item = emalloc(sizeof(*item) + len);
299         item->refcnt = 1;
300         item->k.name = (char *) item + sizeof(*item);
301         memcpy(item->k.name, name, len);
302         item->d.pw = NULL;
303         if (rbinsert(pwcache_byname, item) != NULL)
304             errorx(1, _("unable to cache user %s, already exists"), name);
305     }
306 #ifdef HAVE_SETAUTHDB
307     aix_restoreauthdb();
308 #endif
309 done:
310     item->refcnt++;
311     return item->d.pw;
312 }
313
314 /*
315  * Take a uid in string form "#123" and return a faked up passwd struct.
316  */
317 struct passwd *
318 sudo_fakepwnam(const char *user, gid_t gid)
319 {
320     struct cache_item *item;
321     struct passwd *pw;
322     struct rbnode *node;
323     size_t len, namelen;
324     int i;
325
326     namelen = strlen(user);
327     len = sizeof(*item) + sizeof(*pw) + namelen + 1 /* pw_name */ +
328         sizeof("*") /* pw_passwd */ + sizeof("") /* pw_gecos */ +
329         sizeof("/") /* pw_dir */ + sizeof(_PATH_BSHELL);
330
331     for (i = 0; i < 2; i++) {
332         item = emalloc(len);
333         zero_bytes(item, sizeof(*item) + sizeof(*pw));
334         pw = (struct passwd *) ((char *)item + sizeof(*item));
335         pw->pw_uid = (uid_t) atoi(user + 1);
336         pw->pw_gid = gid;
337         pw->pw_name = (char *)pw + sizeof(struct passwd);
338         memcpy(pw->pw_name, user, namelen + 1);
339         pw->pw_passwd = pw->pw_name + namelen + 1;
340         memcpy(pw->pw_passwd, "*", 2);
341         pw->pw_gecos = pw->pw_passwd + 2;
342         pw->pw_gecos[0] = '\0';
343         pw->pw_dir = pw->pw_gecos + 1;
344         memcpy(pw->pw_dir, "/", 2);
345         pw->pw_shell = pw->pw_dir + 2;
346         memcpy(pw->pw_shell, _PATH_BSHELL, sizeof(_PATH_BSHELL));
347
348         item->refcnt = 1;
349         item->d.pw = pw;
350         if (i == 0) {
351             /* Store by uid, overwriting cached version. */
352             item->k.uid = pw->pw_uid;
353             if ((node = rbinsert(pwcache_byuid, item)) != NULL) {
354                 pw_delref_item(node->data);
355                 node->data = item;
356             }
357         } else {
358             /* Store by name, overwriting cached version. */
359             item->k.name = pw->pw_name;
360             if ((node = rbinsert(pwcache_byname, item)) != NULL) {
361                 pw_delref_item(node->data);
362                 node->data = item;
363             }
364         }
365     }
366     item->refcnt++;
367     return pw;
368 }
369
370 void
371 sudo_setpwent(void)
372 {
373     setpwent();
374     if (pwcache_byuid == NULL)
375         pwcache_byuid = rbcreate(cmp_pwuid);
376     if (pwcache_byname == NULL)
377         pwcache_byname = rbcreate(cmp_pwnam);
378 }
379
380 void
381 sudo_freepwcache(void)
382 {
383     if (pwcache_byuid != NULL) {
384         rbdestroy(pwcache_byuid, pw_delref_item);
385         pwcache_byuid = NULL;
386     }
387     if (pwcache_byname != NULL) {
388         rbdestroy(pwcache_byname, pw_delref_item);
389         pwcache_byname = NULL;
390     }
391 }
392
393 void
394 sudo_endpwent(void)
395 {
396     endpwent();
397     sudo_freepwcache();
398 }
399
400 /*
401  * Compare by gid.
402  */
403 static int
404 cmp_grgid(const void *v1, const void *v2)
405 {
406     const struct cache_item *ci1 = (const struct cache_item *) v1;
407     const struct cache_item *ci2 = (const struct cache_item *) v2;
408     return ci1->k.gid - ci2->k.gid;
409 }
410
411 /*
412  * Dynamically allocate space for a struct item plus the key and data
413  * elements.  If name is non-NULL it is used as the key, else the
414  * gid is the key.  Fills in datum from struct group.
415  */
416 static struct cache_item *
417 make_gritem(const struct group *gr, const char *name)
418 {
419     char *cp;
420     size_t nsize, psize, nmem, total, len;
421     struct cache_item *item;
422     struct group *newgr;
423
424     /* Allocate in one big chunk for easy freeing. */
425     nsize = psize = nmem = 0;
426     total = sizeof(struct cache_item) + sizeof(struct group);
427     FIELD_SIZE(gr, gr_name, nsize);
428     FIELD_SIZE(gr, gr_passwd, psize);
429     if (gr->gr_mem) {
430         for (nmem = 0; gr->gr_mem[nmem] != NULL; nmem++)
431             total += strlen(gr->gr_mem[nmem]) + 1;
432         nmem++;
433         total += sizeof(char *) * nmem;
434     }
435     if (name != NULL)
436         total += strlen(name) + 1;
437
438     item = emalloc(total);
439     cp = (char *) item + sizeof(struct cache_item);
440
441     /*
442      * Copy in group contents and make strings relative to space
443      * at the end of the buffer.  Note that gr_mem must come
444      * immediately after struct group to guarantee proper alignment.
445      */
446     newgr = (struct group *)cp;
447     memcpy(newgr, gr, sizeof(struct group));
448     cp += sizeof(struct group);
449     if (gr->gr_mem) {
450         newgr->gr_mem = (char **)cp;
451         cp += sizeof(char *) * nmem;
452         for (nmem = 0; gr->gr_mem[nmem] != NULL; nmem++) {
453             len = strlen(gr->gr_mem[nmem]) + 1;
454             memcpy(cp, gr->gr_mem[nmem], len);
455             newgr->gr_mem[nmem] = cp;
456             cp += len;
457         }
458         newgr->gr_mem[nmem] = NULL;
459     }
460     FIELD_COPY(gr, newgr, gr_passwd, psize);
461     FIELD_COPY(gr, newgr, gr_name, nsize);
462
463     /* Set key and datum. */
464     if (name != NULL) {
465         memcpy(cp, name, strlen(name) + 1);
466         item->k.name = cp;
467     } else {
468         item->k.gid = gr->gr_gid;
469     }
470     item->d.gr = newgr;
471     item->refcnt = 1;
472
473     return item;
474 }
475
476 #ifdef HAVE_UTMPX_H
477 # define GROUPNAME_LEN  (sizeof((struct utmpx *)0)->ut_user)
478 #else
479 # ifdef HAVE_STRUCT_UTMP_UT_USER
480 #  define GROUPNAME_LEN (sizeof((struct utmp *)0)->ut_user)
481 # else
482 #  define GROUPNAME_LEN (sizeof((struct utmp *)0)->ut_name)
483 # endif
484 #endif /* HAVE_UTMPX_H */
485
486 /*
487  * Dynamically allocate space for a struct item plus the key and data
488  * elements.  Fills in datum from the groups and gids arrays.
489  */
490 static struct cache_item *
491 make_grlist_item(const char *user, GETGROUPS_T *gids, int ngids)
492 {
493     char *cp;
494     size_t i, nsize, ngroups = 0, total, len;
495     struct cache_item *item;
496     struct group_list *grlist;
497     struct group *grp;
498
499     /* Allocate in one big chunk for easy freeing. */
500     nsize = strlen(user) + 1;
501     total = sizeof(struct cache_item) + sizeof(struct group_list) + nsize;
502     total += sizeof(char *) * ngids;
503     total += sizeof(gid_t *) * ngids;
504     total += GROUPNAME_LEN * ngids;
505
506     item = emalloc(total);
507     cp = (char *) item + sizeof(struct cache_item);
508
509     /*
510      * Copy in group list and make pointers relative to space
511      * at the end of the buffer.  Note that the gids array must come
512      * immediately after struct group to guarantee proper alignment.
513      */
514     grlist = (struct group_list *)cp;
515     zero_bytes(grlist, sizeof(struct group_list));
516     cp += sizeof(struct group_list);
517     grlist->gids = (gid_t *)cp;
518     cp += sizeof(gid_t) * ngids;
519     grlist->groups = (char **)cp;
520     cp += sizeof(char *) * ngids;
521
522     /* Set key and datum. */
523     memcpy(cp, user, nsize);
524     item->k.name = cp;
525     item->d.grlist = grlist;
526     item->refcnt = 1;
527     cp += nsize;
528
529     /*
530      * Store group IDs.
531      */
532     for (i = 0; i < ngids; i++)
533         grlist->gids[i] = gids[i];
534     grlist->ngids = ngids;
535
536 #ifdef HAVE_SETAUTHDB
537     aix_setauthdb((char *) user);
538 #endif
539     /*
540      * Resolve group names by ID and store at the end.
541      */
542     for (i = 0; i < ngids; i++) {
543         if ((grp = sudo_getgrgid(gids[i])) != NULL) {
544             len = strlen(grp->gr_name) + 1;
545             if (cp - (char *)grlist + len > total) {
546                 void *ptr = erealloc(grlist, total + len + GROUPNAME_LEN);
547                 total += len + GROUPNAME_LEN;
548                 cp = (char *)ptr + (cp - (char *)grlist);
549                 grlist = ptr;
550             }
551             memcpy(cp, grp->gr_name, len);
552             grlist->groups[ngroups++] = cp;
553             cp += len;
554             gr_delref(grp);
555         }
556     }
557     grlist->ngroups = ngroups;
558
559 #ifdef HAVE_SETAUTHDB
560     aix_restoreauthdb();
561 #endif
562
563     return item;
564 }
565
566 void
567 gr_addref(struct group *gr)
568 {
569     ptr_to_item(gr)->refcnt++;
570 }
571
572 static void
573 gr_delref_item(void *v)
574 {
575     struct cache_item *item = v;
576
577     if (--item->refcnt == 0)
578         efree(item);
579 }
580
581 void
582 gr_delref(struct group *gr)
583 {
584     gr_delref_item(ptr_to_item(gr));
585 }
586
587 /*
588  * Get a group entry by gid and allocate space for it.
589  */
590 struct group *
591 sudo_getgrgid(gid_t gid)
592 {
593     struct cache_item key, *item;
594     struct rbnode *node;
595
596     key.k.gid = gid;
597     if ((node = rbfind(grcache_bygid, &key)) != NULL) {
598         item = (struct cache_item *) node->data;
599         goto done;
600     }
601     /*
602      * Cache group db entry if it exists or a negative response if not.
603      */
604     if ((key.d.gr = getgrgid(gid)) != NULL) {
605         item = make_gritem(key.d.gr, NULL);
606         if (rbinsert(grcache_bygid, item) != NULL)
607             errorx(1, _("unable to cache gid %u (%s), already exists"),
608                 (unsigned int) gid, key.d.gr->gr_name);
609     } else {
610         item = emalloc(sizeof(*item));
611         item->refcnt = 1;
612         item->k.gid = gid;
613         item->d.gr = NULL;
614         if (rbinsert(grcache_bygid, item) != NULL)
615             errorx(1, _("unable to cache gid %u, already exists"),
616                 (unsigned int) gid);
617     }
618 done:
619     item->refcnt++;
620     return item->d.gr;
621 }
622
623 /*
624  * Get a group entry by name and allocate space for it.
625  */
626 struct group *
627 sudo_getgrnam(const char *name)
628 {
629     struct cache_item key, *item;
630     struct rbnode *node;
631     size_t len;
632
633     key.k.name = (char *) name;
634     if ((node = rbfind(grcache_byname, &key)) != NULL) {
635         item = (struct cache_item *) node->data;
636         goto done;
637     }
638     /*
639      * Cache group db entry if it exists or a negative response if not.
640      */
641     if ((key.d.gr = getgrnam(name)) != NULL) {
642         item = make_gritem(key.d.gr, name);
643         if (rbinsert(grcache_byname, item) != NULL)
644             errorx(1, _("unable to cache group %s, already exists"), name);
645     } else {
646         len = strlen(name) + 1;
647         item = emalloc(sizeof(*item) + len);
648         item->refcnt = 1;
649         item->k.name = (char *) item + sizeof(*item);
650         memcpy(item->k.name, name, len);
651         item->d.gr = NULL;
652         if (rbinsert(grcache_byname, item) != NULL)
653             errorx(1, _("unable to cache group %s, already exists"), name);
654     }
655 done:
656     item->refcnt++;
657     return item->d.gr;
658 }
659
660 /*
661  * Take a gid in string form "#123" and return a faked up group struct.
662  */
663 struct group *
664 sudo_fakegrnam(const char *group)
665 {
666     struct cache_item *item;
667     struct group *gr;
668     struct rbnode *node;
669     size_t len, namelen;
670     int i;
671
672     namelen = strlen(group);
673     len = sizeof(*item) + sizeof(*gr) + namelen + 1;
674
675     for (i = 0; i < 2; i++) {
676         item = emalloc(len);
677         zero_bytes(item, sizeof(*item) + sizeof(*gr));
678         gr = (struct group *) ((char *)item + sizeof(*item));
679         gr->gr_gid = (gid_t) atoi(group + 1);
680         gr->gr_name = (char *)gr + sizeof(struct group);
681         memcpy(gr->gr_name, group, namelen + 1);
682
683         item->refcnt = 1;
684         item->d.gr = gr;
685         if (i == 0) {
686             /* Store by gid, overwriting cached version. */
687             item->k.gid = gr->gr_gid;
688             if ((node = rbinsert(grcache_bygid, item)) != NULL) {
689                 gr_delref_item(node->data);
690                 node->data = item;
691             }
692         } else {
693             /* Store by name, overwriting cached version. */
694             item->k.name = gr->gr_name;
695             if ((node = rbinsert(grcache_byname, item)) != NULL) {
696                 gr_delref_item(node->data);
697                 node->data = item;
698             }
699         }
700     }
701     item->refcnt++;
702     return gr;
703 }
704
705 void
706 grlist_addref(struct group_list *grlist)
707 {
708     ptr_to_item(grlist)->refcnt++;
709 }
710
711 static void
712 grlist_delref_item(void *v)
713 {
714     struct cache_item *item = v;
715
716     if (--item->refcnt == 0)
717         efree(item);
718 }
719
720 void
721 grlist_delref(struct group_list *grlist)
722 {
723     grlist_delref_item(ptr_to_item(grlist));
724 }
725
726 void
727 sudo_setgrent(void)
728 {
729     setgrent();
730     if (grcache_bygid == NULL)
731         grcache_bygid = rbcreate(cmp_grgid);
732     if (grcache_byname == NULL)
733         grcache_byname = rbcreate(cmp_grnam);
734     if (grlist_cache == NULL)
735         grlist_cache = rbcreate(cmp_grnam);
736 }
737
738 void
739 sudo_freegrcache(void)
740 {
741     if (grcache_bygid != NULL) {
742         rbdestroy(grcache_bygid, gr_delref_item);
743         grcache_bygid = NULL;
744     }
745     if (grcache_byname != NULL) {
746         rbdestroy(grcache_byname, gr_delref_item);
747         grcache_byname = NULL;
748     }
749     if (grlist_cache != NULL) {
750         rbdestroy(grlist_cache, grlist_delref_item);
751         grlist_cache = NULL;
752     }
753 }
754
755 void
756 sudo_endgrent(void)
757 {
758     endgrent();
759     sudo_freegrcache();
760 }
761
762 struct group_list *
763 get_group_list(struct passwd *pw)
764 {
765     struct cache_item key, *item;
766     struct rbnode *node;
767     size_t len;
768     GETGROUPS_T *gids;
769     int ngids;
770
771     key.k.name = pw->pw_name;
772     if ((node = rbfind(grlist_cache, &key)) != NULL) {
773         item = (struct cache_item *) node->data;
774         goto done;
775     }
776     /*
777      * Cache group db entry if it exists or a negative response if not.
778      */
779 #if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX)
780     ngids = (int)sysconf(_SC_NGROUPS_MAX) * 2;
781     if (ngids < 0)
782 #endif
783         ngids = NGROUPS_MAX * 2;
784     gids = emalloc2(ngids, sizeof(GETGROUPS_T));
785     if (getgrouplist(pw->pw_name, pw->pw_gid, gids, &ngids) == -1) {
786         efree(gids);
787         gids = emalloc2(ngids, sizeof(GETGROUPS_T));
788         if (getgrouplist(pw->pw_name, pw->pw_gid, gids, &ngids) == -1) {
789             efree(gids);
790             return NULL;
791         }
792     }
793     if (ngids > 0) {
794         if ((item = make_grlist_item(pw->pw_name, gids, ngids)) == NULL)
795             errorx(1, "unable to parse group list for %s", pw->pw_name);
796         efree(gids);
797         if (rbinsert(grlist_cache, item) != NULL)
798             errorx(1, "unable to cache group list for %s, already exists",
799                 pw->pw_name);
800     } else {
801         /* Should not happen. */
802         len = strlen(pw->pw_name) + 1;
803         item = emalloc(sizeof(*item) + len);
804         item->refcnt = 1;
805         item->k.name = (char *) item + sizeof(*item);
806         memcpy(item->k.name, pw->pw_name, len);
807         item->d.grlist = NULL;
808         if (rbinsert(grlist_cache, item) != NULL)
809             errorx(1, "unable to cache group list for %s, already exists",
810                 pw->pw_name);
811     }
812 done:
813     item->refcnt++;
814     return item->d.grlist;
815 }
816
817 void
818 set_group_list(const char *user, GETGROUPS_T *gids, int ngids)
819 {
820     struct cache_item key, *item;
821     struct rbnode *node;
822
823     /*
824      * Cache group db entry if it doesn't already exist
825      */
826     key.k.name = (char *) user;
827     if ((node = rbfind(grlist_cache, &key)) == NULL) {
828         if ((item = make_grlist_item(user, gids, ngids)) == NULL)
829             errorx(1, "unable to parse group list for %s", user);
830         if (rbinsert(grlist_cache, item) != NULL)
831             errorx(1, "unable to cache group list for %s, already exists",
832                 user);
833     }
834 }
835
836 int
837 user_in_group(struct passwd *pw, const char *group)
838 {
839     struct group_list *grlist;
840     struct group *grp = NULL;
841     int i, matched = FALSE;
842
843     if ((grlist = get_group_list(pw)) != NULL) {
844         /*
845          * If it could be a sudo-style group ID check gids first.
846          */
847         if (group[0] == '#') {
848             gid_t gid = atoi(group + 1);
849             if (gid == pw->pw_gid) {
850                 matched = TRUE;
851                 goto done;
852             }
853             for (i = 0; i < grlist->ngids; i++) {
854                 if (gid == grlist->gids[i]) {
855                     matched = TRUE;
856                     goto done;
857                 }
858             }
859         }
860
861         /*
862          * Next check the supplementary group vector.
863          * It usually includes the password db group too.
864          */
865         for (i = 0; i < grlist->ngroups; i++) {
866             if (strcasecmp(group, grlist->groups[i]) == 0) {
867                 matched = TRUE;
868                 goto done;
869             }
870         }
871
872         /* Finally check against user's primary (passwd file) group. */
873         if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) {
874             if (strcasecmp(group, grp->gr_name) == 0) {
875                 matched = TRUE;
876                 goto done;
877             }
878         }
879 done:
880         if (grp != NULL)
881             gr_delref(grp);
882         grlist_delref(grlist);
883     }
884     return matched;
885 }