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