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