1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 1996, 1998-2005, 2007-2018
5  *	Todd C. Miller <Todd.Miller@sudo.ws>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  *
19  * Sponsored in part by the Defense Advanced Research Projects
20  * Agency (DARPA) and Air Force Research Laboratory, Air Force
21  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
22  */
23 
24 /*
25  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
26  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
27  */
28 
29 #include <config.h>
30 
31 #include <stdarg.h>
32 #include <stddef.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #ifdef HAVE_STRINGS_H
37 # include <strings.h>		/* strcasecmp */
38 #endif
39 #ifdef HAVE_SETAUTHDB
40 # include <usersec.h>
41 #endif /* HAVE_SETAUTHDB */
42 #include <errno.h>
43 #include <pwd.h>
44 #include <grp.h>
45 
46 #include "sudoers.h"
47 #include "redblack.h"
48 #include "pwutil.h"
49 
50 /*
51  * The passwd and group caches.
52  */
53 static struct rbtree *pwcache_byuid, *pwcache_byname;
54 static struct rbtree *grcache_bygid, *grcache_byname;
55 static struct rbtree *gidlist_cache, *grlist_cache;
56 
57 static int  cmp_pwuid(const void *, const void *);
58 static int  cmp_pwnam(const void *, const void *);
59 static int  cmp_grgid(const void *, const void *);
60 
61 /*
62  * Default functions for building cache items.
63  */
64 static sudo_make_pwitem_t make_pwitem = sudo_make_pwitem;
65 static sudo_make_gritem_t make_gritem = sudo_make_gritem;
66 static sudo_make_gidlist_item_t make_gidlist_item = sudo_make_gidlist_item;
67 static sudo_make_grlist_item_t make_grlist_item = sudo_make_grlist_item;
68 
69 #define cmp_grnam	cmp_pwnam
70 
71 /*
72  * AIX has the concept of authentication registries (files, NIS, LDAP, etc).
73  * This allows you to have separate ID <-> name mappings based on which
74  * authentication registries the user was looked up in.
75  * We store the registry as part of the key and use it when matching.
76  */
77 #ifdef HAVE_SETAUTHDB
78 # define getauthregistry(u, r)	aix_getauthregistry((u), (r))
79 #else
80 # define getauthregistry(u, r)	((r)[0] = '\0')
81 #endif
82 
83 /*
84  * Change the default pwutil backend functions.
85  * The default functions query the password and group databases.
86  */
87 void
sudo_pwutil_set_backend(sudo_make_pwitem_t pwitem,sudo_make_gritem_t gritem,sudo_make_gidlist_item_t gidlist_item,sudo_make_grlist_item_t grlist_item)88 sudo_pwutil_set_backend(sudo_make_pwitem_t pwitem, sudo_make_gritem_t gritem,
89     sudo_make_gidlist_item_t gidlist_item, sudo_make_grlist_item_t grlist_item)
90 {
91     debug_decl(sudo_pwutil_set_backend, SUDOERS_DEBUG_NSS);
92 
93     make_pwitem = pwitem;
94     make_gritem = gritem;
95     make_gidlist_item = gidlist_item;
96     make_grlist_item = grlist_item;
97 
98     debug_return;
99 }
100 
101 /*
102  * Compare by user-ID.
103  * v1 is the key to find or data to insert, v2 is in-tree data.
104  */
105 static int
cmp_pwuid(const void * v1,const void * v2)106 cmp_pwuid(const void *v1, const void *v2)
107 {
108     const struct cache_item *ci1 = (const struct cache_item *) v1;
109     const struct cache_item *ci2 = (const struct cache_item *) v2;
110     if (ci1->k.uid == ci2->k.uid)
111 	return strcmp(ci1->registry, ci2->registry);
112     if (ci1->k.uid < ci2->k.uid)
113 	return -1;
114     return 1;
115 }
116 
117 /*
118  * Compare by user/group name.
119  * v1 is the key to find or data to insert, v2 is in-tree data.
120  */
121 static int
cmp_pwnam(const void * v1,const void * v2)122 cmp_pwnam(const void *v1, const void *v2)
123 {
124     const struct cache_item *ci1 = (const struct cache_item *) v1;
125     const struct cache_item *ci2 = (const struct cache_item *) v2;
126     int ret = strcmp(ci1->k.name, ci2->k.name);
127     if (ret == 0)
128 	ret = strcmp(ci1->registry, ci2->registry);
129     return ret;
130 }
131 
132 /*
133  * Compare by user name, taking into account the source type.
134  * Need to differentiate between group-IDs received from the front-end
135  * (via getgroups()) and groups IDs queried from the group database.
136  * v1 is the key to find or data to insert, v2 is in-tree data.
137  */
138 static int
cmp_gidlist(const void * v1,const void * v2)139 cmp_gidlist(const void *v1, const void *v2)
140 {
141     const struct cache_item *ci1 = (const struct cache_item *) v1;
142     const struct cache_item *ci2 = (const struct cache_item *) v2;
143     int ret = strcmp(ci1->k.name, ci2->k.name);
144     if (ret == 0) {
145 	if (ci1->type == ENTRY_TYPE_ANY || ci1->type == ci2->type)
146 	    return strcmp(ci1->registry, ci2->registry);
147 	if (ci1->type < ci2->type)
148 	    return -1;
149 	return 1;
150     }
151     return ret;
152 }
153 
154 void
sudo_pw_addref(struct passwd * pw)155 sudo_pw_addref(struct passwd *pw)
156 {
157     debug_decl(sudo_pw_addref, SUDOERS_DEBUG_NSS);
158     ptr_to_item(pw)->refcnt++;
159     debug_return;
160 }
161 
162 static void
sudo_pw_delref_item(void * v)163 sudo_pw_delref_item(void *v)
164 {
165     struct cache_item *item = v;
166     debug_decl(sudo_pw_delref_item, SUDOERS_DEBUG_NSS);
167 
168     if (--item->refcnt == 0)
169 	free(item);
170 
171     debug_return;
172 }
173 
174 void
sudo_pw_delref(struct passwd * pw)175 sudo_pw_delref(struct passwd *pw)
176 {
177     debug_decl(sudo_pw_delref, SUDOERS_DEBUG_NSS);
178     sudo_pw_delref_item(ptr_to_item(pw));
179     debug_return;
180 }
181 
182 /*
183  * Get a password entry by uid and allocate space for it.
184  */
185 struct passwd *
sudo_getpwuid(uid_t uid)186 sudo_getpwuid(uid_t uid)
187 {
188     struct cache_item key, *item;
189     struct rbnode *node;
190     debug_decl(sudo_getpwuid, SUDOERS_DEBUG_NSS);
191 
192     if (pwcache_byuid == NULL) {
193 	pwcache_byuid = rbcreate(cmp_pwuid);
194 	if (pwcache_byuid == NULL) {
195 	    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
196 	    debug_return_ptr(NULL);
197 	}
198     }
199 
200     key.k.uid = uid;
201     getauthregistry(IDtouser(uid), key.registry);
202     if ((node = rbfind(pwcache_byuid, &key)) != NULL) {
203 	item = node->data;
204 	goto done;
205     }
206     /*
207      * Cache passwd db entry if it exists or a negative response if not.
208      */
209 #ifdef HAVE_SETAUTHDB
210     aix_setauthdb(IDtouser(uid), key.registry);
211 #endif
212     item = make_pwitem(uid, NULL);
213 #ifdef HAVE_SETAUTHDB
214     aix_restoreauthdb();
215 #endif
216     if (item == NULL) {
217 	if (errno != ENOENT || (item = calloc(1, sizeof(*item))) == NULL) {
218 	    sudo_warn(U_("unable to cache uid %u"), (unsigned int) uid);
219 	    /* cppcheck-suppress memleak */
220 	    debug_return_ptr(NULL);
221 	}
222 	item->refcnt = 1;
223 	item->k.uid = uid;
224 	/* item->d.pw = NULL; */
225     }
226     strlcpy(item->registry, key.registry, sizeof(item->registry));
227     switch (rbinsert(pwcache_byuid, item, NULL)) {
228     case 1:
229 	/* should not happen */
230 	sudo_warnx(U_("unable to cache uid %u, already exists"),
231 	    (unsigned int) uid);
232 	item->refcnt = 0;
233 	break;
234     case -1:
235 	/* can't cache item, just return it */
236 	sudo_warn(U_("unable to cache uid %u"), (unsigned int) uid);
237 	item->refcnt = 0;
238 	break;
239     }
240 done:
241     if (item->refcnt != 0) {
242 	sudo_debug_printf(SUDO_DEBUG_DEBUG,
243 	    "%s: uid %u [%s] -> user %s [%s] (%s)", __func__,
244 	    (unsigned int)uid, key.registry,
245 	    item->d.pw ? item->d.pw->pw_name : "unknown",
246 	    item->registry, node ? "cache hit" : "cached");
247     }
248     if (item->d.pw != NULL)
249 	item->refcnt++;
250     debug_return_ptr(item->d.pw);
251 }
252 
253 /*
254  * Get a password entry by name and allocate space for it.
255  */
256 struct passwd *
sudo_getpwnam(const char * name)257 sudo_getpwnam(const char *name)
258 {
259     struct cache_item key, *item;
260     struct rbnode *node;
261     debug_decl(sudo_getpwnam, SUDOERS_DEBUG_NSS);
262 
263     if (pwcache_byname == NULL) {
264 	pwcache_byname = rbcreate(cmp_pwnam);
265 	if (pwcache_byname == NULL) {
266 	    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
267 	    debug_return_ptr(NULL);
268 	}
269     }
270 
271     key.k.name = (char *) name;
272     getauthregistry((char *) name, key.registry);
273     if ((node = rbfind(pwcache_byname, &key)) != NULL) {
274 	item = node->data;
275 	goto done;
276     }
277     /*
278      * Cache passwd db entry if it exists or a negative response if not.
279      */
280 #ifdef HAVE_SETAUTHDB
281     aix_setauthdb((char *) name, key.registry);
282 #endif
283     item = make_pwitem((uid_t)-1, name);
284 #ifdef HAVE_SETAUTHDB
285     aix_restoreauthdb();
286 #endif
287     if (item == NULL) {
288 	const size_t len = strlen(name) + 1;
289 	if (errno != ENOENT || (item = calloc(1, sizeof(*item) + len)) == NULL) {
290 	    sudo_warn(U_("unable to cache user %s"), name);
291 	    /* cppcheck-suppress memleak */
292 	    debug_return_ptr(NULL);
293 	}
294 	item->refcnt = 1;
295 	item->k.name = (char *) item + sizeof(*item);
296 	memcpy(item->k.name, name, len);
297 	/* item->d.pw = NULL; */
298     }
299     strlcpy(item->registry, key.registry, sizeof(item->registry));
300     switch (rbinsert(pwcache_byname, item, NULL)) {
301     case 1:
302 	/* should not happen */
303 	sudo_warnx(U_("unable to cache user %s, already exists"), name);
304 	item->refcnt = 0;
305 	break;
306     case -1:
307 	/* can't cache item, just return it */
308 	sudo_warn(U_("unable to cache user %s"), name);
309 	item->refcnt = 0;
310 	break;
311     }
312 done:
313     if (item->refcnt != 0) {
314 	sudo_debug_printf(SUDO_DEBUG_DEBUG,
315 	    "%s: user %s [%s] -> uid %d [%s] (%s)", __func__, name,
316 	    key.registry, item->d.pw ? (int)item->d.pw->pw_uid : -1,
317 	    item->registry, node ? "cache hit" : "cached");
318     }
319     if (item->d.pw != NULL)
320 	item->refcnt++;
321     debug_return_ptr(item->d.pw);
322 }
323 
324 /*
325  * Take a user, uid, gid, home and shell and return a faked up passwd struct.
326  * If home or shell are NULL default values will be used.
327  */
328 struct passwd *
sudo_mkpwent(const char * user,uid_t uid,gid_t gid,const char * home,const char * shell)329 sudo_mkpwent(const char *user, uid_t uid, gid_t gid, const char *home,
330     const char *shell)
331 {
332     struct cache_item_pw *pwitem;
333     struct cache_item *item;
334     struct passwd *pw;
335     size_t len, name_len, home_len, shell_len;
336     int i;
337     debug_decl(sudo_mkpwent, SUDOERS_DEBUG_NSS);
338 
339     if (pwcache_byuid == NULL)
340 	pwcache_byuid = rbcreate(cmp_pwuid);
341     if (pwcache_byname == NULL)
342 	pwcache_byname = rbcreate(cmp_pwnam);
343     if (pwcache_byuid == NULL || pwcache_byname == NULL) {
344 	sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
345 	debug_return_ptr(NULL);
346     }
347 
348     /* Optional arguments. */
349     if (home == NULL)
350 	home = "/";
351     if (shell == NULL)
352 	shell = _PATH_BSHELL;
353 
354     sudo_debug_printf(SUDO_DEBUG_DEBUG,
355 	"%s: creating and caching passwd struct for %s:%u:%u:%s:%s", __func__,
356 	user, (unsigned int)uid, (unsigned int)gid, home, shell);
357 
358     name_len = strlen(user);
359     home_len = strlen(home);
360     shell_len = strlen(shell);
361     len = sizeof(*pwitem) + name_len + 1 /* pw_name */ +
362 	sizeof("*") /* pw_passwd */ + sizeof("") /* pw_gecos */ +
363 	home_len + 1 /* pw_dir */ + shell_len + 1 /* pw_shell */;
364 
365     for (i = 0; i < 2; i++) {
366 	struct rbtree *pwcache;
367 	struct rbnode *node;
368 
369 	pwitem = calloc(1, len);
370 	if (pwitem == NULL) {
371 	    sudo_warn(U_("unable to cache user %s"), user);
372 	    debug_return_ptr(NULL);
373 	}
374 	pw = &pwitem->pw;
375 	pw->pw_uid = uid;
376 	pw->pw_gid = gid;
377 	pw->pw_name = (char *)(pwitem + 1);
378 	memcpy(pw->pw_name, user, name_len + 1);
379 	pw->pw_passwd = pw->pw_name + name_len + 1;
380 	memcpy(pw->pw_passwd, "*", 2);
381 	pw->pw_gecos = pw->pw_passwd + 2;
382 	pw->pw_gecos[0] = '\0';
383 	pw->pw_dir = pw->pw_gecos + 1;
384 	memcpy(pw->pw_dir, home, home_len + 1);
385 	pw->pw_shell = pw->pw_dir + home_len + 1;
386 	memcpy(pw->pw_shell, shell, shell_len + 1);
387 
388 	item = &pwitem->cache;
389 	item->refcnt = 1;
390 	item->d.pw = pw;
391 	if (i == 0) {
392 	    /* Store by uid. */
393 	    item->k.uid = pw->pw_uid;
394 	    pwcache = pwcache_byuid;
395 	} else {
396 	    /* Store by name. */
397 	    item->k.name = pw->pw_name;
398 	    pwcache = pwcache_byname;
399 	}
400 	getauthregistry(NULL, item->registry);
401 	switch (rbinsert(pwcache, item, &node)) {
402 	case 1:
403 	    /* Already exists. */
404 	    item = node->data;
405 	    if (item->d.pw == NULL) {
406 		/* Negative cache entry, replace with ours. */
407 		sudo_pw_delref_item(item);
408 		item = node->data = &pwitem->cache;
409 	    } else {
410 		/* Good entry, discard our fake one. */
411 		free(pwitem);
412 	    }
413 	    break;
414 	case -1:
415 	    /* can't cache item, just return it */
416 	    sudo_warn(U_("unable to cache user %s"), user);
417 	    item->refcnt = 0;
418 	    break;
419 	}
420     }
421     item->refcnt++;
422     debug_return_ptr(item->d.pw);
423 }
424 
425 /*
426  * Take a uid in string form "#123" and return a faked up passwd struct.
427  */
428 struct passwd *
sudo_fakepwnam(const char * user,gid_t gid)429 sudo_fakepwnam(const char *user, gid_t gid)
430 {
431     const char *errstr;
432     uid_t uid;
433     debug_decl(sudo_fakepwnam, SUDOERS_DEBUG_NSS);
434 
435     uid = (uid_t) sudo_strtoid(user + 1, &errstr);
436     if (errstr != NULL) {
437 	sudo_debug_printf(SUDO_DEBUG_DIAG|SUDO_DEBUG_LINENO,
438 	    "uid %s %s", user, errstr);
439 	debug_return_ptr(NULL);
440     }
441     debug_return_ptr(sudo_mkpwent(user, uid, gid, NULL, NULL));
442 }
443 
444 void
sudo_freepwcache(void)445 sudo_freepwcache(void)
446 {
447     debug_decl(sudo_freepwcache, SUDOERS_DEBUG_NSS);
448 
449     if (pwcache_byuid != NULL) {
450 	rbdestroy(pwcache_byuid, sudo_pw_delref_item);
451 	pwcache_byuid = NULL;
452     }
453     if (pwcache_byname != NULL) {
454 	rbdestroy(pwcache_byname, sudo_pw_delref_item);
455 	pwcache_byname = NULL;
456     }
457 
458     debug_return;
459 }
460 
461 /*
462  * Compare by group-ID.
463  * v1 is the key to find or data to insert, v2 is in-tree data.
464  */
465 static int
cmp_grgid(const void * v1,const void * v2)466 cmp_grgid(const void *v1, const void *v2)
467 {
468     const struct cache_item *ci1 = (const struct cache_item *) v1;
469     const struct cache_item *ci2 = (const struct cache_item *) v2;
470     if (ci1->k.gid == ci2->k.gid)
471 	return strcmp(ci1->registry, ci2->registry);
472     if (ci1->k.gid < ci2->k.gid)
473 	return -1;
474     return 1;
475 }
476 
477 void
sudo_gr_addref(struct group * gr)478 sudo_gr_addref(struct group *gr)
479 {
480     debug_decl(sudo_gr_addref, SUDOERS_DEBUG_NSS);
481     ptr_to_item(gr)->refcnt++;
482     debug_return;
483 }
484 
485 static void
sudo_gr_delref_item(void * v)486 sudo_gr_delref_item(void *v)
487 {
488     struct cache_item *item = v;
489     debug_decl(sudo_gr_delref_item, SUDOERS_DEBUG_NSS);
490 
491     if (--item->refcnt == 0)
492 	free(item);
493 
494     debug_return;
495 }
496 
497 void
sudo_gr_delref(struct group * gr)498 sudo_gr_delref(struct group *gr)
499 {
500     debug_decl(sudo_gr_delref, SUDOERS_DEBUG_NSS);
501     sudo_gr_delref_item(ptr_to_item(gr));
502     debug_return;
503 }
504 
505 /*
506  * Get a group entry by gid and allocate space for it.
507  */
508 struct group *
sudo_getgrgid(gid_t gid)509 sudo_getgrgid(gid_t gid)
510 {
511     struct cache_item key, *item;
512     struct rbnode *node;
513     debug_decl(sudo_getgrgid, SUDOERS_DEBUG_NSS);
514 
515     if (grcache_bygid == NULL) {
516 	grcache_bygid = rbcreate(cmp_grgid);
517 	if (grcache_bygid == NULL) {
518 	    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
519 	    debug_return_ptr(NULL);
520 	}
521     }
522 
523     key.k.gid = gid;
524     getauthregistry(NULL, key.registry);
525     if ((node = rbfind(grcache_bygid, &key)) != NULL) {
526 	item = node->data;
527 	goto done;
528     }
529     /*
530      * Cache group db entry if it exists or a negative response if not.
531      */
532     item = make_gritem(gid, NULL);
533     if (item == NULL) {
534 	if (errno != ENOENT || (item = calloc(1, sizeof(*item))) == NULL) {
535 	    sudo_warn(U_("unable to cache gid %u"), (unsigned int) gid);
536 	    /* cppcheck-suppress memleak */
537 	    debug_return_ptr(NULL);
538 	}
539 	item->refcnt = 1;
540 	item->k.gid = gid;
541 	/* item->d.gr = NULL; */
542     }
543     strlcpy(item->registry, key.registry, sizeof(item->registry));
544     switch (rbinsert(grcache_bygid, item, NULL)) {
545     case 1:
546 	/* should not happen */
547 	sudo_warnx(U_("unable to cache gid %u, already exists"),
548 	    (unsigned int) gid);
549 	item->refcnt = 0;
550 	break;
551     case -1:
552 	/* can't cache item, just return it */
553 	sudo_warn(U_("unable to cache gid %u"), (unsigned int) gid);
554 	item->refcnt = 0;
555 	break;
556     }
557 done:
558     if (item->refcnt != 0) {
559 	sudo_debug_printf(SUDO_DEBUG_DEBUG,
560 	    "%s: gid %u [%s] -> group %s [%s] (%s)", __func__,
561 	    (unsigned int)gid, key.registry,
562 	    item->d.gr ? item->d.gr->gr_name : "unknown",
563 	    item->registry, node ? "cache hit" : "cached");
564     }
565     if (item->d.gr != NULL)
566 	item->refcnt++;
567     debug_return_ptr(item->d.gr);
568 }
569 
570 /*
571  * Get a group entry by name and allocate space for it.
572  */
573 struct group *
sudo_getgrnam(const char * name)574 sudo_getgrnam(const char *name)
575 {
576     struct cache_item key, *item;
577     struct rbnode *node;
578     debug_decl(sudo_getgrnam, SUDOERS_DEBUG_NSS);
579 
580     if (grcache_byname == NULL) {
581 	grcache_byname = rbcreate(cmp_grnam);
582 	if (grcache_byname == NULL) {
583 	    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
584 	    debug_return_ptr(NULL);
585 	}
586     }
587 
588     key.k.name = (char *) name;
589     getauthregistry(NULL, key.registry);
590     if ((node = rbfind(grcache_byname, &key)) != NULL) {
591 	item = node->data;
592 	goto done;
593     }
594     /*
595      * Cache group db entry if it exists or a negative response if not.
596      */
597     item = make_gritem((gid_t)-1, name);
598     if (item == NULL) {
599 	const size_t len = strlen(name) + 1;
600 	if (errno != ENOENT || (item = calloc(1, sizeof(*item) + len)) == NULL) {
601 	    sudo_warn(U_("unable to cache group %s"), name);
602 	    /* cppcheck-suppress memleak */
603 	    debug_return_ptr(NULL);
604 	}
605 	item->refcnt = 1;
606 	item->k.name = (char *) item + sizeof(*item);
607 	memcpy(item->k.name, name, len);
608 	/* item->d.gr = NULL; */
609     }
610     strlcpy(item->registry, key.registry, sizeof(item->registry));
611     switch (rbinsert(grcache_byname, item, NULL)) {
612     case 1:
613 	/* should not happen */
614 	sudo_warnx(U_("unable to cache group %s, already exists"), name);
615 	item->refcnt = 0;
616 	break;
617     case -1:
618 	/* can't cache item, just return it */
619 	sudo_warn(U_("unable to cache group %s"), name);
620 	item->refcnt = 0;
621 	break;
622     }
623 done:
624     if (item->refcnt != 0) {
625 	sudo_debug_printf(SUDO_DEBUG_DEBUG,
626 	    "%s: group %s [%s] -> gid %d [%s] (%s)", __func__, name,
627 	    key.registry, item->d.gr ? (int)item->d.gr->gr_gid : -1,
628 	    item->registry, node ? "cache hit" : "cached");
629     }
630     if (item->d.gr != NULL)
631 	item->refcnt++;
632     debug_return_ptr(item->d.gr);
633 }
634 
635 /*
636  * Take a group name, ID, members and return a faked up group struct.
637  */
638 struct group *
sudo_mkgrent(const char * group,gid_t gid,...)639 sudo_mkgrent(const char *group, gid_t gid, ...)
640 {
641     struct cache_item_gr *gritem;
642     struct cache_item *item;
643     struct group *gr;
644     size_t nmem, nsize, total;
645     char *cp, *mem;
646     va_list ap;
647     int i;
648     debug_decl(sudo_mkgrent, SUDOERS_DEBUG_NSS);
649 
650     if (grcache_bygid == NULL)
651 	grcache_bygid = rbcreate(cmp_grgid);
652     if (grcache_byname == NULL)
653 	grcache_byname = rbcreate(cmp_grnam);
654     if (grcache_bygid == NULL || grcache_byname == NULL) {
655 	sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
656 	debug_return_ptr(NULL);
657     }
658 
659     /* Allocate in one big chunk for easy freeing. */
660     nsize = strlen(group) + 1;
661     total = sizeof(*gritem) + nsize;
662     va_start(ap, gid);
663     for (nmem = 1; (mem = va_arg(ap, char *)) != NULL; nmem++) {
664 	total += strlen(mem) + 1;
665     }
666     va_end(ap);
667     total += sizeof(char *) * nmem;
668 
669     for (i = 0; i < 2; i++) {
670 	struct rbtree *grcache;
671 	struct rbnode *node;
672 
673 	/*
674 	 * Fill in group contents and make strings relative to space
675 	 * at the end of the buffer.  Note that gr_mem must come
676 	 * immediately after struct group to guarantee proper alignment.
677 	 */
678 	gritem = calloc(1, total);
679 	if (gritem == NULL) {
680 	    sudo_warn(U_("unable to cache group %s"), group);
681 	    debug_return_ptr(NULL);
682 	}
683 	gr = &gritem->gr;
684 	gr->gr_gid = gid;
685 	gr->gr_passwd = "*";
686 	cp = (char *)(gritem + 1);
687 	gr->gr_mem = (char **)cp;
688 	cp += sizeof(char *) * nmem;
689 	va_start(ap, gid);
690 	for (nmem = 0; (mem = va_arg(ap, char *)) != NULL; nmem++) {
691 	    size_t len = strlen(mem) + 1;
692 	    memcpy(cp, mem, len);
693 	    gr->gr_mem[nmem] = cp;
694 	    cp += len;
695 	}
696 	va_end(ap);
697 	gr->gr_mem[nmem] = NULL;
698 	gr->gr_name = cp;
699 	memcpy(gr->gr_name, group, nsize);
700 
701 	item = &gritem->cache;
702 	item->refcnt = 1;
703 	item->d.gr = gr;
704 	if (i == 0) {
705 	    /* Store by gid if it doesn't already exist. */
706 	    item->k.gid = gr->gr_gid;
707 	    grcache = grcache_bygid;
708 	} else {
709 	    /* Store by name, overwriting cached version. */
710 	    gritem->cache.k.name = gr->gr_name;
711 	    grcache = grcache_byname;
712 	}
713 	getauthregistry(NULL, item->registry);
714 	switch (rbinsert(grcache, item, &node)) {
715 	case 1:
716 	    /* Already exists. */
717 	    item = node->data;
718 	    if (item->d.gr == NULL) {
719 		/* Negative cache entry, replace with ours. */
720 		sudo_gr_delref_item(item);
721 		item = node->data = &gritem->cache;
722 	    } else {
723 		/* Good entry, discard our fake one. */
724 		free(gritem);
725 	    }
726 	    break;
727 	case -1:
728 	    /* can't cache item, just return it */
729 	    sudo_warn(U_("unable to cache group %s"), group);
730 	    item->refcnt = 0;
731 	    break;
732 	}
733     }
734     if (item->d.gr != NULL)
735 	item->refcnt++;
736     debug_return_ptr(item->d.gr);
737 }
738 
739 /*
740  * Take a gid in string form "#123" and return a faked up group struct.
741  */
742 struct group *
sudo_fakegrnam(const char * group)743 sudo_fakegrnam(const char *group)
744 {
745     const char *errstr;
746     gid_t gid;
747     debug_decl(sudo_fakegrnam, SUDOERS_DEBUG_NSS);
748 
749     gid = (gid_t) sudo_strtoid(group + 1, &errstr);
750     if (errstr != NULL) {
751 	sudo_debug_printf(SUDO_DEBUG_DIAG|SUDO_DEBUG_LINENO,
752 	    "gid %s %s", group, errstr);
753 	debug_return_ptr(NULL);
754     }
755 
756     debug_return_ptr(sudo_mkgrent(group, gid, (char *)NULL));
757 }
758 
759 void
sudo_gidlist_addref(struct gid_list * gidlist)760 sudo_gidlist_addref(struct gid_list *gidlist)
761 {
762     debug_decl(sudo_gidlist_addref, SUDOERS_DEBUG_NSS);
763     ptr_to_item(gidlist)->refcnt++;
764     debug_return;
765 }
766 
767 static void
sudo_gidlist_delref_item(void * v)768 sudo_gidlist_delref_item(void *v)
769 {
770     struct cache_item *item = v;
771     debug_decl(sudo_gidlist_delref_item, SUDOERS_DEBUG_NSS);
772 
773     if (--item->refcnt == 0)
774 	free(item);
775 
776     debug_return;
777 }
778 
779 void
sudo_gidlist_delref(struct gid_list * gidlist)780 sudo_gidlist_delref(struct gid_list *gidlist)
781 {
782     debug_decl(sudo_gidlist_delref, SUDOERS_DEBUG_NSS);
783     sudo_gidlist_delref_item(ptr_to_item(gidlist));
784     debug_return;
785 }
786 
787 void
sudo_grlist_addref(struct group_list * grlist)788 sudo_grlist_addref(struct group_list *grlist)
789 {
790     debug_decl(sudo_grlist_addref, SUDOERS_DEBUG_NSS);
791     ptr_to_item(grlist)->refcnt++;
792     debug_return;
793 }
794 
795 static void
sudo_grlist_delref_item(void * v)796 sudo_grlist_delref_item(void *v)
797 {
798     struct cache_item *item = v;
799     debug_decl(sudo_grlist_delref_item, SUDOERS_DEBUG_NSS);
800 
801     if (--item->refcnt == 0)
802 	free(item);
803 
804     debug_return;
805 }
806 
807 void
sudo_grlist_delref(struct group_list * grlist)808 sudo_grlist_delref(struct group_list *grlist)
809 {
810     debug_decl(sudo_grlist_delref, SUDOERS_DEBUG_NSS);
811     sudo_grlist_delref_item(ptr_to_item(grlist));
812     debug_return;
813 }
814 
815 void
sudo_freegrcache(void)816 sudo_freegrcache(void)
817 {
818     debug_decl(sudo_freegrcache, SUDOERS_DEBUG_NSS);
819 
820     if (grcache_bygid != NULL) {
821 	rbdestroy(grcache_bygid, sudo_gr_delref_item);
822 	grcache_bygid = NULL;
823     }
824     if (grcache_byname != NULL) {
825 	rbdestroy(grcache_byname, sudo_gr_delref_item);
826 	grcache_byname = NULL;
827     }
828     if (grlist_cache != NULL) {
829 	rbdestroy(grlist_cache, sudo_grlist_delref_item);
830 	grlist_cache = NULL;
831     }
832     if (gidlist_cache != NULL) {
833 	rbdestroy(gidlist_cache, sudo_gidlist_delref_item);
834 	gidlist_cache = NULL;
835     }
836 
837     debug_return;
838 }
839 
840 struct group_list *
sudo_get_grlist(const struct passwd * pw)841 sudo_get_grlist(const struct passwd *pw)
842 {
843     struct cache_item key, *item;
844     struct rbnode *node;
845     debug_decl(sudo_get_grlist, SUDOERS_DEBUG_NSS);
846 
847     sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: looking up group names for %s",
848 	__func__, pw->pw_name);
849 
850     if (grlist_cache == NULL) {
851 	grlist_cache = rbcreate(cmp_pwnam);
852 	if (grlist_cache == NULL) {
853 	    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
854 	    debug_return_ptr(NULL);
855 	}
856     }
857 
858     key.k.name = pw->pw_name;
859     getauthregistry(pw->pw_name, key.registry);
860     if ((node = rbfind(grlist_cache, &key)) != NULL) {
861 	item = node->data;
862 	goto done;
863     }
864     /*
865      * Cache group db entry if it exists or a negative response if not.
866      */
867     item = make_grlist_item(pw, NULL);
868     if (item == NULL) {
869 	/* Out of memory? */
870 	debug_return_ptr(NULL);
871     }
872     strlcpy(item->registry, key.registry, sizeof(item->registry));
873     switch (rbinsert(grlist_cache, item, NULL)) {
874     case 1:
875 	/* should not happen */
876 	sudo_warnx(U_("unable to cache group list for %s, already exists"),
877 	    pw->pw_name);
878 	item->refcnt = 0;
879 	break;
880     case -1:
881 	/* can't cache item, just return it */
882 	sudo_warn(U_("unable to cache group list for %s"), pw->pw_name);
883 	item->refcnt = 0;
884 	break;
885     }
886     if (item->d.grlist != NULL) {
887 	int i;
888 	for (i = 0; i < item->d.grlist->ngroups; i++) {
889 	    sudo_debug_printf(SUDO_DEBUG_DEBUG,
890 		"%s: user %s is a member of group %s", __func__,
891 		pw->pw_name, item->d.grlist->groups[i]);
892 	}
893     }
894 done:
895     if (item->d.grlist != NULL)
896 	item->refcnt++;
897     debug_return_ptr(item->d.grlist);
898 }
899 
900 int
sudo_set_grlist(struct passwd * pw,char * const * groups)901 sudo_set_grlist(struct passwd *pw, char * const *groups)
902 {
903     struct cache_item key, *item;
904     debug_decl(sudo_set_grlist, SUDOERS_DEBUG_NSS);
905 
906     if (grlist_cache == NULL) {
907 	grlist_cache = rbcreate(cmp_pwnam);
908 	if (grlist_cache == NULL) {
909 	    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
910 	    debug_return_int(-1);
911 	}
912     }
913 
914     /*
915      * Cache group db entry if it doesn't already exist
916      */
917     key.k.name = pw->pw_name;
918     getauthregistry(NULL, key.registry);
919     if (rbfind(grlist_cache, &key) == NULL) {
920 	if ((item = make_grlist_item(pw, groups)) == NULL) {
921 	    sudo_warnx(U_("unable to parse groups for %s"), pw->pw_name);
922 	    debug_return_int(-1);
923 	}
924 	strlcpy(item->registry, key.registry, sizeof(item->registry));
925 	switch (rbinsert(grlist_cache, item, NULL)) {
926 	case 1:
927 	    sudo_warnx(U_("unable to cache group list for %s, already exists"),
928 		pw->pw_name);
929 	    sudo_grlist_delref_item(item);
930 	    break;
931 	case -1:
932 	    sudo_warn(U_("unable to cache group list for %s"), pw->pw_name);
933 	    sudo_grlist_delref_item(item);
934 	    debug_return_int(-1);
935 	}
936     }
937     debug_return_int(0);
938 }
939 
940 struct gid_list *
sudo_get_gidlist(const struct passwd * pw,unsigned int type)941 sudo_get_gidlist(const struct passwd *pw, unsigned int type)
942 {
943     struct cache_item key, *item;
944     struct rbnode *node;
945     debug_decl(sudo_get_gidlist, SUDOERS_DEBUG_NSS);
946 
947     sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: looking up group-IDs for %s",
948 	__func__, pw->pw_name);
949 
950     if (gidlist_cache == NULL) {
951 	gidlist_cache = rbcreate(cmp_gidlist);
952 	if (gidlist_cache == NULL) {
953 	    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
954 	    debug_return_ptr(NULL);
955 	}
956     }
957 
958     key.k.name = pw->pw_name;
959     key.type = type;
960     getauthregistry(pw->pw_name, key.registry);
961     if ((node = rbfind(gidlist_cache, &key)) != NULL) {
962 	item = node->data;
963 	goto done;
964     }
965     /*
966      * Cache group db entry if it exists or a negative response if not.
967      */
968     item = make_gidlist_item(pw, NULL, type);
969     if (item == NULL) {
970 	/* Out of memory? */
971 	debug_return_ptr(NULL);
972     }
973     strlcpy(item->registry, key.registry, sizeof(item->registry));
974     switch (rbinsert(gidlist_cache, item, NULL)) {
975     case 1:
976 	/* should not happen */
977 	sudo_warnx(U_("unable to cache group list for %s, already exists"),
978 	    pw->pw_name);
979 	item->refcnt = 0;
980 	break;
981     case -1:
982 	/* can't cache item, just return it */
983 	sudo_warn(U_("unable to cache group list for %s"), pw->pw_name);
984 	item->refcnt = 0;
985 	break;
986     }
987     if (item->d.gidlist != NULL) {
988 	int i;
989 	for (i = 0; i < item->d.gidlist->ngids; i++) {
990 	    sudo_debug_printf(SUDO_DEBUG_DEBUG,
991 		"%s: user %s has supplementary gid %u", __func__,
992 		pw->pw_name, (unsigned int)item->d.gidlist->gids[i]);
993 	}
994     }
995 done:
996     if (item->d.gidlist != NULL)
997 	item->refcnt++;
998     debug_return_ptr(item->d.gidlist);
999 }
1000 
1001 int
sudo_set_gidlist(struct passwd * pw,char * const * gids,unsigned int type)1002 sudo_set_gidlist(struct passwd *pw, char * const *gids, unsigned int type)
1003 {
1004     struct cache_item key, *item;
1005     debug_decl(sudo_set_gidlist, SUDOERS_DEBUG_NSS);
1006 
1007     if (gidlist_cache == NULL) {
1008 	gidlist_cache = rbcreate(cmp_gidlist);
1009 	if (gidlist_cache == NULL) {
1010 	    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
1011 	    debug_return_int(-1);
1012 	}
1013     }
1014 
1015     /*
1016      * Cache group db entry if it doesn't already exist
1017      */
1018     key.k.name = pw->pw_name;
1019     key.type = type;
1020     getauthregistry(NULL, key.registry);
1021     if (rbfind(gidlist_cache, &key) == NULL) {
1022 	if ((item = make_gidlist_item(pw, gids, type)) == NULL) {
1023 	    sudo_warnx(U_("unable to parse gids for %s"), pw->pw_name);
1024 	    debug_return_int(-1);
1025 	}
1026 	strlcpy(item->registry, key.registry, sizeof(item->registry));
1027 	switch (rbinsert(gidlist_cache, item, NULL)) {
1028 	case 1:
1029 	    sudo_warnx(U_("unable to cache group list for %s, already exists"),
1030 		pw->pw_name);
1031 	    sudo_gidlist_delref_item(item);
1032 	    break;
1033 	case -1:
1034 	    sudo_warn(U_("unable to cache group list for %s"), pw->pw_name);
1035 	    sudo_gidlist_delref_item(item);
1036 	    debug_return_int(-1);
1037 	}
1038     }
1039     debug_return_int(0);
1040 }
1041 
1042 bool
user_in_group(const struct passwd * pw,const char * group)1043 user_in_group(const struct passwd *pw, const char *group)
1044 {
1045     struct group_list *grlist = NULL;
1046     struct gid_list *gidlist = NULL;
1047     struct group *grp = NULL;
1048     bool matched = false;
1049     int i;
1050     debug_decl(user_in_group, SUDOERS_DEBUG_NSS);
1051 
1052     /*
1053      * If it could be a sudo-style group-ID check gids first.
1054      */
1055     if (group[0] == '#') {
1056 	const char *errstr;
1057 	gid_t gid = (gid_t) sudo_strtoid(group + 1, &errstr);
1058 	if (errstr != NULL) {
1059 	    sudo_debug_printf(SUDO_DEBUG_DIAG|SUDO_DEBUG_LINENO,
1060 		"gid %s %s", group, errstr);
1061 	} else {
1062 	    if (gid == pw->pw_gid) {
1063 		matched = true;
1064 		goto done;
1065 	    }
1066 	    if ((gidlist = sudo_get_gidlist(pw, ENTRY_TYPE_ANY)) != NULL) {
1067 		for (i = 0; i < gidlist->ngids; i++) {
1068 		    if (gid == gidlist->gids[i]) {
1069 			matched = true;
1070 			goto done;
1071 		    }
1072 		}
1073 	    }
1074 	}
1075     }
1076 
1077     /*
1078      * Next match the group name.  By default, sudoers resolves all the user's
1079      * group-IDs to names and matches by name.  If match_group_by_gid is
1080      * set, each group is sudoers is resolved and matching is by group-ID.
1081      */
1082     if (def_match_group_by_gid) {
1083 	gid_t gid;
1084 
1085 	/* Look up the ID of the group in sudoers. */
1086 	if ((grp = sudo_getgrnam(group)) == NULL)
1087 	    goto done;
1088 	gid = grp->gr_gid;
1089 
1090 	/* Check against user's primary (passwd file) group-ID. */
1091 	if (gid == pw->pw_gid) {
1092 	    matched = true;
1093 	    goto done;
1094 	}
1095 
1096 	/* Check the supplementary group vector. */
1097 	if (gidlist == NULL) {
1098 	    if ((gidlist = sudo_get_gidlist(pw, ENTRY_TYPE_ANY)) != NULL) {
1099 		for (i = 0; i < gidlist->ngids; i++) {
1100 		    if (gid == gidlist->gids[i]) {
1101 			matched = true;
1102 			goto done;
1103 		    }
1104 		}
1105 	    }
1106 	}
1107     } else if ((grlist = sudo_get_grlist(pw)) != NULL) {
1108 	int (*compare)(const char *, const char *);
1109 	if (def_case_insensitive_group)
1110 	    compare = strcasecmp;
1111 	else
1112 	    compare = strcmp;
1113 
1114 	/* Check the supplementary group vector. */
1115 	for (i = 0; i < grlist->ngroups; i++) {
1116 	    if (compare(group, grlist->groups[i]) == 0) {
1117 		matched = true;
1118 		goto done;
1119 	    }
1120 	}
1121 
1122 	/* Check against user's primary (passwd file) group. */
1123 	if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) {
1124 	    if (compare(group, grp->gr_name) == 0) {
1125 		matched = true;
1126 		goto done;
1127 	    }
1128 	}
1129     }
1130 
1131 done:
1132     if (grp != NULL)
1133 	sudo_gr_delref(grp);
1134     if (grlist != NULL)
1135 	sudo_grlist_delref(grlist);
1136     if (gidlist != NULL)
1137 	sudo_gidlist_delref(gidlist);
1138 
1139     sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: user %s %sin group %s",
1140 	__func__, pw->pw_name, matched ? "" : "NOT ", group);
1141     debug_return_bool(matched);
1142 }
1143