File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / plugins / sudoers / match.c
Revision 1.1.1.6 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Sun Jun 15 16:12:54 2014 UTC (10 years, 1 month ago) by misho
Branches: sudo, MAIN
CVS tags: v1_8_10p3_0, v1_8_10p3, HEAD
sudo v 1.8.10p3

    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:  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   17:  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   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: #include <config.h>
   25: 
   26: #include <sys/types.h>
   27: #include <sys/stat.h>
   28: #include <stdio.h>
   29: #ifdef STDC_HEADERS
   30: # include <stdlib.h>
   31: # include <stddef.h>
   32: #else
   33: # ifdef HAVE_STDLIB_H
   34: #  include <stdlib.h>
   35: # endif
   36: #endif /* STDC_HEADERS */
   37: #ifdef HAVE_STRING_H
   38: # include <string.h>
   39: #endif /* HAVE_STRING_H */
   40: #ifdef HAVE_STRINGS_H
   41: # include <strings.h>
   42: #endif /* HAVE_STRINGS_H */
   43: #if defined(HAVE_STDINT_H)
   44: # include <stdint.h>
   45: #elif defined(HAVE_INTTYPES_H)
   46: # include <inttypes.h>
   47: #endif
   48: #ifdef HAVE_UNISTD_H
   49: # include <unistd.h>
   50: #endif /* HAVE_UNISTD_H */
   51: #ifdef HAVE_FNMATCH
   52: # include <fnmatch.h>
   53: #else
   54: # include "compat/fnmatch.h"
   55: #endif /* HAVE_FNMATCH */
   56: #ifndef SUDOERS_NAME_MATCH
   57: # ifdef HAVE_GLOB
   58: #  include <glob.h>
   59: # else
   60: #  include "compat/glob.h"
   61: # endif /* HAVE_GLOB */
   62: #endif /* SUDOERS_NAME_MATCH */
   63: #ifdef HAVE_NETGROUP_H
   64: # include <netgroup.h>
   65: #else
   66: # include <netdb.h>
   67: #endif /* HAVE_NETGROUP_H */
   68: #ifdef HAVE_DIRENT_H
   69: # include <dirent.h>
   70: # define NAMLEN(dirent) strlen((dirent)->d_name)
   71: #else
   72: # define dirent direct
   73: # define NAMLEN(dirent) (dirent)->d_namlen
   74: # ifdef HAVE_SYS_NDIR_H
   75: #  include <sys/ndir.h>
   76: # endif
   77: # ifdef HAVE_SYS_DIR_H
   78: #  include <sys/dir.h>
   79: # endif
   80: # ifdef HAVE_NDIR_H
   81: #  include <ndir.h>
   82: # endif
   83: #endif
   84: #include <ctype.h>
   85: #include <pwd.h>
   86: #include <grp.h>
   87: #include <errno.h>
   88: 
   89: #include "sudoers.h"
   90: #include "parse.h"
   91: #include "sha2.h"
   92: #include <gram.h>
   93: 
   94: static struct member_list empty = TAILQ_HEAD_INITIALIZER(empty);
   95: 
   96: static bool command_matches_dir(const char *sudoers_dir, size_t dlen);
   97: #ifndef SUDOERS_NAME_MATCH
   98: static bool command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args);
   99: #endif
  100: static bool command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args);
  101: static bool command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const struct sudo_digest *digest);
  102: 
  103: /*
  104:  * Returns true if string 's' contains meta characters.
  105:  */
  106: #define has_meta(s)	(strpbrk(s, "\\?*[]") != NULL)
  107: 
  108: /*
  109:  * Check for user described by pw in a list of members.
  110:  * Returns ALLOW, DENY or UNSPEC.
  111:  */
  112: int
  113: userlist_matches(const struct passwd *pw, const struct member_list *list)
  114: {
  115:     struct member *m;
  116:     struct alias *a;
  117:     int rval, matched = UNSPEC;
  118:     debug_decl(userlist_matches, SUDO_DEBUG_MATCH)
  119: 
  120:     TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
  121: 	switch (m->type) {
  122: 	    case ALL:
  123: 		matched = !m->negated;
  124: 		break;
  125: 	    case NETGROUP:
  126: 		if (netgr_matches(m->name, NULL, NULL, pw->pw_name))
  127: 		    matched = !m->negated;
  128: 		break;
  129: 	    case USERGROUP:
  130: 		if (usergr_matches(m->name, pw->pw_name, pw))
  131: 		    matched = !m->negated;
  132: 		break;
  133: 	    case ALIAS:
  134: 		if ((a = alias_get(m->name, USERALIAS)) != NULL) {
  135: 		    rval = userlist_matches(pw, &a->members);
  136: 		    if (rval != UNSPEC)
  137: 			matched = m->negated ? !rval : rval;
  138: 		    alias_put(a);
  139: 		    break;
  140: 		}
  141: 		/* FALLTHROUGH */
  142: 	    case WORD:
  143: 		if (userpw_matches(m->name, pw->pw_name, pw))
  144: 		    matched = !m->negated;
  145: 		break;
  146: 	}
  147: 	if (matched != UNSPEC)
  148: 	    break;
  149:     }
  150:     debug_return_bool(matched);
  151: }
  152: 
  153: /*
  154:  * Check for user described by pw in a list of members.
  155:  * If both lists are empty compare against def_runas_default.
  156:  * Returns ALLOW, DENY or UNSPEC.
  157:  */
  158: int
  159: runaslist_matches(const struct member_list *user_list,
  160:     const struct member_list *group_list, struct member **matching_user,
  161:     struct member **matching_group)
  162: {
  163:     struct member *m;
  164:     struct alias *a;
  165:     int rval;
  166:     int user_matched = UNSPEC;
  167:     int group_matched = UNSPEC;
  168:     debug_decl(runaslist_matches, SUDO_DEBUG_MATCH)
  169: 
  170:     if (runas_pw != NULL) {
  171: 	/* If no runas user or runas group listed in sudoers, use default. */
  172: 	if (user_list == NULL && group_list == NULL)
  173: 	    debug_return_int(userpw_matches(def_runas_default, runas_pw->pw_name, runas_pw));
  174: 
  175: 	if (user_list != NULL) {
  176: 	    TAILQ_FOREACH_REVERSE(m, user_list, member_list, entries) {
  177: 		switch (m->type) {
  178: 		    case ALL:
  179: 			user_matched = !m->negated;
  180: 			break;
  181: 		    case NETGROUP:
  182: 			if (netgr_matches(m->name, NULL, NULL, runas_pw->pw_name))
  183: 			    user_matched = !m->negated;
  184: 			break;
  185: 		    case USERGROUP:
  186: 			if (usergr_matches(m->name, runas_pw->pw_name, runas_pw))
  187: 			    user_matched = !m->negated;
  188: 			break;
  189: 		    case ALIAS:
  190: 			if ((a = alias_get(m->name, RUNASALIAS)) != NULL) {
  191: 			    rval = runaslist_matches(&a->members, &empty,
  192: 				matching_user, NULL);
  193: 			    if (rval != UNSPEC)
  194: 				user_matched = m->negated ? !rval : rval;
  195: 			    alias_put(a);
  196: 			    break;
  197: 			}
  198: 			/* FALLTHROUGH */
  199: 		    case WORD:
  200: 			if (userpw_matches(m->name, runas_pw->pw_name, runas_pw))
  201: 			    user_matched = !m->negated;
  202: 			break;
  203: 		    case MYSELF:
  204: 			if (!ISSET(sudo_user.flags, RUNAS_USER_SPECIFIED) ||
  205: 			    strcmp(user_name, runas_pw->pw_name) == 0)
  206: 			    user_matched = !m->negated;
  207: 			break;
  208: 		}
  209: 		if (user_matched != UNSPEC) {
  210: 		    if (matching_user != NULL && m->type != ALIAS)
  211: 			*matching_user = m;
  212: 		    break;
  213: 		}
  214: 	    }
  215: 	}
  216:     }
  217: 
  218:     if (runas_gr != NULL) {
  219: 	if (user_matched == UNSPEC) {
  220: 	    if (runas_pw == NULL || strcmp(runas_pw->pw_name, user_name) == 0)
  221: 		user_matched = ALLOW;	/* only changing group */
  222: 	}
  223: 	if (group_list != NULL) {
  224: 	    TAILQ_FOREACH_REVERSE(m, group_list, member_list, entries) {
  225: 		switch (m->type) {
  226: 		    case ALL:
  227: 			group_matched = !m->negated;
  228: 			break;
  229: 		    case ALIAS:
  230: 			if ((a = alias_get(m->name, RUNASALIAS)) != NULL) {
  231: 			    rval = runaslist_matches(&empty, &a->members,
  232: 				NULL, matching_group);
  233: 			    if (rval != UNSPEC)
  234: 				group_matched = m->negated ? !rval : rval;
  235: 			    alias_put(a);
  236: 			    break;
  237: 			}
  238: 			/* FALLTHROUGH */
  239: 		    case WORD:
  240: 			if (group_matches(m->name, runas_gr))
  241: 			    group_matched = !m->negated;
  242: 			break;
  243: 		}
  244: 		if (group_matched != UNSPEC) {
  245: 		    if (matching_group != NULL && m->type != ALIAS)
  246: 			*matching_group = m;
  247: 		    break;
  248: 		}
  249: 	    }
  250: 	}
  251: 	if (group_matched == UNSPEC) {
  252: 	    if (runas_pw != NULL && runas_pw->pw_gid == runas_gr->gr_gid)
  253: 		group_matched = ALLOW;	/* runas group matches passwd db */
  254: 	}
  255:     }
  256: 
  257:     if (user_matched == DENY || group_matched == DENY)
  258: 	debug_return_int(DENY);
  259:     if (user_matched == group_matched || runas_gr == NULL)
  260: 	debug_return_int(user_matched);
  261:     debug_return_int(UNSPEC);
  262: }
  263: 
  264: /*
  265:  * Check for host and shost in a list of members.
  266:  * Returns ALLOW, DENY or UNSPEC.
  267:  */
  268: int
  269: hostlist_matches(const struct member_list *list)
  270: {
  271:     struct member *m;
  272:     struct alias *a;
  273:     int rval, matched = UNSPEC;
  274:     debug_decl(hostlist_matches, SUDO_DEBUG_MATCH)
  275: 
  276:     TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
  277: 	switch (m->type) {
  278: 	    case ALL:
  279: 		matched = !m->negated;
  280: 		break;
  281: 	    case NETGROUP:
  282: 		if (netgr_matches(m->name, user_runhost, user_srunhost, NULL))
  283: 		    matched = !m->negated;
  284: 		break;
  285: 	    case NTWKADDR:
  286: 		if (addr_matches(m->name))
  287: 		    matched = !m->negated;
  288: 		break;
  289: 	    case ALIAS:
  290: 		if ((a = alias_get(m->name, HOSTALIAS)) != NULL) {
  291: 		    rval = hostlist_matches(&a->members);
  292: 		    if (rval != UNSPEC)
  293: 			matched = m->negated ? !rval : rval;
  294: 		    alias_put(a);
  295: 		    break;
  296: 		}
  297: 		/* FALLTHROUGH */
  298: 	    case WORD:
  299: 		if (hostname_matches(user_srunhost, user_runhost, m->name))
  300: 		    matched = !m->negated;
  301: 		break;
  302: 	}
  303: 	if (matched != UNSPEC)
  304: 	    break;
  305:     }
  306:     debug_return_bool(matched);
  307: }
  308: 
  309: /*
  310:  * Check for cmnd and args in a list of members.
  311:  * Returns ALLOW, DENY or UNSPEC.
  312:  */
  313: int
  314: cmndlist_matches(const struct member_list *list)
  315: {
  316:     struct member *m;
  317:     int matched = UNSPEC;
  318:     debug_decl(cmndlist_matches, SUDO_DEBUG_MATCH)
  319: 
  320:     TAILQ_FOREACH_REVERSE(m, list, member_list, entries) {
  321: 	matched = cmnd_matches(m);
  322: 	if (matched != UNSPEC)
  323: 	    break;
  324:     }
  325:     debug_return_bool(matched);
  326: }
  327: 
  328: /*
  329:  * Check cmnd and args.
  330:  * Returns ALLOW, DENY or UNSPEC.
  331:  */
  332: int
  333: cmnd_matches(const struct member *m)
  334: {
  335:     struct alias *a;
  336:     struct sudo_command *c;
  337:     int rval, matched = UNSPEC;
  338:     debug_decl(cmnd_matches, SUDO_DEBUG_MATCH)
  339: 
  340:     switch (m->type) {
  341: 	case ALL:
  342: 	    matched = !m->negated;
  343: 	    break;
  344: 	case ALIAS:
  345: 	    if ((a = alias_get(m->name, CMNDALIAS)) != NULL) {
  346: 		rval = cmndlist_matches(&a->members);
  347: 		if (rval != UNSPEC)
  348: 		    matched = m->negated ? !rval : rval;
  349: 		alias_put(a);
  350: 	    }
  351: 	    break;
  352: 	case COMMAND:
  353: 	    c = (struct sudo_command *)m->name;
  354: 	    if (command_matches(c->cmnd, c->args, c->digest))
  355: 		matched = !m->negated;
  356: 	    break;
  357:     }
  358:     debug_return_bool(matched);
  359: }
  360: 
  361: static bool
  362: command_args_match(const char *sudoers_cmnd, const char *sudoers_args)
  363: {
  364:     int flags = 0;
  365:     debug_decl(command_args_match, SUDO_DEBUG_MATCH)
  366: 
  367:     /*
  368:      * If no args specified in sudoers, any user args are allowed.
  369:      * If the empty string is specified in sudoers, no user args are allowed.
  370:      */
  371:     if (!sudoers_args ||
  372: 	(!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)))
  373: 	debug_return_bool(true);
  374:     /*
  375:      * If args are specified in sudoers, they must match the user args.
  376:      * If running as sudoedit, all args are assumed to be paths.
  377:      */
  378:     if (sudoers_args) {
  379: 	/* For sudoedit, all args are assumed to be pathnames. */
  380: 	if (strcmp(sudoers_cmnd, "sudoedit") == 0)
  381: 	    flags = FNM_PATHNAME;
  382: 	if (fnmatch(sudoers_args, user_args ? user_args : "", flags) == 0)
  383: 	    debug_return_bool(true);
  384:     }
  385:     debug_return_bool(false);
  386: }
  387: 
  388: /*
  389:  * If path doesn't end in /, return true iff cmnd & path name the same inode;
  390:  * otherwise, return true if user_cmnd names one of the inodes in path.
  391:  */
  392: bool
  393: command_matches(const char *sudoers_cmnd, const char *sudoers_args, const struct sudo_digest *digest)
  394: {
  395:     bool rc = false;
  396:     debug_decl(command_matches, SUDO_DEBUG_MATCH)
  397: 
  398:     /* Check for pseudo-commands */
  399:     if (sudoers_cmnd[0] != '/') {
  400: 	/*
  401: 	 * Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND
  402: 	 *  a) there are no args in sudoers OR
  403: 	 *  b) there are no args on command line and none req by sudoers OR
  404: 	 *  c) there are args in sudoers and on command line and they match
  405: 	 */
  406: 	if (strcmp(sudoers_cmnd, "sudoedit") == 0 &&
  407: 	    strcmp(user_cmnd, "sudoedit") == 0 &&
  408: 	    command_args_match(sudoers_cmnd, sudoers_args)) {
  409: 	    efree(safe_cmnd);
  410: 	    safe_cmnd = estrdup(sudoers_cmnd);
  411: 	    rc = true;
  412: 	}
  413: 	goto done;
  414:     }
  415: 
  416:     if (has_meta(sudoers_cmnd)) {
  417: 	/*
  418: 	 * If sudoers_cmnd has meta characters in it, we need to
  419: 	 * use glob(3) and/or fnmatch(3) to do the matching.
  420: 	 */
  421: #ifdef SUDOERS_NAME_MATCH
  422: 	rc = command_matches_fnmatch(sudoers_cmnd, sudoers_args);
  423: #else
  424: 	if (def_fast_glob)
  425: 	    rc = command_matches_fnmatch(sudoers_cmnd, sudoers_args);
  426: 	else
  427: 	    rc = command_matches_glob(sudoers_cmnd, sudoers_args);
  428: #endif
  429:     } else {
  430: 	rc = command_matches_normal(sudoers_cmnd, sudoers_args, digest);
  431:     }
  432: done:
  433:     sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
  434: 	"user command \"%s%s%s\" matches sudoers command \"%s%s%s\": %s",
  435: 	user_cmnd, user_args ? " " : "", user_args ? user_args : "",
  436: 	sudoers_cmnd, sudoers_args ? " " : "", sudoers_args ? sudoers_args : "",
  437: 	rc ? "true" : "false");
  438:     debug_return_bool(rc);
  439: }
  440: 
  441: static bool
  442: command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args)
  443: {
  444:     debug_decl(command_matches_fnmatch, SUDO_DEBUG_MATCH)
  445: 
  446:     /*
  447:      * Return true if fnmatch(3) succeeds AND
  448:      *  a) there are no args in sudoers OR
  449:      *  b) there are no args on command line and none required by sudoers OR
  450:      *  c) there are args in sudoers and on command line and they match
  451:      * else return false.
  452:      */
  453:     if (fnmatch(sudoers_cmnd, user_cmnd, FNM_PATHNAME) != 0)
  454: 	debug_return_bool(false);
  455:     if (command_args_match(sudoers_cmnd, sudoers_args)) {
  456: 	if (safe_cmnd)
  457: 	    free(safe_cmnd);
  458: 	safe_cmnd = estrdup(user_cmnd);
  459: 	debug_return_bool(true);
  460:     }
  461:     debug_return_bool(false);
  462: }
  463: 
  464: #ifndef SUDOERS_NAME_MATCH
  465: static bool
  466: command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args)
  467: {
  468:     struct stat sudoers_stat;
  469:     size_t dlen;
  470:     char **ap, *base, *cp;
  471:     glob_t gl;
  472:     debug_decl(command_matches_glob, SUDO_DEBUG_MATCH)
  473: 
  474:     /*
  475:      * First check to see if we can avoid the call to glob(3).
  476:      * Short circuit if there are no meta chars in the command itself
  477:      * and user_base and basename(sudoers_cmnd) don't match.
  478:      */
  479:     dlen = strlen(sudoers_cmnd);
  480:     if (sudoers_cmnd[dlen - 1] != '/') {
  481: 	if ((base = strrchr(sudoers_cmnd, '/')) != NULL) {
  482: 	    base++;
  483: 	    if (!has_meta(base) && strcmp(user_base, base) != 0)
  484: 		debug_return_bool(false);
  485: 	}
  486:     }
  487:     /*
  488:      * Return true if we find a match in the glob(3) results AND
  489:      *  a) there are no args in sudoers OR
  490:      *  b) there are no args on command line and none required by sudoers OR
  491:      *  c) there are args in sudoers and on command line and they match
  492:      * else return false.
  493:      */
  494:     if (glob(sudoers_cmnd, GLOB_NOSORT, NULL, &gl) != 0 || gl.gl_pathc == 0) {
  495: 	globfree(&gl);
  496: 	debug_return_bool(false);
  497:     }
  498:     /* For each glob match, compare basename, st_dev and st_ino. */
  499:     for (ap = gl.gl_pathv; (cp = *ap) != NULL; ap++) {
  500: 	/* If it ends in '/' it is a directory spec. */
  501: 	dlen = strlen(cp);
  502: 	if (cp[dlen - 1] == '/') {
  503: 	    if (command_matches_dir(cp, dlen))
  504: 		debug_return_bool(true);
  505: 	    continue;
  506: 	}
  507: 
  508: 	/* Only proceed if user_base and basename(cp) match */
  509: 	if ((base = strrchr(cp, '/')) != NULL)
  510: 	    base++;
  511: 	else
  512: 	    base = cp;
  513: 	if (strcmp(user_base, base) != 0 ||
  514: 	    stat(cp, &sudoers_stat) == -1)
  515: 	    continue;
  516: 	if (user_stat == NULL ||
  517: 	    (user_stat->st_dev == sudoers_stat.st_dev &&
  518: 	    user_stat->st_ino == sudoers_stat.st_ino)) {
  519: 	    efree(safe_cmnd);
  520: 	    safe_cmnd = estrdup(cp);
  521: 	    break;
  522: 	}
  523:     }
  524:     globfree(&gl);
  525:     if (cp == NULL)
  526: 	debug_return_bool(false);
  527: 
  528:     if (command_args_match(sudoers_cmnd, sudoers_args)) {
  529: 	efree(safe_cmnd);
  530: 	safe_cmnd = estrdup(user_cmnd);
  531: 	debug_return_bool(true);
  532:     }
  533:     debug_return_bool(false);
  534: }
  535: #endif /* SUDOERS_NAME_MATCH */
  536: 
  537: #ifdef SUDOERS_NAME_MATCH
  538: static bool
  539: command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const struct sudo_digest *digest)
  540: {
  541:     size_t dlen;
  542:     debug_decl(command_matches_normal, SUDO_DEBUG_MATCH)
  543: 
  544:     dlen = strlen(sudoers_cmnd);
  545: 
  546:     /* If it ends in '/' it is a directory spec. */
  547:     if (sudoers_cmnd[dlen - 1] == '/')
  548: 	debug_return_bool(command_matches_dir(sudoers_cmnd, dlen));
  549: 
  550:     if (strcmp(user_cmnd, sudoers_cmnd) == 0) {
  551: 	if (command_args_match(sudoers_cmnd, sudoers_args)) {
  552: 	    efree(safe_cmnd);
  553: 	    safe_cmnd = estrdup(sudoers_cmnd);
  554: 	    debug_return_bool(true);
  555: 	}
  556:     }
  557:     debug_return_bool(false);
  558: }
  559: #else /* !SUDOERS_NAME_MATCH */
  560: 
  561: static struct digest_function {
  562:     const char *digest_name;
  563:     const unsigned int digest_len;
  564:     void (*init)(SHA2_CTX *);
  565:     void (*update)(SHA2_CTX *, const unsigned char *, size_t);
  566:     void (*final)(unsigned char *, SHA2_CTX *);
  567: } digest_functions[] = {
  568:     {
  569: 	"SHA224",
  570: 	SHA224_DIGEST_LENGTH,
  571: 	SHA224Init,
  572: 	SHA224Update,
  573: 	SHA224Final
  574:     }, {
  575: 	"SHA256",
  576: 	SHA256_DIGEST_LENGTH,
  577: 	SHA256Init,
  578: 	SHA256Update,
  579: 	SHA256Final
  580:     }, {
  581: 	"SHA384",
  582: 	SHA384_DIGEST_LENGTH,
  583: 	SHA384Init,
  584: 	SHA384Update,
  585: 	SHA384Final
  586:     }, {
  587: 	"SHA512",
  588: 	SHA512_DIGEST_LENGTH,
  589: 	SHA512Init,
  590: 	SHA512Update,
  591: 	SHA512Final
  592:     }, {
  593: 	NULL
  594:     }
  595: };
  596: 
  597: static bool
  598: digest_matches(const char *file, const struct sudo_digest *sd)
  599: {
  600:     unsigned char file_digest[SHA512_DIGEST_LENGTH];
  601:     unsigned char sudoers_digest[SHA512_DIGEST_LENGTH];
  602:     unsigned char buf[32 * 1024];
  603:     struct digest_function *func = NULL;
  604:     size_t nread;
  605:     SHA2_CTX ctx;
  606:     FILE *fp;
  607:     unsigned int i;
  608:     debug_decl(digest_matches, SUDO_DEBUG_MATCH)
  609: 
  610:     for (i = 0; digest_functions[i].digest_name != NULL; i++) {
  611: 	if (sd->digest_type == i) {
  612: 	    func = &digest_functions[i];
  613: 	    break;
  614: 	}
  615:     }
  616:     if (func == NULL) {
  617: 	warningx(U_("unsupported digest type %d for %s"), sd->digest_type, file);
  618: 	debug_return_bool(false);
  619:     }
  620:     if (strlen(sd->digest_str) == func->digest_len * 2) {
  621: 	/* Convert the command digest from ascii hex to binary. */
  622: 	for (i = 0; i < func->digest_len; i++) {
  623: 	    if (!isxdigit((unsigned char)sd->digest_str[i + i]) ||
  624: 		!isxdigit((unsigned char)sd->digest_str[i + i + 1])) {
  625: 		goto bad_format;
  626: 	    }
  627: 	    sudoers_digest[i] = hexchar(&sd->digest_str[i + i]);
  628: 	}
  629:     } else {
  630: 	size_t len = base64_decode(sd->digest_str, sudoers_digest,
  631: 	    sizeof(sudoers_digest));
  632: 	if (len != func->digest_len)
  633: 	    goto bad_format;
  634:     }
  635: 
  636:     if ((fp = fopen(file, "r")) == NULL) {
  637: 	sudo_debug_printf(SUDO_DEBUG_INFO, "unable to open %s: %s",
  638: 	    file, strerror(errno));
  639: 	debug_return_bool(false);
  640:     }
  641: 
  642:     func->init(&ctx);
  643:     while ((nread = fread(buf, 1, sizeof(buf), fp)) != 0) {
  644: 	func->update(&ctx, buf, nread);
  645:     }
  646:     if (ferror(fp)) {
  647: 	warningx(U_("%s: read error"), file);
  648: 	fclose(fp);
  649: 	debug_return_bool(false);
  650:     }
  651:     fclose(fp);
  652:     func->final(file_digest, &ctx);
  653: 
  654:     if (memcmp(file_digest, sudoers_digest, func->digest_len) == 0)
  655: 	debug_return_bool(true);
  656:     sudo_debug_printf(SUDO_DEBUG_DIAG|SUDO_DEBUG_LINENO,
  657: 	"%s digest mismatch for %s, expecting %s",
  658: 	func->digest_name, file, sd->digest_str);
  659:     debug_return_bool(false);
  660: bad_format:
  661:     warningx(U_("digest for %s (%s) is not in %s form"), file,
  662: 	sd->digest_str, func->digest_name);
  663:     debug_return_bool(false);
  664: }
  665: 
  666: static bool
  667: command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const struct sudo_digest *digest)
  668: {
  669:     struct stat sudoers_stat;
  670:     const char *base;
  671:     size_t dlen;
  672:     debug_decl(command_matches_normal, SUDO_DEBUG_MATCH)
  673: 
  674:     /* If it ends in '/' it is a directory spec. */
  675:     dlen = strlen(sudoers_cmnd);
  676:     if (sudoers_cmnd[dlen - 1] == '/')
  677: 	debug_return_bool(command_matches_dir(sudoers_cmnd, dlen));
  678: 
  679:     /* Only proceed if user_base and basename(sudoers_cmnd) match */
  680:     if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
  681: 	base = sudoers_cmnd;
  682:     else
  683: 	base++;
  684:     if (strcmp(user_base, base) != 0 ||
  685: 	stat(sudoers_cmnd, &sudoers_stat) == -1)
  686: 	debug_return_bool(false);
  687: 
  688:     /*
  689:      * Return true if inode/device matches AND
  690:      *  a) there are no args in sudoers OR
  691:      *  b) there are no args on command line and none req by sudoers OR
  692:      *  c) there are args in sudoers and on command line and they match
  693:      *  d) there is a digest and it matches
  694:      */
  695:     if (user_stat != NULL &&
  696: 	(user_stat->st_dev != sudoers_stat.st_dev ||
  697: 	user_stat->st_ino != sudoers_stat.st_ino))
  698: 	debug_return_bool(false);
  699:     if (!command_args_match(sudoers_cmnd, sudoers_args))
  700: 	debug_return_bool(false);
  701:     if (digest != NULL && !digest_matches(sudoers_cmnd, digest)) {
  702: 	/* XXX - log functions not available but we should log very loudly */
  703: 	debug_return_bool(false);
  704:     }
  705:     efree(safe_cmnd);
  706:     safe_cmnd = estrdup(sudoers_cmnd);
  707:     debug_return_bool(true);
  708: }
  709: #endif /* SUDOERS_NAME_MATCH */
  710: 
  711: #ifdef SUDOERS_NAME_MATCH
  712: /*
  713:  * Return true if user_cmnd begins with sudoers_dir, else false.
  714:  * Note that sudoers_dir include the trailing '/'
  715:  */
  716: static bool
  717: command_matches_dir(const char *sudoers_dir, size_t dlen)
  718: {
  719:     debug_decl(command_matches_dir, SUDO_DEBUG_MATCH)
  720:     debug_return_bool(strncmp(user_cmnd, sudoers_dir, dlen) == 0);
  721: }
  722: #else /* !SUDOERS_NAME_MATCH */
  723: /*
  724:  * Return true if user_cmnd names one of the inodes in dir, else false.
  725:  */
  726: static bool
  727: command_matches_dir(const char *sudoers_dir, size_t dlen)
  728: {
  729:     struct stat sudoers_stat;
  730:     struct dirent *dent;
  731:     char buf[PATH_MAX];
  732:     DIR *dirp;
  733:     debug_decl(command_matches_dir, SUDO_DEBUG_MATCH)
  734: 
  735:     /*
  736:      * Grot through directory entries, looking for user_base.
  737:      */
  738:     dirp = opendir(sudoers_dir);
  739:     if (dirp == NULL)
  740: 	debug_return_bool(false);
  741: 
  742:     if (strlcpy(buf, sudoers_dir, sizeof(buf)) >= sizeof(buf)) {
  743: 	closedir(dirp);
  744: 	debug_return_bool(false);
  745:     }
  746:     while ((dent = readdir(dirp)) != NULL) {
  747: 	/* ignore paths > PATH_MAX (XXX - log) */
  748: 	buf[dlen] = '\0';
  749: 	if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
  750: 	    continue;
  751: 
  752: 	/* only stat if basenames are the same */
  753: 	if (strcmp(user_base, dent->d_name) != 0 ||
  754: 	    stat(buf, &sudoers_stat) == -1)
  755: 	    continue;
  756: 	if (user_stat == NULL ||
  757: 	    (user_stat->st_dev == sudoers_stat.st_dev &&
  758: 	    user_stat->st_ino == sudoers_stat.st_ino)) {
  759: 	    efree(safe_cmnd);
  760: 	    safe_cmnd = estrdup(buf);
  761: 	    break;
  762: 	}
  763:     }
  764: 
  765:     closedir(dirp);
  766:     debug_return_bool(dent != NULL);
  767: }
  768: #endif /* SUDOERS_NAME_MATCH */
  769: 
  770: /*
  771:  * Returns true if the hostname matches the pattern, else false
  772:  */
  773: bool
  774: hostname_matches(const char *shost, const char *lhost, const char *pattern)
  775: {
  776:     debug_decl(hostname_matches, SUDO_DEBUG_MATCH)
  777:     const char *host;
  778:     bool rc;
  779: 
  780:     host = strchr(pattern, '.') != NULL ? lhost : shost;
  781:     if (has_meta(pattern)) {
  782: 	rc = !fnmatch(pattern, host, FNM_CASEFOLD);
  783:     } else {
  784: 	rc = !strcasecmp(host, pattern);
  785:     }
  786:     sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
  787: 	"host %s matches sudoers pattern %s: %s",
  788: 	host, pattern, rc ? "true" : "false");
  789:     debug_return_bool(rc);
  790: }
  791: 
  792: /*
  793:  * Returns true if the user/uid from sudoers matches the specified user/uid,
  794:  * else returns false.
  795:  */
  796: bool
  797: userpw_matches(const char *sudoers_user, const char *user, const struct passwd *pw)
  798: {
  799:     const char *errstr;
  800:     uid_t uid;
  801:     bool rc;
  802:     debug_decl(userpw_matches, SUDO_DEBUG_MATCH)
  803: 
  804:     if (pw != NULL && *sudoers_user == '#') {
  805: 	uid = (uid_t) atoid(sudoers_user + 1, NULL, NULL, &errstr);
  806: 	if (errstr == NULL && uid == pw->pw_uid) {
  807: 	    rc = true;
  808: 	    goto done;
  809: 	}
  810:     }
  811:     rc = strcmp(sudoers_user, user) == 0;
  812: done:
  813:     sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
  814: 	"user %s matches sudoers user %s: %s",
  815: 	user, sudoers_user, rc ? "true" : "false");
  816:     debug_return_bool(rc);
  817: }
  818: 
  819: /*
  820:  * Returns true if the group/gid from sudoers matches the specified group/gid,
  821:  * else returns false.
  822:  */
  823: bool
  824: group_matches(const char *sudoers_group, const struct group *gr)
  825: {
  826:     const char *errstr;
  827:     gid_t gid;
  828:     bool rc;
  829:     debug_decl(group_matches, SUDO_DEBUG_MATCH)
  830: 
  831:     if (*sudoers_group == '#') {
  832: 	gid = (gid_t) atoid(sudoers_group + 1, NULL, NULL, &errstr);
  833: 	if (errstr == NULL && gid == gr->gr_gid) {
  834: 	    rc = true;
  835: 	    goto done;
  836: 	}
  837:     }
  838:     rc = strcmp(gr->gr_name, sudoers_group) == 0;
  839: done:
  840:     sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
  841: 	"group %s matches sudoers group %s: %s",
  842: 	gr->gr_name, sudoers_group, rc ? "true" : "false");
  843:     debug_return_bool(rc);
  844: }
  845: 
  846: /*
  847:  * Returns true if the given user belongs to the named group,
  848:  * else returns false.
  849:  */
  850: bool
  851: usergr_matches(const char *group, const char *user, const struct passwd *pw)
  852: {
  853:     int matched = false;
  854:     struct passwd *pw0 = NULL;
  855:     debug_decl(usergr_matches, SUDO_DEBUG_MATCH)
  856: 
  857:     /* make sure we have a valid usergroup, sudo style */
  858:     if (*group++ != '%') {
  859: 	sudo_debug_printf(SUDO_DEBUG_DIAG, "user group %s has no leading '%%'",
  860: 	    group);
  861: 	goto done;
  862:     }
  863: 
  864:     if (*group == ':' && def_group_plugin) {
  865: 	matched = group_plugin_query(user, group + 1, pw);
  866: 	goto done;
  867:     }
  868: 
  869:     /* look up user's primary gid in the passwd file */
  870:     if (pw == NULL) {
  871: 	if ((pw0 = sudo_getpwnam(user)) == NULL) {
  872: 	    sudo_debug_printf(SUDO_DEBUG_DIAG, "unable to find %s in passwd db",
  873: 		user);
  874: 	    goto done;
  875: 	}
  876: 	pw = pw0;
  877:     }
  878: 
  879:     if (user_in_group(pw, group)) {
  880: 	matched = true;
  881: 	goto done;
  882:     }
  883: 
  884:     /* not a Unix group, could be an external group */
  885:     if (def_group_plugin && group_plugin_query(user, group, pw)) {
  886: 	matched = true;
  887: 	goto done;
  888:     }
  889: 
  890: done:
  891:     if (pw0 != NULL)
  892: 	sudo_pw_delref(pw0);
  893: 
  894:     sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
  895: 	"user %s matches group %s: %s", user, group, matched ? "true" : "false");
  896:     debug_return_bool(matched);
  897: }
  898: 
  899: #ifdef HAVE_INNETGR
  900: /*
  901:  * Get NIS-style domain name and return a malloc()ed copy or NULL if none.
  902:  */
  903: static char *
  904: sudo_getdomainname(void)
  905: {
  906:     char *domain = NULL;
  907: #ifdef HAVE_GETDOMAINNAME
  908:     char *buf, *cp;
  909: 
  910:     buf = emalloc(HOST_NAME_MAX + 1);
  911:     if (getdomainname(buf, HOST_NAME_MAX + 1) == 0 && *buf != '\0') {
  912: 	domain = buf;
  913: 	for (cp = buf; *cp != '\0'; cp++) {
  914: 	    /* Check for illegal characters, Linux may use "(none)". */
  915: 	    if (*cp == '(' || *cp == ')' || *cp == ',' || *cp == ' ') {
  916: 		domain = NULL;
  917: 		break;
  918: 	    }
  919: 	}
  920:     }
  921:     if (domain == NULL)
  922: 	efree(buf);
  923: #endif /* HAVE_GETDOMAINNAME */
  924:     return domain;
  925: }
  926: #endif /* HAVE_INNETGR */
  927: 
  928: /*
  929:  * Returns true if "host" and "user" belong to the netgroup "netgr",
  930:  * else return false.  Either of "lhost", "shost" or "user" may be NULL
  931:  * in which case that argument is not checked...
  932:  */
  933: bool
  934: netgr_matches(const char *netgr, const char *lhost, const char *shost, const char *user)
  935: {
  936: #ifdef HAVE_INNETGR
  937:     static char *domain;
  938:     static int initialized;
  939: #endif
  940:     bool rc = false;
  941:     debug_decl(netgr_matches, SUDO_DEBUG_MATCH)
  942: 
  943:     if (!def_use_netgroups) {
  944: 	sudo_debug_printf(SUDO_DEBUG_INFO, "netgroups are disabled");
  945: 	debug_return_bool(false);
  946:     }
  947: 
  948: #ifdef HAVE_INNETGR
  949:     /* make sure we have a valid netgroup, sudo style */
  950:     if (*netgr++ != '+') {
  951: 	sudo_debug_printf(SUDO_DEBUG_DIAG, "netgroup %s has no leading '+'",
  952: 	    netgr);
  953: 	debug_return_bool(false);
  954:     }
  955: 
  956:     /* get the domain name (if any) */
  957:     if (!initialized) {
  958: 	domain = sudo_getdomainname();
  959: 	initialized = 1;
  960:     }
  961: 
  962:     if (innetgr(netgr, lhost, user, domain))
  963: 	rc = true;
  964:     else if (lhost != shost && innetgr(netgr, shost, user, domain))
  965: 	rc = true;
  966: #endif /* HAVE_INNETGR */
  967: 
  968:     sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
  969: 	"netgroup %s matches (%s|%s, %s, %s): %s", netgr, lhost ? lhost : "",
  970: 	shost ? shost : "", user ? user : "", domain ? domain : "",
  971: 	rc ? "true" : "false");
  972: 
  973:     debug_return_bool(rc);
  974: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>