File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / plugins / sudoers / match.c
Revision 1.1.1.5 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Oct 14 07:56:34 2013 UTC (10 years, 8 months ago) by misho
Branches: sudo, MAIN
CVS tags: v1_8_8p0, v1_8_8, HEAD
v 1.8.8

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

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