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