Annotation of src/usr.bin/sudo/parse.c, Revision 1.1
1.1 ! millert 1: /*
! 2: * Copyright (c) 1996, 1998, 1999 Todd C. Miller <Todd.Miller@courtesan.com>
! 3: * All rights reserved.
! 4: *
! 5: * This code is derived from software contributed by Chris Jepeway
! 6: * <jepeway@cs.utk.edu>.
! 7: *
! 8: * Redistribution and use in source and binary forms, with or without
! 9: * modification, are permitted provided that the following conditions
! 10: * are met:
! 11: *
! 12: * 1. Redistributions of source code must retain the above copyright
! 13: * notice, this list of conditions and the following disclaimer.
! 14: *
! 15: * 2. Redistributions in binary form must reproduce the above copyright
! 16: * notice, this list of conditions and the following disclaimer in the
! 17: * documentation and/or other materials provided with the distribution.
! 18: *
! 19: * 3. The name of the author may not be used to endorse or promote products
! 20: * derived from this software without specific prior written permission.
! 21: *
! 22: * 4. Products derived from this software may not be called "Sudo" nor
! 23: * may "Sudo" appear in their names without specific prior written
! 24: * permission from the author.
! 25: *
! 26: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
! 27: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
! 28: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
! 29: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
! 30: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
! 31: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
! 32: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
! 33: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
! 34: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
! 35: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
! 36: */
! 37:
! 38: #include "config.h"
! 39:
! 40: #include <stdio.h>
! 41: #ifdef STDC_HEADERS
! 42: # include <stdlib.h>
! 43: #endif /* STDC_HEADERS */
! 44: #ifdef HAVE_UNISTD_H
! 45: # include <unistd.h>
! 46: #endif /* HAVE_UNISTD_H */
! 47: #ifdef HAVE_STRING_H
! 48: # include <string.h>
! 49: #endif /* HAVE_STRING_H */
! 50: #ifdef HAVE_STRINGS_H
! 51: # include <strings.h>
! 52: #endif /* HAVE_STRINGS_H */
! 53: #if defined(HAVE_FNMATCH) && defined(HAVE_FNMATCH_H)
! 54: # include <fnmatch.h>
! 55: #endif /* HAVE_FNMATCH_H */
! 56: #ifdef HAVE_NETGROUP_H
! 57: # include <netgroup.h>
! 58: #endif /* HAVE_NETGROUP_H */
! 59: #include <ctype.h>
! 60: #include <pwd.h>
! 61: #include <grp.h>
! 62: #include <sys/param.h>
! 63: #include <sys/types.h>
! 64: #include <netinet/in.h>
! 65: #include <arpa/inet.h>
! 66: #include <netdb.h>
! 67: #include <sys/stat.h>
! 68: #if 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: # if HAVE_SYS_NDIR_H
! 75: # include <sys/ndir.h>
! 76: # endif
! 77: # if HAVE_SYS_DIR_H
! 78: # include <sys/dir.h>
! 79: # endif
! 80: # if HAVE_NDIR_H
! 81: # include <ndir.h>
! 82: # endif
! 83: #endif
! 84:
! 85: #include "sudo.h"
! 86: #include "parse.h"
! 87: #include "interfaces.h"
! 88:
! 89: #ifndef HAVE_FNMATCH
! 90: # include "emul/fnmatch.h"
! 91: #endif /* HAVE_FNMATCH */
! 92:
! 93: #ifndef lint
! 94: static const char rcsid[] = "$Sudo: parse.c,v 1.121 1999/08/28 10:00:22 millert Exp $";
! 95: #endif /* lint */
! 96:
! 97: /*
! 98: * Globals
! 99: */
! 100: int parse_error = FALSE;
! 101: extern FILE *yyin, *yyout;
! 102:
! 103: /*
! 104: * Prototypes
! 105: */
! 106: static int has_meta __P((char *));
! 107: void init_parser __P((void));
! 108:
! 109: /*
! 110: * Look up the user in the sudoers file and check to see if they are
! 111: * allowed to run the specified command on this host as the target user.
! 112: */
! 113: int
! 114: sudoers_lookup(check_cmnd)
! 115: int check_cmnd;
! 116: {
! 117: int error;
! 118:
! 119: /* Become sudoers file owner */
! 120: set_perms(PERM_SUDOERS, 0);
! 121:
! 122: /* We opened _PATH_SUDOERS in check_sudoers() so just rewind it. */
! 123: rewind(sudoers_fp);
! 124: yyin = sudoers_fp;
! 125: yyout = stdout;
! 126:
! 127: /* Allocate space for data structures in the parser. */
! 128: init_parser();
! 129:
! 130: /* Need to be root while stat'ing things in the parser. */
! 131: set_perms(PERM_ROOT, 0);
! 132: error = yyparse();
! 133:
! 134: /* Close the sudoers file now that we are done with it. */
! 135: (void) fclose(sudoers_fp);
! 136: sudoers_fp = NULL;
! 137:
! 138: if (error || parse_error)
! 139: return(VALIDATE_ERROR);
! 140:
! 141: /*
! 142: * Assume the worst. If the stack is empty the user was
! 143: * not mentioned at all.
! 144: */
! 145: error = VALIDATE_NOT_OK;
! 146: if (check_cmnd == TRUE) {
! 147: error |= FLAG_NO_HOST;
! 148: if (!top)
! 149: error |= FLAG_NO_USER;
! 150: } else
! 151: error |= FLAG_NO_CHECK;
! 152:
! 153: /*
! 154: * Only check the actual command if the check_cmnd flag is set.
! 155: * It is not set for the "validate" and "list" pseudo-commands.
! 156: * Always check the host and user.
! 157: */
! 158: if (check_cmnd == FALSE)
! 159: while (top) {
! 160: if (host_matches == TRUE) {
! 161: /* User may always validate or list on allowed hosts */
! 162: if (no_passwd == TRUE)
! 163: return(VALIDATE_OK | FLAG_NOPASS);
! 164: else
! 165: return(VALIDATE_OK);
! 166: }
! 167: top--;
! 168: }
! 169: else
! 170: while (top) {
! 171: if (host_matches == TRUE) {
! 172: error &= ~FLAG_NO_HOST;
! 173: if (runas_matches == TRUE) {
! 174: if (cmnd_matches == TRUE) {
! 175: /*
! 176: * User was granted access to cmnd on host.
! 177: * If no passwd required return as such.
! 178: */
! 179: if (no_passwd == TRUE)
! 180: return(VALIDATE_OK | FLAG_NOPASS);
! 181: else
! 182: return(VALIDATE_OK);
! 183: } else if (cmnd_matches == FALSE) {
! 184: /*
! 185: * User was explicitly denied access to cmnd on host.
! 186: */
! 187: if (no_passwd == TRUE)
! 188: return(VALIDATE_NOT_OK | FLAG_NOPASS);
! 189: else
! 190: return(VALIDATE_NOT_OK);
! 191: }
! 192: }
! 193: }
! 194: top--;
! 195: }
! 196:
! 197: /*
! 198: * The user was not explicitly granted nor denied access.
! 199: */
! 200: return(error);
! 201: }
! 202:
! 203: /*
! 204: * If path doesn't end in /, return TRUE iff cmnd & path name the same inode;
! 205: * otherwise, return TRUE if cmnd names one of the inodes in path.
! 206: */
! 207: int
! 208: command_matches(cmnd, cmnd_args, path, sudoers_args)
! 209: char *cmnd;
! 210: char *cmnd_args;
! 211: char *path;
! 212: char *sudoers_args;
! 213: {
! 214: int plen;
! 215: static struct stat cst;
! 216: struct stat pst;
! 217: DIR *dirp;
! 218: struct dirent *dent;
! 219: char buf[MAXPATHLEN];
! 220: static char *cmnd_base;
! 221:
! 222: /* Don't bother with pseudo commands like "validate" */
! 223: if (strchr(cmnd, '/') == NULL)
! 224: return(FALSE);
! 225:
! 226: plen = strlen(path);
! 227:
! 228: /* Only need to stat cmnd once since it never changes */
! 229: if (cst.st_dev == 0) {
! 230: if (stat(cmnd, &cst) == -1)
! 231: return(FALSE);
! 232: if ((cmnd_base = strrchr(cmnd, '/')) == NULL)
! 233: cmnd_base = cmnd;
! 234: else
! 235: cmnd_base++;
! 236: }
! 237:
! 238: /*
! 239: * If the pathname has meta characters in it use fnmatch(3)
! 240: * to do the matching
! 241: */
! 242: if (has_meta(path)) {
! 243: /*
! 244: * Return true if fnmatch(3) succeeds AND
! 245: * a) there are no args in sudoers OR
! 246: * b) there are no args on command line and none required by sudoers OR
! 247: * c) there are args in sudoers and on command line and they match
! 248: * else return false.
! 249: */
! 250: if (fnmatch(path, cmnd, FNM_PATHNAME) != 0)
! 251: return(FALSE);
! 252: if (!sudoers_args ||
! 253: (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
! 254: (sudoers_args && fnmatch(sudoers_args, cmnd_args ? cmnd_args : "",
! 255: 0) == 0)) {
! 256: if (safe_cmnd)
! 257: free(safe_cmnd);
! 258: safe_cmnd = estrdup(user_cmnd);
! 259: return(TRUE);
! 260: } else
! 261: return(FALSE);
! 262: } else {
! 263: /*
! 264: * No meta characters
! 265: * Check to make sure this is not a directory spec (doesn't end in '/')
! 266: */
! 267: if (path[plen - 1] != '/') {
! 268: char *p;
! 269:
! 270: /* Only proceed if the basenames of cmnd and path are the same */
! 271: if ((p = strrchr(path, '/')) == NULL)
! 272: p = path;
! 273: else
! 274: p++;
! 275: if (strcmp(cmnd_base, p) != 0 || stat(path, &pst) == -1)
! 276: return(FALSE);
! 277:
! 278: /*
! 279: * Return true if inode/device matches AND
! 280: * a) there are no args in sudoers OR
! 281: * b) there are no args on command line and none req by sudoers OR
! 282: * c) there are args in sudoers and on command line and they match
! 283: */
! 284: if (cst.st_dev != pst.st_dev || cst.st_ino != pst.st_ino)
! 285: return(FALSE);
! 286: if (!sudoers_args ||
! 287: (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
! 288: (sudoers_args &&
! 289: fnmatch(sudoers_args, cmnd_args ? cmnd_args : "", 0) == 0)) {
! 290: if (safe_cmnd)
! 291: free(safe_cmnd);
! 292: safe_cmnd = estrdup(path);
! 293: return(TRUE);
! 294: } else
! 295: return(FALSE);
! 296: }
! 297:
! 298: /*
! 299: * Grot through path's directory entries, looking for cmnd.
! 300: */
! 301: dirp = opendir(path);
! 302: if (dirp == NULL)
! 303: return(FALSE);
! 304:
! 305: while ((dent = readdir(dirp)) != NULL) {
! 306: /* ignore paths > MAXPATHLEN (XXX - log) */
! 307: if (plen + NAMLEN(dent) >= sizeof(buf))
! 308: continue;
! 309: strcpy(buf, path);
! 310: strcat(buf, dent->d_name);
! 311:
! 312: /* only stat if basenames are the same */
! 313: if (strcmp(cmnd_base, dent->d_name) != 0 || stat(buf, &pst) == -1)
! 314: continue;
! 315: if (cst.st_dev == pst.st_dev && cst.st_ino == pst.st_ino) {
! 316: if (safe_cmnd)
! 317: free(safe_cmnd);
! 318: safe_cmnd = estrdup(buf);
! 319: break;
! 320: }
! 321: }
! 322:
! 323: closedir(dirp);
! 324: return(dent != NULL);
! 325: }
! 326: }
! 327:
! 328: /*
! 329: * Returns TRUE if "n" is one of our ip addresses or if
! 330: * "n" is a network that we are on, else returns FALSE.
! 331: */
! 332: int
! 333: addr_matches(n)
! 334: char *n;
! 335: {
! 336: int i;
! 337: char *m;
! 338: struct in_addr addr, mask;
! 339:
! 340: /* If there's an explicit netmask, use it. */
! 341: if ((m = strchr(n, '/'))) {
! 342: *m++ = '\0';
! 343: addr.s_addr = inet_addr(n);
! 344: if (strchr(m, '.'))
! 345: mask.s_addr = inet_addr(m);
! 346: else
! 347: mask.s_addr = (1 << atoi(m)) - 1; /* XXX - better way? */
! 348: *(m - 1) = '/';
! 349:
! 350: for (i = 0; i < num_interfaces; i++)
! 351: if ((interfaces[i].addr.s_addr & mask.s_addr) == addr.s_addr)
! 352: return(TRUE);
! 353: } else {
! 354: addr.s_addr = inet_addr(n);
! 355:
! 356: for (i = 0; i < num_interfaces; i++)
! 357: if (interfaces[i].addr.s_addr == addr.s_addr ||
! 358: (interfaces[i].addr.s_addr & interfaces[i].netmask.s_addr)
! 359: == addr.s_addr)
! 360: return(TRUE);
! 361: }
! 362:
! 363: return(FALSE);
! 364: }
! 365:
! 366: /*
! 367: * Returns TRUE if the given user belongs to the named group,
! 368: * else returns FALSE.
! 369: */
! 370: int
! 371: usergr_matches(group, user)
! 372: char *group;
! 373: char *user;
! 374: {
! 375: struct group *grp;
! 376: struct passwd *pw;
! 377: char **cur;
! 378:
! 379: /* make sure we have a valid usergroup, sudo style */
! 380: if (*group++ != '%')
! 381: return(FALSE);
! 382:
! 383: if ((grp = getgrnam(group)) == NULL)
! 384: return(FALSE);
! 385:
! 386: /*
! 387: * Check against user's real gid as well as group's user list
! 388: */
! 389: if ((pw = getpwnam(user)) == NULL)
! 390: return(FALSE);
! 391:
! 392: if (grp->gr_gid == pw->pw_gid)
! 393: return(TRUE);
! 394:
! 395: for (cur=grp->gr_mem; *cur; cur++) {
! 396: if (strcmp(*cur, user) == 0)
! 397: return(TRUE);
! 398: }
! 399:
! 400: return(FALSE);
! 401: }
! 402:
! 403: /*
! 404: * Returns TRUE if "host" and "user" belong to the netgroup "netgr",
! 405: * else return FALSE. Either of "host" or "user" may be NULL
! 406: * in which case that argument is not checked...
! 407: */
! 408: int
! 409: netgr_matches(netgr, host, user)
! 410: char *netgr;
! 411: char *host;
! 412: char *user;
! 413: {
! 414: #ifdef HAVE_GETDOMAINNAME
! 415: static char *domain = (char *) -1;
! 416: #else
! 417: static char *domain = NULL;
! 418: #endif /* HAVE_GETDOMAINNAME */
! 419:
! 420: /* make sure we have a valid netgroup, sudo style */
! 421: if (*netgr++ != '+')
! 422: return(FALSE);
! 423:
! 424: #ifdef HAVE_GETDOMAINNAME
! 425: /* get the domain name (if any) */
! 426: if (domain == (char *) -1) {
! 427: domain = (char *) emalloc(MAXHOSTNAMELEN);
! 428: if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') {
! 429: free(domain);
! 430: domain = NULL;
! 431: }
! 432: }
! 433: #endif /* HAVE_GETDOMAINNAME */
! 434:
! 435: #ifdef HAVE_INNETGR
! 436: return(innetgr(netgr, host, user, domain));
! 437: #else
! 438: return(FALSE);
! 439: #endif /* HAVE_INNETGR */
! 440: }
! 441:
! 442: /*
! 443: * Returns TRUE if "s" has shell meta characters in it,
! 444: * else returns FALSE.
! 445: */
! 446: static int
! 447: has_meta(s)
! 448: char *s;
! 449: {
! 450: register char *t;
! 451:
! 452: for (t = s; *t; t++) {
! 453: if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']')
! 454: return(TRUE);
! 455: }
! 456: return(FALSE);
! 457: }