Annotation of src/usr.bin/sudo/parse.c, Revision 1.6
1.1 millert 1: /*
1.6 ! millert 2: * Copyright (c) 1996, 1998-2001 Todd C. Miller <Todd.Miller@courtesan.com>
1.1 millert 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:
1.6 ! millert 40: #include <sys/types.h>
! 41: #include <sys/param.h>
! 42: #include <sys/stat.h>
1.1 millert 43: #include <stdio.h>
44: #ifdef STDC_HEADERS
45: # include <stdlib.h>
1.6 ! millert 46: # include <stddef.h>
! 47: #else
! 48: # ifdef HAVE_STDLIB_H
! 49: # include <stdlib.h>
! 50: # endif
1.1 millert 51: #endif /* STDC_HEADERS */
1.6 ! millert 52: #ifdef HAVE_STRING_H
! 53: # include <string.h>
! 54: #else
! 55: # ifdef HAVE_STRINGS_H
! 56: # include <strings.h>
! 57: # endif
! 58: #endif /* HAVE_STRING_H */
1.1 millert 59: #ifdef HAVE_UNISTD_H
60: # include <unistd.h>
61: #endif /* HAVE_UNISTD_H */
1.2 millert 62: #ifdef HAVE_FNMATCH
1.1 millert 63: # include <fnmatch.h>
64: #endif /* HAVE_FNMATCH_H */
65: #ifdef HAVE_NETGROUP_H
66: # include <netgroup.h>
67: #endif /* HAVE_NETGROUP_H */
68: #include <ctype.h>
69: #include <pwd.h>
70: #include <grp.h>
71: #include <netinet/in.h>
72: #include <arpa/inet.h>
73: #include <netdb.h>
1.6 ! millert 74: #ifdef HAVE_DIRENT_H
1.1 millert 75: # include <dirent.h>
76: # define NAMLEN(dirent) strlen((dirent)->d_name)
77: #else
78: # define dirent direct
79: # define NAMLEN(dirent) (dirent)->d_namlen
1.6 ! millert 80: # ifdef HAVE_SYS_NDIR_H
1.1 millert 81: # include <sys/ndir.h>
82: # endif
1.6 ! millert 83: # ifdef HAVE_SYS_DIR_H
1.1 millert 84: # include <sys/dir.h>
85: # endif
1.6 ! millert 86: # ifdef HAVE_NDIR_H
1.1 millert 87: # include <ndir.h>
88: # endif
89: #endif
90:
91: #include "sudo.h"
92: #include "parse.h"
93: #include "interfaces.h"
94:
95: #ifndef HAVE_FNMATCH
96: # include "emul/fnmatch.h"
97: #endif /* HAVE_FNMATCH */
98:
99: #ifndef lint
1.6 ! millert 100: static const char rcsid[] = "$Sudo: parse.c,v 1.134 2001/12/14 19:52:48 millert Exp $";
1.1 millert 101: #endif /* lint */
102:
103: /*
104: * Globals
105: */
106: int parse_error = FALSE;
1.3 millert 107: extern int keepall;
1.1 millert 108: extern FILE *yyin, *yyout;
109:
110: /*
111: * Prototypes
112: */
113: static int has_meta __P((char *));
114: void init_parser __P((void));
115:
116: /*
117: * Look up the user in the sudoers file and check to see if they are
118: * allowed to run the specified command on this host as the target user.
119: */
120: int
1.6 ! millert 121: sudoers_lookup(pwflag)
! 122: int pwflag;
1.1 millert 123: {
124: int error;
1.5 millert 125: int pwcheck;
1.6 ! millert 126: int nopass;
1.1 millert 127:
128: /* Become sudoers file owner */
129: set_perms(PERM_SUDOERS, 0);
130:
131: /* We opened _PATH_SUDOERS in check_sudoers() so just rewind it. */
132: rewind(sudoers_fp);
133: yyin = sudoers_fp;
134: yyout = stdout;
135:
136: /* Allocate space for data structures in the parser. */
137: init_parser();
138:
1.5 millert 139: /* If pwcheck *could* be PWCHECK_ALL or PWCHECK_ANY, keep more state. */
1.6 ! millert 140: if (pwflag > 0)
1.3 millert 141: keepall = TRUE;
142:
1.1 millert 143: /* Need to be root while stat'ing things in the parser. */
144: set_perms(PERM_ROOT, 0);
145: error = yyparse();
146:
147: /* Close the sudoers file now that we are done with it. */
148: (void) fclose(sudoers_fp);
149: sudoers_fp = NULL;
150:
151: if (error || parse_error)
152: return(VALIDATE_ERROR);
153:
154: /*
1.5 millert 155: * The pw options may have changed during sudoers parse so we
156: * wait until now to set this.
157: */
1.6 ! millert 158: if (pwflag)
! 159: pwcheck = (pwflag == -1) ? PWCHECK_NEVER : def_ival(pwflag);
! 160: else
! 161: pwcheck = 0;
1.5 millert 162:
163: /*
1.1 millert 164: * Assume the worst. If the stack is empty the user was
165: * not mentioned at all.
166: */
1.2 millert 167: if (def_flag(I_AUTHENTICATE))
168: error = VALIDATE_NOT_OK;
169: else
170: error = VALIDATE_NOT_OK | FLAG_NOPASS;
1.5 millert 171: if (pwcheck) {
1.3 millert 172: error |= FLAG_NO_CHECK;
173: } else {
1.1 millert 174: error |= FLAG_NO_HOST;
175: if (!top)
176: error |= FLAG_NO_USER;
1.3 millert 177: }
1.1 millert 178:
179: /*
1.5 millert 180: * Only check the actual command if pwcheck flag is not set.
1.3 millert 181: * It is set for the "validate", "list" and "kill" pseudo-commands.
1.1 millert 182: * Always check the host and user.
183: */
1.6 ! millert 184: nopass = -1;
1.5 millert 185: if (pwcheck) {
1.6 ! millert 186: int found;
1.3 millert 187:
1.5 millert 188: if (pwcheck == PWCHECK_NEVER || !def_flag(I_AUTHENTICATE))
1.3 millert 189: nopass = FLAG_NOPASS;
190: found = 0;
1.1 millert 191: while (top) {
192: if (host_matches == TRUE) {
1.3 millert 193: found = 1;
1.5 millert 194: if (pwcheck == PWCHECK_ANY && no_passwd == TRUE)
1.3 millert 195: nopass = FLAG_NOPASS;
1.5 millert 196: else if (pwcheck == PWCHECK_ALL && nopass != 0)
1.3 millert 197: nopass = (no_passwd == TRUE) ? FLAG_NOPASS : 0;
1.1 millert 198: }
199: top--;
200: }
1.3 millert 201: if (found) {
202: if (nopass == -1)
203: nopass = 0;
204: return(VALIDATE_OK | nopass);
205: }
206: } else {
1.1 millert 207: while (top) {
208: if (host_matches == TRUE) {
209: error &= ~FLAG_NO_HOST;
210: if (runas_matches == TRUE) {
211: if (cmnd_matches == TRUE) {
212: /*
213: * User was granted access to cmnd on host.
214: * If no passwd required return as such.
215: */
216: if (no_passwd == TRUE)
217: return(VALIDATE_OK | FLAG_NOPASS);
218: else
219: return(VALIDATE_OK);
220: } else if (cmnd_matches == FALSE) {
221: /*
222: * User was explicitly denied access to cmnd on host.
223: */
224: if (no_passwd == TRUE)
225: return(VALIDATE_NOT_OK | FLAG_NOPASS);
226: else
227: return(VALIDATE_NOT_OK);
228: }
229: }
230: }
231: top--;
232: }
1.3 millert 233: }
1.1 millert 234:
235: /*
236: * The user was not explicitly granted nor denied access.
237: */
1.6 ! millert 238: if (nopass == -1)
! 239: nopass = 0;
! 240: return(error | nopass);
1.1 millert 241: }
242:
243: /*
244: * If path doesn't end in /, return TRUE iff cmnd & path name the same inode;
245: * otherwise, return TRUE if cmnd names one of the inodes in path.
246: */
247: int
248: command_matches(cmnd, cmnd_args, path, sudoers_args)
249: char *cmnd;
250: char *cmnd_args;
251: char *path;
252: char *sudoers_args;
253: {
254: int plen;
255: static struct stat cst;
256: struct stat pst;
257: DIR *dirp;
258: struct dirent *dent;
259: char buf[MAXPATHLEN];
260: static char *cmnd_base;
261:
262: /* Don't bother with pseudo commands like "validate" */
263: if (strchr(cmnd, '/') == NULL)
264: return(FALSE);
265:
266: plen = strlen(path);
267:
268: /* Only need to stat cmnd once since it never changes */
269: if (cst.st_dev == 0) {
270: if (stat(cmnd, &cst) == -1)
271: return(FALSE);
272: if ((cmnd_base = strrchr(cmnd, '/')) == NULL)
273: cmnd_base = cmnd;
274: else
275: cmnd_base++;
276: }
277:
278: /*
279: * If the pathname has meta characters in it use fnmatch(3)
280: * to do the matching
281: */
282: if (has_meta(path)) {
283: /*
284: * Return true if fnmatch(3) succeeds AND
285: * a) there are no args in sudoers OR
286: * b) there are no args on command line and none required by sudoers OR
287: * c) there are args in sudoers and on command line and they match
288: * else return false.
289: */
290: if (fnmatch(path, cmnd, FNM_PATHNAME) != 0)
291: return(FALSE);
292: if (!sudoers_args ||
293: (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
294: (sudoers_args && fnmatch(sudoers_args, cmnd_args ? cmnd_args : "",
295: 0) == 0)) {
296: if (safe_cmnd)
297: free(safe_cmnd);
298: safe_cmnd = estrdup(user_cmnd);
299: return(TRUE);
300: } else
301: return(FALSE);
302: } else {
303: /*
304: * No meta characters
305: * Check to make sure this is not a directory spec (doesn't end in '/')
306: */
307: if (path[plen - 1] != '/') {
308: char *p;
309:
310: /* Only proceed if the basenames of cmnd and path are the same */
311: if ((p = strrchr(path, '/')) == NULL)
312: p = path;
313: else
314: p++;
315: if (strcmp(cmnd_base, p) != 0 || stat(path, &pst) == -1)
316: return(FALSE);
317:
318: /*
319: * Return true if inode/device matches AND
320: * a) there are no args in sudoers OR
321: * b) there are no args on command line and none req by sudoers OR
322: * c) there are args in sudoers and on command line and they match
323: */
324: if (cst.st_dev != pst.st_dev || cst.st_ino != pst.st_ino)
325: return(FALSE);
326: if (!sudoers_args ||
327: (!cmnd_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
328: (sudoers_args &&
329: fnmatch(sudoers_args, cmnd_args ? cmnd_args : "", 0) == 0)) {
330: if (safe_cmnd)
331: free(safe_cmnd);
332: safe_cmnd = estrdup(path);
333: return(TRUE);
334: } else
335: return(FALSE);
336: }
337:
338: /*
339: * Grot through path's directory entries, looking for cmnd.
340: */
341: dirp = opendir(path);
342: if (dirp == NULL)
343: return(FALSE);
344:
345: while ((dent = readdir(dirp)) != NULL) {
346: /* ignore paths > MAXPATHLEN (XXX - log) */
347: if (plen + NAMLEN(dent) >= sizeof(buf))
348: continue;
349: strcpy(buf, path);
350: strcat(buf, dent->d_name);
351:
352: /* only stat if basenames are the same */
353: if (strcmp(cmnd_base, dent->d_name) != 0 || stat(buf, &pst) == -1)
354: continue;
355: if (cst.st_dev == pst.st_dev && cst.st_ino == pst.st_ino) {
356: if (safe_cmnd)
357: free(safe_cmnd);
358: safe_cmnd = estrdup(buf);
359: break;
360: }
361: }
362:
363: closedir(dirp);
364: return(dent != NULL);
365: }
366: }
367:
368: /*
369: * Returns TRUE if "n" is one of our ip addresses or if
370: * "n" is a network that we are on, else returns FALSE.
371: */
372: int
373: addr_matches(n)
374: char *n;
375: {
376: int i;
377: char *m;
378: struct in_addr addr, mask;
379:
380: /* If there's an explicit netmask, use it. */
381: if ((m = strchr(n, '/'))) {
382: *m++ = '\0';
383: addr.s_addr = inet_addr(n);
384: if (strchr(m, '.'))
385: mask.s_addr = inet_addr(m);
1.6 ! millert 386: else {
! 387: i = 32 - atoi(m);
! 388: mask.s_addr = 0xffffffff;
! 389: mask.s_addr >>= i;
! 390: mask.s_addr <<= i;
! 391: mask.s_addr = htonl(mask.s_addr);
! 392: }
1.1 millert 393: *(m - 1) = '/';
394:
395: for (i = 0; i < num_interfaces; i++)
396: if ((interfaces[i].addr.s_addr & mask.s_addr) == addr.s_addr)
397: return(TRUE);
398: } else {
399: addr.s_addr = inet_addr(n);
400:
401: for (i = 0; i < num_interfaces; i++)
402: if (interfaces[i].addr.s_addr == addr.s_addr ||
403: (interfaces[i].addr.s_addr & interfaces[i].netmask.s_addr)
404: == addr.s_addr)
405: return(TRUE);
406: }
407:
408: return(FALSE);
409: }
410:
411: /*
1.4 millert 412: * Returns 0 if the hostname matches the pattern and non-zero otherwise.
413: */
414: int
415: hostname_matches(shost, lhost, pattern)
416: char *shost;
417: char *lhost;
418: char *pattern;
419: {
420: if (has_meta(pattern)) {
421: if (strchr(pattern, '.'))
422: return(fnmatch(pattern, lhost, FNM_CASEFOLD));
423: else
424: return(fnmatch(pattern, shost, FNM_CASEFOLD));
425: } else {
426: if (strchr(pattern, '.'))
427: return(strcasecmp(lhost, pattern));
428: else
429: return(strcasecmp(shost, pattern));
430: }
431: }
432:
433: /*
1.1 millert 434: * Returns TRUE if the given user belongs to the named group,
435: * else returns FALSE.
436: */
437: int
438: usergr_matches(group, user)
439: char *group;
440: char *user;
441: {
442: struct group *grp;
443: struct passwd *pw;
444: char **cur;
445:
446: /* make sure we have a valid usergroup, sudo style */
447: if (*group++ != '%')
448: return(FALSE);
449:
450: if ((grp = getgrnam(group)) == NULL)
451: return(FALSE);
452:
453: /*
454: * Check against user's real gid as well as group's user list
455: */
456: if ((pw = getpwnam(user)) == NULL)
457: return(FALSE);
458:
459: if (grp->gr_gid == pw->pw_gid)
460: return(TRUE);
461:
462: for (cur=grp->gr_mem; *cur; cur++) {
463: if (strcmp(*cur, user) == 0)
464: return(TRUE);
465: }
466:
467: return(FALSE);
468: }
469:
470: /*
471: * Returns TRUE if "host" and "user" belong to the netgroup "netgr",
1.3 millert 472: * else return FALSE. Either of "host", "shost" or "user" may be NULL
1.1 millert 473: * in which case that argument is not checked...
474: */
475: int
1.3 millert 476: netgr_matches(netgr, host, shost, user)
1.1 millert 477: char *netgr;
478: char *host;
1.3 millert 479: char *shost;
1.1 millert 480: char *user;
481: {
482: #ifdef HAVE_GETDOMAINNAME
483: static char *domain = (char *) -1;
484: #else
485: static char *domain = NULL;
486: #endif /* HAVE_GETDOMAINNAME */
487:
488: /* make sure we have a valid netgroup, sudo style */
489: if (*netgr++ != '+')
490: return(FALSE);
491:
492: #ifdef HAVE_GETDOMAINNAME
493: /* get the domain name (if any) */
494: if (domain == (char *) -1) {
495: domain = (char *) emalloc(MAXHOSTNAMELEN);
496: if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') {
497: free(domain);
498: domain = NULL;
499: }
500: }
501: #endif /* HAVE_GETDOMAINNAME */
502:
503: #ifdef HAVE_INNETGR
1.3 millert 504: if (innetgr(netgr, host, user, domain))
505: return(TRUE);
506: else if (host != shost && innetgr(netgr, shost, user, domain))
507: return(TRUE);
508: #endif /* HAVE_INNETGR */
509:
1.1 millert 510: return(FALSE);
511: }
512:
513: /*
514: * Returns TRUE if "s" has shell meta characters in it,
515: * else returns FALSE.
516: */
517: static int
518: has_meta(s)
519: char *s;
520: {
1.4 millert 521: char *t;
1.1 millert 522:
523: for (t = s; *t; t++) {
524: if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']')
525: return(TRUE);
526: }
527: return(FALSE);
528: }