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