Imported Upstream version 1.8.3
[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 user, uid and gid and return a faked up passwd struct.
316  */
317 struct passwd *
318 sudo_fakepwnamid(const char *user, uid_t uid, 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;
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 /*
371  * Take a uid in string form "#123" and return a faked up passwd struct.
372  */
373 struct passwd *
374 sudo_fakepwnam(const char *user, gid_t gid)
375 {
376     uid_t uid;
377
378     uid = (uid_t) atoi(user + 1);
379     return sudo_fakepwnamid(user, uid, gid);
380 }
381
382 void
383 sudo_setpwent(void)
384 {
385     setpwent();
386     if (pwcache_byuid == NULL)
387         pwcache_byuid = rbcreate(cmp_pwuid);
388     if (pwcache_byname == NULL)
389         pwcache_byname = rbcreate(cmp_pwnam);
390 }
391
392 void
393 sudo_freepwcache(void)
394 {
395     if (pwcache_byuid != NULL) {
396         rbdestroy(pwcache_byuid, pw_delref_item);
397         pwcache_byuid = NULL;
398     }
399     if (pwcache_byname != NULL) {
400         rbdestroy(pwcache_byname, pw_delref_item);
401         pwcache_byname = NULL;
402     }
403 }
404
405 void
406 sudo_endpwent(void)
407 {
408     endpwent();
409     sudo_freepwcache();
410 }
411
412 /*
413  * Compare by gid.
414  */
415 static int
416 cmp_grgid(const void *v1, const void *v2)
417 {
418     const struct cache_item *ci1 = (const struct cache_item *) v1;
419     const struct cache_item *ci2 = (const struct cache_item *) v2;
420     return ci1->k.gid - ci2->k.gid;
421 }
422
423 /*
424  * Dynamically allocate space for a struct item plus the key and data
425  * elements.  If name is non-NULL it is used as the key, else the
426  * gid is the key.  Fills in datum from struct group.
427  */
428 static struct cache_item *
429 make_gritem(const struct group *gr, const char *name)
430 {
431     char *cp;
432     size_t nsize, psize, nmem, total, len;
433     struct cache_item *item;
434     struct group *newgr;
435
436     /* Allocate in one big chunk for easy freeing. */
437     nsize = psize = nmem = 0;
438     total = sizeof(struct cache_item) + sizeof(struct group);
439     FIELD_SIZE(gr, gr_name, nsize);
440     FIELD_SIZE(gr, gr_passwd, psize);
441     if (gr->gr_mem) {
442         for (nmem = 0; gr->gr_mem[nmem] != NULL; nmem++)
443             total += strlen(gr->gr_mem[nmem]) + 1;
444         nmem++;
445         total += sizeof(char *) * nmem;
446     }
447     if (name != NULL)
448         total += strlen(name) + 1;
449
450     item = emalloc(total);
451     cp = (char *) item + sizeof(struct cache_item);
452
453     /*
454      * Copy in group contents and make strings relative to space
455      * at the end of the buffer.  Note that gr_mem must come
456      * immediately after struct group to guarantee proper alignment.
457      */
458     newgr = (struct group *)cp;
459     memcpy(newgr, gr, sizeof(struct group));
460     cp += sizeof(struct group);
461     if (gr->gr_mem) {
462         newgr->gr_mem = (char **)cp;
463         cp += sizeof(char *) * nmem;
464         for (nmem = 0; gr->gr_mem[nmem] != NULL; nmem++) {
465             len = strlen(gr->gr_mem[nmem]) + 1;
466             memcpy(cp, gr->gr_mem[nmem], len);
467             newgr->gr_mem[nmem] = cp;
468             cp += len;
469         }
470         newgr->gr_mem[nmem] = NULL;
471     }
472     FIELD_COPY(gr, newgr, gr_passwd, psize);
473     FIELD_COPY(gr, newgr, gr_name, nsize);
474
475     /* Set key and datum. */
476     if (name != NULL) {
477         memcpy(cp, name, strlen(name) + 1);
478         item->k.name = cp;
479     } else {
480         item->k.gid = gr->gr_gid;
481     }
482     item->d.gr = newgr;
483     item->refcnt = 1;
484
485     return item;
486 }
487
488 #ifdef HAVE_UTMPX_H
489 # define GROUPNAME_LEN  (sizeof((struct utmpx *)0)->ut_user + 1)
490 #else
491 # ifdef HAVE_STRUCT_UTMP_UT_USER
492 #  define GROUPNAME_LEN (sizeof((struct utmp *)0)->ut_user + 1)
493 # else
494 #  define GROUPNAME_LEN (sizeof((struct utmp *)0)->ut_name + 1)
495 # endif
496 #endif /* HAVE_UTMPX_H */
497
498 /*
499  * Dynamically allocate space for a struct item plus the key and data
500  * elements.  Fills in datum from the groups and gids arrays.
501  */
502 static struct cache_item *
503 make_grlist_item(const char *user, GETGROUPS_T *gids, int ngids)
504 {
505     char *cp;
506     size_t i, nsize, ngroups, total, len;
507     struct cache_item *item;
508     struct group_list *grlist;
509     struct group *grp;
510
511 #ifdef HAVE_SETAUTHDB
512     aix_setauthdb((char *) user);
513 #endif
514
515     /* Allocate in one big chunk for easy freeing. */
516     nsize = strlen(user) + 1;
517     total = sizeof(struct cache_item) + sizeof(struct group_list) + nsize;
518     total += sizeof(char *) * ngids;
519     total += sizeof(gid_t *) * ngids;
520     total += GROUPNAME_LEN * ngids;
521
522 again:
523     item = emalloc(total);
524     cp = (char *) item + sizeof(struct cache_item);
525
526     /*
527      * Copy in group list and make pointers relative to space
528      * at the end of the buffer.  Note that the groups array must come
529      * immediately after struct group to guarantee proper alignment.
530      */
531     grlist = (struct group_list *)cp;
532     zero_bytes(grlist, sizeof(struct group_list));
533     cp += sizeof(struct group_list);
534     grlist->groups = (char **)cp;
535     cp += sizeof(char *) * ngids;
536     grlist->gids = (gid_t *)cp;
537     cp += sizeof(gid_t) * ngids;
538
539     /* Set key and datum. */
540     memcpy(cp, user, nsize);
541     item->k.name = cp;
542     item->d.grlist = grlist;
543     item->refcnt = 1;
544     cp += nsize;
545
546     /*
547      * Store group IDs.
548      */
549     for (i = 0; i < ngids; i++)
550         grlist->gids[i] = gids[i];
551     grlist->ngids = ngids;
552
553     /*
554      * Resolve and store group names by ID.
555      */
556     ngroups = 0;
557     for (i = 0; i < ngids; i++) {
558         if ((grp = sudo_getgrgid(gids[i])) != NULL) {
559             len = strlen(grp->gr_name) + 1;
560             if (cp - (char *)item + len > total) {
561                 total += len + GROUPNAME_LEN;
562                 efree(item);
563                 gr_delref(grp);
564                 goto again;
565             }
566             memcpy(cp, grp->gr_name, len);
567             grlist->groups[ngroups++] = cp;
568             cp += len;
569             gr_delref(grp);
570         }
571     }
572     grlist->ngroups = ngroups;
573
574 #ifdef HAVE_SETAUTHDB
575     aix_restoreauthdb();
576 #endif
577
578     return item;
579 }
580
581 void
582 gr_addref(struct group *gr)
583 {
584     ptr_to_item(gr)->refcnt++;
585 }
586
587 static void
588 gr_delref_item(void *v)
589 {
590     struct cache_item *item = v;
591
592     if (--item->refcnt == 0)
593         efree(item);
594 }
595
596 void
597 gr_delref(struct group *gr)
598 {
599     gr_delref_item(ptr_to_item(gr));
600 }
601
602 /*
603  * Get a group entry by gid and allocate space for it.
604  */
605 struct group *
606 sudo_getgrgid(gid_t gid)
607 {
608     struct cache_item key, *item;
609     struct rbnode *node;
610
611     key.k.gid = gid;
612     if ((node = rbfind(grcache_bygid, &key)) != NULL) {
613         item = (struct cache_item *) node->data;
614         goto done;
615     }
616     /*
617      * Cache group db entry if it exists or a negative response if not.
618      */
619     if ((key.d.gr = getgrgid(gid)) != NULL) {
620         item = make_gritem(key.d.gr, NULL);
621         if (rbinsert(grcache_bygid, item) != NULL)
622             errorx(1, _("unable to cache gid %u (%s), already exists"),
623                 (unsigned int) gid, key.d.gr->gr_name);
624     } else {
625         item = emalloc(sizeof(*item));
626         item->refcnt = 1;
627         item->k.gid = gid;
628         item->d.gr = NULL;
629         if (rbinsert(grcache_bygid, item) != NULL)
630             errorx(1, _("unable to cache gid %u, already exists"),
631                 (unsigned int) gid);
632     }
633 done:
634     item->refcnt++;
635     return item->d.gr;
636 }
637
638 /*
639  * Get a group entry by name and allocate space for it.
640  */
641 struct group *
642 sudo_getgrnam(const char *name)
643 {
644     struct cache_item key, *item;
645     struct rbnode *node;
646     size_t len;
647
648     key.k.name = (char *) name;
649     if ((node = rbfind(grcache_byname, &key)) != NULL) {
650         item = (struct cache_item *) node->data;
651         goto done;
652     }
653     /*
654      * Cache group db entry if it exists or a negative response if not.
655      */
656     if ((key.d.gr = getgrnam(name)) != NULL) {
657         item = make_gritem(key.d.gr, name);
658         if (rbinsert(grcache_byname, item) != NULL)
659             errorx(1, _("unable to cache group %s, already exists"), name);
660     } else {
661         len = strlen(name) + 1;
662         item = emalloc(sizeof(*item) + len);
663         item->refcnt = 1;
664         item->k.name = (char *) item + sizeof(*item);
665         memcpy(item->k.name, name, len);
666         item->d.gr = NULL;
667         if (rbinsert(grcache_byname, item) != NULL)
668             errorx(1, _("unable to cache group %s, already exists"), name);
669     }
670 done:
671     item->refcnt++;
672     return item->d.gr;
673 }
674
675 /*
676  * Take a gid in string form "#123" and return a faked up group struct.
677  */
678 struct group *
679 sudo_fakegrnam(const char *group)
680 {
681     struct cache_item *item;
682     struct group *gr;
683     struct rbnode *node;
684     size_t len, namelen;
685     int i;
686
687     namelen = strlen(group);
688     len = sizeof(*item) + sizeof(*gr) + namelen + 1;
689
690     for (i = 0; i < 2; i++) {
691         item = emalloc(len);
692         zero_bytes(item, sizeof(*item) + sizeof(*gr));
693         gr = (struct group *) ((char *)item + sizeof(*item));
694         gr->gr_gid = (gid_t) atoi(group + 1);
695         gr->gr_name = (char *)gr + sizeof(struct group);
696         memcpy(gr->gr_name, group, namelen + 1);
697
698         item->refcnt = 1;
699         item->d.gr = gr;
700         if (i == 0) {
701             /* Store by gid, overwriting cached version. */
702             item->k.gid = gr->gr_gid;
703             if ((node = rbinsert(grcache_bygid, item)) != NULL) {
704                 gr_delref_item(node->data);
705                 node->data = item;
706             }
707         } else {
708             /* Store by name, overwriting cached version. */
709             item->k.name = gr->gr_name;
710             if ((node = rbinsert(grcache_byname, item)) != NULL) {
711                 gr_delref_item(node->data);
712                 node->data = item;
713             }
714         }
715     }
716     item->refcnt++;
717     return gr;
718 }
719
720 void
721 grlist_addref(struct group_list *grlist)
722 {
723     ptr_to_item(grlist)->refcnt++;
724 }
725
726 static void
727 grlist_delref_item(void *v)
728 {
729     struct cache_item *item = v;
730
731     if (--item->refcnt == 0)
732         efree(item);
733 }
734
735 void
736 grlist_delref(struct group_list *grlist)
737 {
738     grlist_delref_item(ptr_to_item(grlist));
739 }
740
741 void
742 sudo_setgrent(void)
743 {
744     setgrent();
745     if (grcache_bygid == NULL)
746         grcache_bygid = rbcreate(cmp_grgid);
747     if (grcache_byname == NULL)
748         grcache_byname = rbcreate(cmp_grnam);
749     if (grlist_cache == NULL)
750         grlist_cache = rbcreate(cmp_grnam);
751 }
752
753 void
754 sudo_freegrcache(void)
755 {
756     if (grcache_bygid != NULL) {
757         rbdestroy(grcache_bygid, gr_delref_item);
758         grcache_bygid = NULL;
759     }
760     if (grcache_byname != NULL) {
761         rbdestroy(grcache_byname, gr_delref_item);
762         grcache_byname = NULL;
763     }
764     if (grlist_cache != NULL) {
765         rbdestroy(grlist_cache, grlist_delref_item);
766         grlist_cache = NULL;
767     }
768 }
769
770 void
771 sudo_endgrent(void)
772 {
773     endgrent();
774     sudo_freegrcache();
775 }
776
777 struct group_list *
778 get_group_list(struct passwd *pw)
779 {
780     struct cache_item key, *item;
781     struct rbnode *node;
782     size_t len;
783     GETGROUPS_T *gids;
784     int ngids;
785
786     key.k.name = pw->pw_name;
787     if ((node = rbfind(grlist_cache, &key)) != NULL) {
788         item = (struct cache_item *) node->data;
789         goto done;
790     }
791     /*
792      * Cache group db entry if it exists or a negative response if not.
793      */
794 #if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX)
795     ngids = (int)sysconf(_SC_NGROUPS_MAX) * 2;
796     if (ngids < 0)
797 #endif
798         ngids = NGROUPS_MAX * 2;
799     gids = emalloc2(ngids, sizeof(GETGROUPS_T));
800     if (getgrouplist(pw->pw_name, pw->pw_gid, gids, &ngids) == -1) {
801         efree(gids);
802         gids = emalloc2(ngids, sizeof(GETGROUPS_T));
803         if (getgrouplist(pw->pw_name, pw->pw_gid, gids, &ngids) == -1) {
804             efree(gids);
805             return NULL;
806         }
807     }
808     if (ngids > 0) {
809         if ((item = make_grlist_item(pw->pw_name, gids, ngids)) == NULL)
810             errorx(1, "unable to parse group list for %s", pw->pw_name);
811         efree(gids);
812         if (rbinsert(grlist_cache, item) != NULL)
813             errorx(1, "unable to cache group list for %s, already exists",
814                 pw->pw_name);
815     } else {
816         /* Should not happen. */
817         len = strlen(pw->pw_name) + 1;
818         item = emalloc(sizeof(*item) + len);
819         item->refcnt = 1;
820         item->k.name = (char *) item + sizeof(*item);
821         memcpy(item->k.name, pw->pw_name, len);
822         item->d.grlist = NULL;
823         if (rbinsert(grlist_cache, item) != NULL)
824             errorx(1, "unable to cache group list for %s, already exists",
825                 pw->pw_name);
826     }
827 done:
828     item->refcnt++;
829     return item->d.grlist;
830 }
831
832 void
833 set_group_list(const char *user, GETGROUPS_T *gids, int ngids)
834 {
835     struct cache_item key, *item;
836     struct rbnode *node;
837
838     /*
839      * Cache group db entry if it doesn't already exist
840      */
841     key.k.name = (char *) user;
842     if ((node = rbfind(grlist_cache, &key)) == NULL) {
843         if ((item = make_grlist_item(user, gids, ngids)) == NULL)
844             errorx(1, "unable to parse group list for %s", user);
845         if (rbinsert(grlist_cache, item) != NULL)
846             errorx(1, "unable to cache group list for %s, already exists",
847                 user);
848     }
849 }
850
851 int
852 user_in_group(struct passwd *pw, const char *group)
853 {
854     struct group_list *grlist;
855     struct group *grp = NULL;
856     int i, matched = FALSE;
857
858     if ((grlist = get_group_list(pw)) != NULL) {
859         /*
860          * If it could be a sudo-style group ID check gids first.
861          */
862         if (group[0] == '#') {
863             gid_t gid = atoi(group + 1);
864             if (gid == pw->pw_gid) {
865                 matched = TRUE;
866                 goto done;
867             }
868             for (i = 0; i < grlist->ngids; i++) {
869                 if (gid == grlist->gids[i]) {
870                     matched = TRUE;
871                     goto done;
872                 }
873             }
874         }
875
876         /*
877          * Next check the supplementary group vector.
878          * It usually includes the password db group too.
879          */
880         for (i = 0; i < grlist->ngroups; i++) {
881             if (strcasecmp(group, grlist->groups[i]) == 0) {
882                 matched = TRUE;
883                 goto done;
884             }
885         }
886
887         /* Finally check against user's primary (passwd file) group. */
888         if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) {
889             if (strcasecmp(group, grp->gr_name) == 0) {
890                 matched = TRUE;
891                 goto done;
892             }
893         }
894 done:
895         if (grp != NULL)
896             gr_delref(grp);
897         grlist_delref(grlist);
898     }
899     return matched;
900 }