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