Imported Upstream version 1.8.7
[debian/sudo] / plugins / sudoers / pwutil.c
1 /*
2  * Copyright (c) 1996, 1998-2005, 2007-2013
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 <stdio.h>
26 #ifdef STDC_HEADERS
27 # include <stdlib.h>
28 # include <stddef.h>
29 #else
30 # ifdef HAVE_STDLIB_H
31 #  include <stdlib.h>
32 # endif
33 #endif /* STDC_HEADERS */
34 #ifdef HAVE_STRING_H
35 # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
36 #  include <memory.h>
37 # endif
38 # include <string.h>
39 #endif /* HAVE_STRING_H */
40 #ifdef HAVE_STRINGS_H
41 # include <strings.h>
42 #endif /* HAVE_STRINGS_H */
43 #ifdef HAVE_UNISTD_H
44 # include <unistd.h>
45 #endif /* HAVE_UNISTD_H */
46 #ifdef HAVE_SETAUTHDB
47 # include <usersec.h>
48 #endif /* HAVE_SETAUTHDB */
49 #include <pwd.h>
50 #include <grp.h>
51
52 #include "sudoers.h"
53 #include "redblack.h"
54 #include "pwutil.h"
55
56 /*
57  * The passwd and group caches.
58  */
59 static struct rbtree *pwcache_byuid, *pwcache_byname;
60 static struct rbtree *grcache_bygid, *grcache_byname;
61 static struct rbtree *grlist_cache;
62
63 static int  cmp_pwuid(const void *, const void *);
64 static int  cmp_pwnam(const void *, const void *);
65 static int  cmp_grgid(const void *, const void *);
66
67 #define cmp_grnam       cmp_pwnam
68
69 /*
70  * Compare by uid.
71  */
72 static int
73 cmp_pwuid(const void *v1, const void *v2)
74 {
75     const struct cache_item *ci1 = (const struct cache_item *) v1;
76     const struct cache_item *ci2 = (const struct cache_item *) v2;
77     return ci1->k.uid - ci2->k.uid;
78 }
79
80 /*
81  * Compare by user name.
82  */
83 static int
84 cmp_pwnam(const void *v1, const void *v2)
85 {
86     const struct cache_item *ci1 = (const struct cache_item *) v1;
87     const struct cache_item *ci2 = (const struct cache_item *) v2;
88     return strcmp(ci1->k.name, ci2->k.name);
89 }
90
91 void
92 sudo_pw_addref(struct passwd *pw)
93 {
94     debug_decl(sudo_pw_addref, SUDO_DEBUG_NSS)
95     ptr_to_item(pw)->refcnt++;
96     debug_return;
97 }
98
99 static void
100 sudo_pw_delref_item(void *v)
101 {
102     struct cache_item *item = v;
103     debug_decl(sudo_pw_delref_item, SUDO_DEBUG_NSS)
104
105     if (--item->refcnt == 0)
106         efree(item);
107
108     debug_return;
109 }
110
111 void
112 sudo_pw_delref(struct passwd *pw)
113 {
114     debug_decl(sudo_pw_delref, SUDO_DEBUG_NSS)
115     sudo_pw_delref_item(ptr_to_item(pw));
116     debug_return;
117 }
118
119 /*
120  * Get a password entry by uid and allocate space for it.
121  */
122 struct passwd *
123 sudo_getpwuid(uid_t uid)
124 {
125     struct cache_item key, *item;
126     struct rbnode *node;
127     debug_decl(sudo_getpwuid, SUDO_DEBUG_NSS)
128
129     key.k.uid = uid;
130     if ((node = rbfind(pwcache_byuid, &key)) != NULL) {
131         item = (struct cache_item *) node->data;
132         goto done;
133     }
134     /*
135      * Cache passwd db entry if it exists or a negative response if not.
136      */
137 #ifdef HAVE_SETAUTHDB
138     aix_setauthdb(IDtouser(uid));
139 #endif
140     item = sudo_make_pwitem(uid, NULL);
141     if (item == NULL) {
142         item = ecalloc(1, sizeof(*item));
143         item->refcnt = 1;
144         item->k.uid = uid;
145         /* item->d.pw = NULL; */
146     }
147     if (rbinsert(pwcache_byuid, item) != NULL)
148         fatalx(_("unable to cache uid %u, already exists"),
149             (unsigned int) uid);
150 #ifdef HAVE_SETAUTHDB
151     aix_restoreauthdb();
152 #endif
153 done:
154     item->refcnt++;
155     debug_return_ptr(item->d.pw);
156 }
157
158 /*
159  * Get a password entry by name and allocate space for it.
160  */
161 struct passwd *
162 sudo_getpwnam(const char *name)
163 {
164     struct cache_item key, *item;
165     struct rbnode *node;
166     size_t len;
167     debug_decl(sudo_getpwnam, SUDO_DEBUG_NSS)
168
169     key.k.name = (char *) name;
170     if ((node = rbfind(pwcache_byname, &key)) != NULL) {
171         item = (struct cache_item *) node->data;
172         goto done;
173     }
174     /*
175      * Cache passwd db entry if it exists or a negative response if not.
176      */
177 #ifdef HAVE_SETAUTHDB
178     aix_setauthdb((char *) name);
179 #endif
180     item = sudo_make_pwitem((uid_t)-1, name);
181     if (item == NULL) {
182         len = strlen(name) + 1;
183         item = ecalloc(1, sizeof(*item) + len);
184         item->refcnt = 1;
185         item->k.name = (char *) item + sizeof(*item);
186         memcpy(item->k.name, name, len);
187         /* item->d.pw = NULL; */
188     }
189     if (rbinsert(pwcache_byname, item) != NULL)
190         fatalx(_("unable to cache user %s, already exists"), name);
191 #ifdef HAVE_SETAUTHDB
192     aix_restoreauthdb();
193 #endif
194 done:
195     item->refcnt++;
196     debug_return_ptr(item->d.pw);
197 }
198
199 /*
200  * Take a user, uid, gid, home and shell and return a faked up passwd struct.
201  * If home or shell are NULL default values will be used.
202  */
203 struct passwd *
204 sudo_mkpwent(const char *user, uid_t uid, gid_t gid, const char *home,
205     const char *shell)
206 {
207     struct cache_item_pw *pwitem;
208     struct passwd *pw;
209     struct rbnode *node;
210     size_t len, name_len, home_len, shell_len;
211     int i;
212     debug_decl(sudo_mkpwent, SUDO_DEBUG_NSS)
213
214     /* Optional arguments. */
215     if (home == NULL)
216         home = "/";
217     if (shell == NULL)
218         shell = _PATH_BSHELL;
219
220     name_len = strlen(user);
221     home_len = strlen(home);
222     shell_len = strlen(shell);
223     len = sizeof(*pwitem) + name_len + 1 /* pw_name */ +
224         sizeof("*") /* pw_passwd */ + sizeof("") /* pw_gecos */ +
225         home_len + 1 /* pw_dir */ + shell_len + 1 /* pw_shell */;
226
227     for (i = 0; i < 2; i++) {
228         pwitem = ecalloc(1, len);
229         pw = &pwitem->pw;
230         pw->pw_uid = uid;
231         pw->pw_gid = gid;
232         pw->pw_name = (char *)(pwitem + 1);
233         memcpy(pw->pw_name, user, name_len + 1);
234         pw->pw_passwd = pw->pw_name + name_len + 1;
235         memcpy(pw->pw_passwd, "*", 2);
236         pw->pw_gecos = pw->pw_passwd + 2;
237         pw->pw_gecos[0] = '\0';
238         pw->pw_dir = pw->pw_gecos + 1;
239         memcpy(pw->pw_dir, home, home_len + 1);
240         pw->pw_shell = pw->pw_dir + home_len + 1;
241         memcpy(pw->pw_shell, shell, shell_len + 1);
242
243         pwitem->cache.refcnt = 1;
244         pwitem->cache.d.pw = pw;
245         if (i == 0) {
246             /* Store by uid if it doesn't already exist. */
247             pwitem->cache.k.uid = pw->pw_uid;
248             if ((node = rbinsert(pwcache_byuid, &pwitem->cache)) != NULL) {
249                 /* Already exists, free the item we created. */
250                 efree(pwitem);
251                 pwitem = (struct cache_item_pw *) node->data;
252             }
253         } else {
254             /* Store by name if it doesn't already exist. */
255             pwitem->cache.k.name = pw->pw_name;
256             if ((node = rbinsert(pwcache_byname, &pwitem->cache)) != NULL) {
257                 /* Already exists, free the item we created. */
258                 efree(pwitem);
259                 pwitem = (struct cache_item_pw *) node->data;
260             }
261         }
262     }
263     pwitem->cache.refcnt++;
264     debug_return_ptr(&pwitem->pw);
265 }
266
267 /*
268  * Take a uid in string form "#123" and return a faked up passwd struct.
269  */
270 struct passwd *
271 sudo_fakepwnam(const char *user, gid_t gid)
272 {
273     uid_t uid;
274
275     uid = (uid_t) atoi(user + 1);
276     return sudo_mkpwent(user, uid, gid, NULL, NULL);
277 }
278
279 void
280 sudo_setpwent(void)
281 {
282     debug_decl(sudo_setpwent, SUDO_DEBUG_NSS)
283
284     setpwent();
285     if (pwcache_byuid == NULL)
286         pwcache_byuid = rbcreate(cmp_pwuid);
287     if (pwcache_byname == NULL)
288         pwcache_byname = rbcreate(cmp_pwnam);
289
290     debug_return;
291 }
292
293 void
294 sudo_freepwcache(void)
295 {
296     debug_decl(sudo_freepwcache, SUDO_DEBUG_NSS)
297
298     if (pwcache_byuid != NULL) {
299         rbdestroy(pwcache_byuid, sudo_pw_delref_item);
300         pwcache_byuid = NULL;
301     }
302     if (pwcache_byname != NULL) {
303         rbdestroy(pwcache_byname, sudo_pw_delref_item);
304         pwcache_byname = NULL;
305     }
306
307     debug_return;
308 }
309
310 void
311 sudo_endpwent(void)
312 {
313     debug_decl(sudo_endpwent, SUDO_DEBUG_NSS)
314
315     endpwent();
316     sudo_freepwcache();
317
318     debug_return;
319 }
320
321 /*
322  * Compare by gid.
323  */
324 static int
325 cmp_grgid(const void *v1, const void *v2)
326 {
327     const struct cache_item *ci1 = (const struct cache_item *) v1;
328     const struct cache_item *ci2 = (const struct cache_item *) v2;
329     return ci1->k.gid - ci2->k.gid;
330 }
331
332 void
333 sudo_gr_addref(struct group *gr)
334 {
335     debug_decl(sudo_gr_addref, SUDO_DEBUG_NSS)
336     ptr_to_item(gr)->refcnt++;
337     debug_return;
338 }
339
340 static void
341 sudo_gr_delref_item(void *v)
342 {
343     struct cache_item *item = v;
344     debug_decl(sudo_gr_delref_item, SUDO_DEBUG_NSS)
345
346     if (--item->refcnt == 0)
347         efree(item);
348
349     debug_return;
350 }
351
352 void
353 sudo_gr_delref(struct group *gr)
354 {
355     debug_decl(sudo_gr_delref, SUDO_DEBUG_NSS)
356     sudo_gr_delref_item(ptr_to_item(gr));
357     debug_return;
358 }
359
360 /*
361  * Get a group entry by gid and allocate space for it.
362  */
363 struct group *
364 sudo_getgrgid(gid_t gid)
365 {
366     struct cache_item key, *item;
367     struct rbnode *node;
368     debug_decl(sudo_getgrgid, SUDO_DEBUG_NSS)
369
370     key.k.gid = gid;
371     if ((node = rbfind(grcache_bygid, &key)) != NULL) {
372         item = (struct cache_item *) node->data;
373         goto done;
374     }
375     /*
376      * Cache group db entry if it exists or a negative response if not.
377      */
378     item = sudo_make_gritem(gid, NULL);
379     if (item == NULL) {
380         item = ecalloc(1, sizeof(*item));
381         item->refcnt = 1;
382         item->k.gid = gid;
383         /* item->d.gr = NULL; */
384     }
385     if (rbinsert(grcache_bygid, item) != NULL)
386         fatalx(_("unable to cache gid %u, already exists"),
387             (unsigned int) gid);
388 done:
389     item->refcnt++;
390     debug_return_ptr(item->d.gr);
391 }
392
393 /*
394  * Get a group entry by name and allocate space for it.
395  */
396 struct group *
397 sudo_getgrnam(const char *name)
398 {
399     struct cache_item key, *item;
400     struct rbnode *node;
401     size_t len;
402     debug_decl(sudo_getgrnam, SUDO_DEBUG_NSS)
403
404     key.k.name = (char *) name;
405     if ((node = rbfind(grcache_byname, &key)) != NULL) {
406         item = (struct cache_item *) node->data;
407         goto done;
408     }
409     /*
410      * Cache group db entry if it exists or a negative response if not.
411      */
412     item = sudo_make_gritem((gid_t)-1, name);
413     if (item == NULL) {
414         len = strlen(name) + 1;
415         item = ecalloc(1, sizeof(*item) + len);
416         item->refcnt = 1;
417         item->k.name = (char *) item + sizeof(*item);
418         memcpy(item->k.name, name, len);
419         /* item->d.gr = NULL; */
420     }
421     if (rbinsert(grcache_byname, item) != NULL)
422         fatalx(_("unable to cache group %s, already exists"), name);
423 done:
424     item->refcnt++;
425     debug_return_ptr(item->d.gr);
426 }
427
428 /*
429  * Take a gid in string form "#123" and return a faked up group struct.
430  */
431 struct group *
432 sudo_fakegrnam(const char *group)
433 {
434     struct cache_item_gr *gritem;
435     struct group *gr;
436     struct rbnode *node;
437     size_t len, name_len;
438     int i;
439     debug_decl(sudo_fakegrnam, SUDO_DEBUG_NSS)
440
441     name_len = strlen(group);
442     len = sizeof(*gritem) + name_len + 1;
443
444     for (i = 0; i < 2; i++) {
445         gritem = ecalloc(1, len);
446         gr = &gritem->gr;
447         gr->gr_gid = (gid_t) atoi(group + 1);
448         gr->gr_name = (char *)(gritem + 1);
449         memcpy(gr->gr_name, group, name_len + 1);
450
451         gritem->cache.refcnt = 1;
452         gritem->cache.d.gr = gr;
453         if (i == 0) {
454             /* Store by gid if it doesn't already exist. */
455             gritem->cache.k.gid = gr->gr_gid;
456             if ((node = rbinsert(grcache_bygid, &gritem->cache)) != NULL) {
457                 /* Already exists, free the item we created. */
458                 efree(gritem);
459                 gritem = (struct cache_item_gr *) node->data;
460             }
461         } else {
462             /* Store by name, overwriting cached version. */
463             gritem->cache.k.name = gr->gr_name;
464             if ((node = rbinsert(grcache_byname, &gritem->cache)) != NULL) {
465                 /* Already exists, free the item we created. */
466                 efree(gritem);
467                 gritem = (struct cache_item_gr *) node->data;
468             }
469         }
470     }
471     gritem->cache.refcnt++;
472     debug_return_ptr(&gritem->gr);
473 }
474
475 void
476 sudo_grlist_addref(struct group_list *grlist)
477 {
478     debug_decl(sudo_gr_addref, SUDO_DEBUG_NSS)
479     ptr_to_item(grlist)->refcnt++;
480     debug_return;
481 }
482
483 static void
484 sudo_grlist_delref_item(void *v)
485 {
486     struct cache_item *item = v;
487     debug_decl(sudo_gr_delref_item, SUDO_DEBUG_NSS)
488
489     if (--item->refcnt == 0)
490         efree(item);
491
492     debug_return;
493 }
494
495 void
496 sudo_grlist_delref(struct group_list *grlist)
497 {
498     debug_decl(sudo_gr_delref, SUDO_DEBUG_NSS)
499     sudo_grlist_delref_item(ptr_to_item(grlist));
500     debug_return;
501 }
502
503 void
504 sudo_setgrent(void)
505 {
506     debug_decl(sudo_setgrent, SUDO_DEBUG_NSS)
507
508     setgrent();
509     if (grcache_bygid == NULL)
510         grcache_bygid = rbcreate(cmp_grgid);
511     if (grcache_byname == NULL)
512         grcache_byname = rbcreate(cmp_grnam);
513     if (grlist_cache == NULL)
514         grlist_cache = rbcreate(cmp_grnam);
515
516     debug_return;
517 }
518
519 void
520 sudo_freegrcache(void)
521 {
522     debug_decl(sudo_freegrcache, SUDO_DEBUG_NSS)
523
524     if (grcache_bygid != NULL) {
525         rbdestroy(grcache_bygid, sudo_gr_delref_item);
526         grcache_bygid = NULL;
527     }
528     if (grcache_byname != NULL) {
529         rbdestroy(grcache_byname, sudo_gr_delref_item);
530         grcache_byname = NULL;
531     }
532     if (grlist_cache != NULL) {
533         rbdestroy(grlist_cache, sudo_grlist_delref_item);
534         grlist_cache = NULL;
535     }
536
537     debug_return;
538 }
539
540 void
541 sudo_endgrent(void)
542 {
543     debug_decl(sudo_endgrent, SUDO_DEBUG_NSS)
544
545     endgrent();
546     sudo_freegrcache();
547
548     debug_return;
549 }
550
551 struct group_list *
552 sudo_get_grlist(struct passwd *pw)
553 {
554     struct cache_item key, *item;
555     struct rbnode *node;
556     size_t len;
557     debug_decl(sudo_get_grlist, SUDO_DEBUG_NSS)
558
559     key.k.name = pw->pw_name;
560     if ((node = rbfind(grlist_cache, &key)) != NULL) {
561         item = (struct cache_item *) node->data;
562         goto done;
563     }
564     /*
565      * Cache group db entry if it exists or a negative response if not.
566      */
567     item = sudo_make_grlist_item(pw, NULL, NULL);
568     if (item == NULL) {
569         /* Should not happen. */
570         len = strlen(pw->pw_name) + 1;
571         item = ecalloc(1, sizeof(*item) + len);
572         item->refcnt = 1;
573         item->k.name = (char *) item + sizeof(*item);
574         memcpy(item->k.name, pw->pw_name, len);
575         /* item->d.grlist = NULL; */
576     }
577     if (rbinsert(grlist_cache, item) != NULL)
578         fatalx(_("unable to cache group list for %s, already exists"),
579             pw->pw_name);
580 done:
581     item->refcnt++;
582     debug_return_ptr(item->d.grlist);
583 }
584
585 void
586 sudo_set_grlist(struct passwd *pw, char * const *groups, char * const *gids)
587 {
588     struct cache_item key, *item;
589     struct rbnode *node;
590     debug_decl(sudo_set_grlist, SUDO_DEBUG_NSS)
591
592     /*
593      * Cache group db entry if it doesn't already exist
594      */
595     key.k.name = pw->pw_name;
596     if ((node = rbfind(grlist_cache, &key)) == NULL) {
597         if ((item = sudo_make_grlist_item(pw, groups, gids)) == NULL)
598             fatalx(_("unable to parse groups for %s"), pw->pw_name);
599         if (rbinsert(grlist_cache, item) != NULL)
600             fatalx(_("unable to cache group list for %s, already exists"),
601                 pw->pw_name);
602     }
603     debug_return;
604 }
605
606 bool
607 user_in_group(struct passwd *pw, const char *group)
608 {
609     struct group_list *grlist;
610     struct group *grp = NULL;
611     int i;
612     bool matched = false;
613     debug_decl(user_in_group, SUDO_DEBUG_NSS)
614
615     if ((grlist = sudo_get_grlist(pw)) != NULL) {
616         /*
617          * If it could be a sudo-style group ID check gids first.
618          */
619         if (group[0] == '#') {
620             gid_t gid = atoi(group + 1);
621             if (gid == pw->pw_gid) {
622                 matched = true;
623                 goto done;
624             }
625             for (i = 0; i < grlist->ngids; i++) {
626                 if (gid == grlist->gids[i]) {
627                     matched = true;
628                     goto done;
629                 }
630             }
631         }
632
633         /*
634          * Next check the supplementary group vector.
635          * It usually includes the password db group too.
636          */
637         for (i = 0; i < grlist->ngroups; i++) {
638             if (strcasecmp(group, grlist->groups[i]) == 0) {
639                 matched = true;
640                 goto done;
641             }
642         }
643
644         /* Finally check against user's primary (passwd file) group. */
645         if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) {
646             if (strcasecmp(group, grp->gr_name) == 0) {
647                 matched = true;
648                 goto done;
649             }
650         }
651 done:
652         if (grp != NULL)
653             sudo_gr_delref(grp);
654         sudo_grlist_delref(grlist);
655     }
656     debug_return_bool(matched);
657 }