Annotation of src/usr.bin/sudo/parse.c, Revision 1.12
1.1 millert 1: /*
1.12 ! millert 2: * Copyright (c) 1996, 1998-2004 Todd C. Miller <Todd.Miller@courtesan.com>
1.1 millert 3: *
1.12 ! millert 4: * Permission to use, copy, modify, and distribute this software for any
! 5: * purpose with or without fee is hereby granted, provided that the above
! 6: * copyright notice and this permission notice appear in all copies.
1.1 millert 7: *
1.12 ! millert 8: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
! 9: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
! 10: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
! 11: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
! 12: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
! 13: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
! 14: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.1 millert 15: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
16: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.11 millert 17: *
18: * Sponsored in part by the Defense Advanced Research Projects
19: * Agency (DARPA) and Air Force Research Laboratory, Air Force
20: * Materiel Command, USAF, under agreement number F39502-99-1-0512.
1.1 millert 21: */
22:
23: #include "config.h"
24:
1.6 millert 25: #include <sys/types.h>
26: #include <sys/param.h>
27: #include <sys/stat.h>
1.1 millert 28: #include <stdio.h>
29: #ifdef STDC_HEADERS
30: # include <stdlib.h>
1.6 millert 31: # include <stddef.h>
32: #else
33: # ifdef HAVE_STDLIB_H
34: # include <stdlib.h>
35: # endif
1.1 millert 36: #endif /* STDC_HEADERS */
1.6 millert 37: #ifdef HAVE_STRING_H
38: # include <string.h>
39: #else
40: # ifdef HAVE_STRINGS_H
41: # include <strings.h>
42: # endif
43: #endif /* HAVE_STRING_H */
1.1 millert 44: #ifdef HAVE_UNISTD_H
45: # include <unistd.h>
46: #endif /* HAVE_UNISTD_H */
1.2 millert 47: #ifdef HAVE_FNMATCH
1.1 millert 48: # include <fnmatch.h>
1.10 millert 49: #endif /* HAVE_FNMATCH */
1.1 millert 50: #ifdef HAVE_NETGROUP_H
51: # include <netgroup.h>
52: #endif /* HAVE_NETGROUP_H */
53: #include <ctype.h>
54: #include <pwd.h>
55: #include <grp.h>
56: #include <netinet/in.h>
57: #include <arpa/inet.h>
58: #include <netdb.h>
1.6 millert 59: #ifdef HAVE_DIRENT_H
1.1 millert 60: # include <dirent.h>
61: # define NAMLEN(dirent) strlen((dirent)->d_name)
62: #else
63: # define dirent direct
64: # define NAMLEN(dirent) (dirent)->d_namlen
1.6 millert 65: # ifdef HAVE_SYS_NDIR_H
1.1 millert 66: # include <sys/ndir.h>
67: # endif
1.6 millert 68: # ifdef HAVE_SYS_DIR_H
1.1 millert 69: # include <sys/dir.h>
70: # endif
1.6 millert 71: # ifdef HAVE_NDIR_H
1.1 millert 72: # include <ndir.h>
73: # endif
74: #endif
75:
76: #include "sudo.h"
77: #include "parse.h"
78: #include "interfaces.h"
79:
80: #ifndef HAVE_FNMATCH
81: # include "emul/fnmatch.h"
82: #endif /* HAVE_FNMATCH */
83:
84: #ifndef lint
1.12 ! millert 85: static const char rcsid[] = "$Sudo: parse.c,v 1.161 2004/08/24 18:01:13 millert Exp $";
1.1 millert 86: #endif /* lint */
87:
88: /*
89: * Globals
90: */
91: int parse_error = FALSE;
1.3 millert 92: extern int keepall;
1.1 millert 93: extern FILE *yyin, *yyout;
94:
95: /*
96: * Prototypes
97: */
98: static int has_meta __P((char *));
99: void init_parser __P((void));
100:
101: /*
102: * Look up the user in the sudoers file and check to see if they are
103: * allowed to run the specified command on this host as the target user.
104: */
105: int
1.6 millert 106: sudoers_lookup(pwflag)
107: int pwflag;
1.1 millert 108: {
1.12 ! millert 109: int error, nopass;
! 110: enum def_tupple pwcheck;
1.1 millert 111:
112: /* We opened _PATH_SUDOERS in check_sudoers() so just rewind it. */
113: rewind(sudoers_fp);
114: yyin = sudoers_fp;
115: yyout = stdout;
116:
117: /* Allocate space for data structures in the parser. */
118: init_parser();
119:
1.12 ! millert 120: /* If pwcheck *could* be "all" or "any", keep more state. */
1.6 millert 121: if (pwflag > 0)
1.3 millert 122: keepall = TRUE;
123:
1.12 ! millert 124: /* Need to be runas user while stat'ing things in the parser. */
! 125: set_perms(PERM_RUNAS);
1.1 millert 126: error = yyparse();
127:
128: /* Close the sudoers file now that we are done with it. */
129: (void) fclose(sudoers_fp);
130: sudoers_fp = NULL;
131:
1.12 ! millert 132: if (error || parse_error) {
! 133: set_perms(PERM_ROOT);
1.1 millert 134: return(VALIDATE_ERROR);
1.12 ! millert 135: }
1.1 millert 136:
137: /*
1.5 millert 138: * The pw options may have changed during sudoers parse so we
139: * wait until now to set this.
140: */
1.6 millert 141: if (pwflag)
1.12 ! millert 142: pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple;
1.6 millert 143: else
144: pwcheck = 0;
1.5 millert 145:
146: /*
1.1 millert 147: * Assume the worst. If the stack is empty the user was
148: * not mentioned at all.
149: */
1.12 ! millert 150: if (def_authenticate)
1.2 millert 151: error = VALIDATE_NOT_OK;
152: else
153: error = VALIDATE_NOT_OK | FLAG_NOPASS;
1.5 millert 154: if (pwcheck) {
1.12 ! millert 155: SET(error, FLAG_NO_CHECK);
1.3 millert 156: } else {
1.12 ! millert 157: SET(error, FLAG_NO_HOST);
1.1 millert 158: if (!top)
1.12 ! millert 159: SET(error, FLAG_NO_USER);
1.3 millert 160: }
1.1 millert 161:
162: /*
1.12 ! millert 163: * Only check the actual command if pwflag is not set.
1.3 millert 164: * It is set for the "validate", "list" and "kill" pseudo-commands.
1.1 millert 165: * Always check the host and user.
166: */
1.6 millert 167: nopass = -1;
1.12 ! millert 168: if (pwflag) {
1.6 millert 169: int found;
1.3 millert 170:
1.12 ! millert 171: if (pwcheck == always && def_authenticate)
! 172: nopass = FLAG_CHECK_USER;
! 173: else if (pwcheck == never || !def_authenticate)
1.3 millert 174: nopass = FLAG_NOPASS;
175: found = 0;
1.1 millert 176: while (top) {
177: if (host_matches == TRUE) {
1.3 millert 178: found = 1;
1.12 ! millert 179: if (pwcheck == any && no_passwd == TRUE)
1.3 millert 180: nopass = FLAG_NOPASS;
1.12 ! millert 181: else if (pwcheck == all && nopass != 0)
1.3 millert 182: nopass = (no_passwd == TRUE) ? FLAG_NOPASS : 0;
1.1 millert 183: }
184: top--;
185: }
1.3 millert 186: if (found) {
1.12 ! millert 187: set_perms(PERM_ROOT);
1.3 millert 188: if (nopass == -1)
189: nopass = 0;
190: return(VALIDATE_OK | nopass);
191: }
192: } else {
1.1 millert 193: while (top) {
194: if (host_matches == TRUE) {
1.12 ! millert 195: CLR(error, FLAG_NO_HOST);
! 196: if (runas_matches == TRUE && cmnd_matches == TRUE) {
! 197: /*
! 198: * User was granted access to cmnd on host as user.
! 199: */
! 200: set_perms(PERM_ROOT);
! 201: return(VALIDATE_OK |
! 202: (no_passwd == TRUE ? FLAG_NOPASS : 0) |
! 203: (no_execve == TRUE ? FLAG_NOEXEC : 0));
! 204: } else if ((runas_matches == TRUE && cmnd_matches == FALSE) ||
! 205: (runas_matches == FALSE && cmnd_matches == TRUE)) {
! 206: /*
! 207: * User was explicitly denied access to cmnd on host.
! 208: */
! 209: set_perms(PERM_ROOT);
! 210: return(VALIDATE_NOT_OK |
! 211: (no_passwd == TRUE ? FLAG_NOPASS : 0) |
! 212: (no_execve == TRUE ? FLAG_NOEXEC : 0));
1.1 millert 213: }
214: }
215: top--;
216: }
1.3 millert 217: }
1.12 ! millert 218: set_perms(PERM_ROOT);
1.1 millert 219:
220: /*
1.12 ! millert 221: * The user was neither explicitly granted nor denied access.
1.1 millert 222: */
1.6 millert 223: if (nopass == -1)
224: nopass = 0;
225: return(error | nopass);
1.1 millert 226: }
227:
228: /*
229: * If path doesn't end in /, return TRUE iff cmnd & path name the same inode;
1.12 ! millert 230: * otherwise, return TRUE if user_cmnd names one of the inodes in path.
1.1 millert 231: */
232: int
1.12 ! millert 233: command_matches(sudoers_cmnd, sudoers_args)
! 234: char *sudoers_cmnd;
1.1 millert 235: char *sudoers_args;
236: {
1.12 ! millert 237: struct stat sudoers_stat;
! 238: struct dirent *dent;
! 239: char buf[PATH_MAX];
1.1 millert 240: DIR *dirp;
241:
1.12 ! millert 242: /* Check for pseudo-commands */
! 243: if (strchr(user_cmnd, '/') == NULL) {
! 244: /*
! 245: * Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND
! 246: * a) there are no args in sudoers OR
! 247: * b) there are no args on command line and none req by sudoers OR
! 248: * c) there are args in sudoers and on command line and they match
! 249: */
! 250: if (strcmp(sudoers_cmnd, "sudoedit") != 0 ||
! 251: strcmp(user_cmnd, "sudoedit") != 0)
! 252: return(FALSE);
! 253: if (!sudoers_args ||
! 254: (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
! 255: (sudoers_args &&
! 256: fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
! 257: if (safe_cmnd)
! 258: free(safe_cmnd);
! 259: safe_cmnd = estrdup(sudoers_cmnd);
! 260: return(TRUE);
! 261: } else
1.1 millert 262: return(FALSE);
263: }
264:
265: /*
1.12 ! millert 266: * If sudoers_cmnd has meta characters in it, use fnmatch(3)
! 267: * to do the matching.
1.1 millert 268: */
1.12 ! millert 269: if (has_meta(sudoers_cmnd)) {
1.1 millert 270: /*
271: * Return true if fnmatch(3) succeeds AND
272: * a) there are no args in sudoers OR
273: * b) there are no args on command line and none required by sudoers OR
274: * c) there are args in sudoers and on command line and they match
275: * else return false.
276: */
1.12 ! millert 277: if (fnmatch(sudoers_cmnd, user_cmnd, FNM_PATHNAME) != 0)
1.1 millert 278: return(FALSE);
279: if (!sudoers_args ||
1.12 ! millert 280: (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
! 281: (sudoers_args &&
! 282: fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
1.1 millert 283: if (safe_cmnd)
284: free(safe_cmnd);
285: safe_cmnd = estrdup(user_cmnd);
286: return(TRUE);
287: } else
288: return(FALSE);
289: } else {
1.12 ! millert 290: size_t dlen = strlen(sudoers_cmnd);
! 291:
1.1 millert 292: /*
293: * No meta characters
294: * Check to make sure this is not a directory spec (doesn't end in '/')
295: */
1.12 ! millert 296: if (sudoers_cmnd[dlen - 1] != '/') {
! 297: char *base;
1.1 millert 298:
1.12 ! millert 299: /* Only proceed if user_base and basename(sudoers_cmnd) match */
! 300: if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
! 301: base = sudoers_cmnd;
1.1 millert 302: else
1.12 ! millert 303: base++;
! 304: if (strcmp(user_base, base) != 0 ||
! 305: stat(sudoers_cmnd, &sudoers_stat) == -1)
1.1 millert 306: return(FALSE);
307:
308: /*
309: * Return true if inode/device matches AND
310: * a) there are no args in sudoers OR
311: * b) there are no args on command line and none req by sudoers OR
312: * c) there are args in sudoers and on command line and they match
313: */
1.12 ! millert 314: if (user_stat->st_dev != sudoers_stat.st_dev ||
! 315: user_stat->st_ino != sudoers_stat.st_ino)
1.1 millert 316: return(FALSE);
317: if (!sudoers_args ||
1.12 ! millert 318: (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
1.1 millert 319: (sudoers_args &&
1.12 ! millert 320: fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
1.1 millert 321: if (safe_cmnd)
322: free(safe_cmnd);
1.12 ! millert 323: safe_cmnd = estrdup(sudoers_cmnd);
1.1 millert 324: return(TRUE);
325: } else
326: return(FALSE);
327: }
328:
329: /*
1.12 ! millert 330: * Grot through sudoers_cmnd's directory entries, looking for user_base.
1.1 millert 331: */
1.12 ! millert 332: dirp = opendir(sudoers_cmnd);
1.1 millert 333: if (dirp == NULL)
334: return(FALSE);
335:
1.12 ! millert 336: if (strlcpy(buf, sudoers_cmnd, sizeof(buf)) >= sizeof(buf))
! 337: return(FALSE);
1.1 millert 338: while ((dent = readdir(dirp)) != NULL) {
1.12 ! millert 339: /* ignore paths > PATH_MAX (XXX - log) */
! 340: buf[dlen] = '\0';
! 341: if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
1.1 millert 342: continue;
343:
344: /* only stat if basenames are the same */
1.12 ! millert 345: if (strcmp(user_base, dent->d_name) != 0 ||
! 346: stat(buf, &sudoers_stat) == -1)
1.1 millert 347: continue;
1.12 ! millert 348: if (user_stat->st_dev == sudoers_stat.st_dev &&
! 349: user_stat->st_ino == sudoers_stat.st_ino) {
1.1 millert 350: if (safe_cmnd)
351: free(safe_cmnd);
352: safe_cmnd = estrdup(buf);
353: break;
354: }
355: }
356:
357: closedir(dirp);
358: return(dent != NULL);
359: }
360: }
361:
362: /*
363: * Returns TRUE if "n" is one of our ip addresses or if
364: * "n" is a network that we are on, else returns FALSE.
365: */
366: int
367: addr_matches(n)
368: char *n;
369: {
370: int i;
371: char *m;
372: struct in_addr addr, mask;
373:
374: /* If there's an explicit netmask, use it. */
375: if ((m = strchr(n, '/'))) {
376: *m++ = '\0';
377: addr.s_addr = inet_addr(n);
378: if (strchr(m, '.'))
379: mask.s_addr = inet_addr(m);
1.6 millert 380: else {
381: i = 32 - atoi(m);
382: mask.s_addr = 0xffffffff;
383: mask.s_addr >>= i;
384: mask.s_addr <<= i;
385: mask.s_addr = htonl(mask.s_addr);
386: }
1.1 millert 387: *(m - 1) = '/';
388:
389: for (i = 0; i < num_interfaces; i++)
390: if ((interfaces[i].addr.s_addr & mask.s_addr) == addr.s_addr)
391: return(TRUE);
392: } else {
393: addr.s_addr = inet_addr(n);
394:
395: for (i = 0; i < num_interfaces; i++)
396: if (interfaces[i].addr.s_addr == addr.s_addr ||
397: (interfaces[i].addr.s_addr & interfaces[i].netmask.s_addr)
398: == addr.s_addr)
399: return(TRUE);
400: }
401:
402: return(FALSE);
403: }
404:
405: /*
1.4 millert 406: * Returns 0 if the hostname matches the pattern and non-zero otherwise.
407: */
408: int
409: hostname_matches(shost, lhost, pattern)
410: char *shost;
411: char *lhost;
412: char *pattern;
413: {
414: if (has_meta(pattern)) {
415: if (strchr(pattern, '.'))
416: return(fnmatch(pattern, lhost, FNM_CASEFOLD));
417: else
418: return(fnmatch(pattern, shost, FNM_CASEFOLD));
419: } else {
420: if (strchr(pattern, '.'))
421: return(strcasecmp(lhost, pattern));
422: else
423: return(strcasecmp(shost, pattern));
424: }
425: }
426:
427: /*
1.12 ! millert 428: * Returns TRUE if the user/uid from sudoers matches the specified user/uid,
! 429: * else returns FALSE.
! 430: */
! 431: int
! 432: userpw_matches(sudoers_user, user, pw)
! 433: char *sudoers_user;
! 434: char *user;
! 435: struct passwd *pw;
! 436: {
! 437: if (pw != NULL && *sudoers_user == '#') {
! 438: uid_t uid = atoi(sudoers_user + 1);
! 439: if (uid == pw->pw_uid)
! 440: return(1);
! 441: }
! 442: return(strcmp(sudoers_user, user) == 0);
! 443: }
! 444:
! 445: /*
1.1 millert 446: * Returns TRUE if the given user belongs to the named group,
447: * else returns FALSE.
1.12 ! millert 448: * XXX - reduce the number of passwd/group lookups
1.1 millert 449: */
450: int
1.12 ! millert 451: usergr_matches(group, user, pw)
1.1 millert 452: char *group;
453: char *user;
1.12 ! millert 454: struct passwd *pw;
1.1 millert 455: {
456: struct group *grp;
1.12 ! millert 457: gid_t pw_gid;
1.1 millert 458: char **cur;
459:
460: /* make sure we have a valid usergroup, sudo style */
461: if (*group++ != '%')
462: return(FALSE);
463:
1.12 ! millert 464: /* look up user's primary gid in the passwd file */
! 465: if (pw == NULL && (pw = getpwnam(user)) == NULL)
1.1 millert 466: return(FALSE);
1.12 ! millert 467: pw_gid = pw->pw_gid;
1.1 millert 468:
1.12 ! millert 469: if ((grp = getgrnam(group)) == NULL)
1.1 millert 470: return(FALSE);
471:
1.12 ! millert 472: /* check against user's primary (passwd file) gid */
! 473: if (grp->gr_gid == pw_gid)
1.1 millert 474: return(TRUE);
475:
1.12 ! millert 476: /* check to see if user is explicitly listed in the group */
! 477: for (cur = grp->gr_mem; *cur; cur++) {
1.1 millert 478: if (strcmp(*cur, user) == 0)
479: return(TRUE);
480: }
481:
482: return(FALSE);
483: }
484:
485: /*
486: * Returns TRUE if "host" and "user" belong to the netgroup "netgr",
1.3 millert 487: * else return FALSE. Either of "host", "shost" or "user" may be NULL
1.1 millert 488: * in which case that argument is not checked...
489: */
490: int
1.3 millert 491: netgr_matches(netgr, host, shost, user)
1.1 millert 492: char *netgr;
493: char *host;
1.3 millert 494: char *shost;
1.1 millert 495: char *user;
496: {
497: #ifdef HAVE_GETDOMAINNAME
498: static char *domain = (char *) -1;
499: #else
500: static char *domain = NULL;
501: #endif /* HAVE_GETDOMAINNAME */
502:
503: /* make sure we have a valid netgroup, sudo style */
504: if (*netgr++ != '+')
505: return(FALSE);
506:
507: #ifdef HAVE_GETDOMAINNAME
508: /* get the domain name (if any) */
509: if (domain == (char *) -1) {
510: domain = (char *) emalloc(MAXHOSTNAMELEN);
511: if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') {
512: free(domain);
513: domain = NULL;
514: }
515: }
516: #endif /* HAVE_GETDOMAINNAME */
517:
518: #ifdef HAVE_INNETGR
1.3 millert 519: if (innetgr(netgr, host, user, domain))
520: return(TRUE);
521: else if (host != shost && innetgr(netgr, shost, user, domain))
522: return(TRUE);
523: #endif /* HAVE_INNETGR */
524:
1.1 millert 525: return(FALSE);
526: }
527:
528: /*
529: * Returns TRUE if "s" has shell meta characters in it,
530: * else returns FALSE.
531: */
532: static int
533: has_meta(s)
534: char *s;
535: {
1.4 millert 536: char *t;
1.12 ! millert 537:
1.1 millert 538: for (t = s; *t; t++) {
539: if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']')
540: return(TRUE);
541: }
542: return(FALSE);
543: }