Imported Upstream version 1.6.6
[debian/sudo] / parse.c
1 /*
2  * Copyright (c) 1996, 1998-2002 Todd C. Miller <Todd.Miller@courtesan.com>
3  * All rights reserved.
4  *
5  * This code is derived from software contributed by Chris Jepeway.
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
39 #include <sys/types.h>
40 #include <sys/param.h>
41 #include <sys/stat.h>
42 #include <stdio.h>
43 #ifdef STDC_HEADERS
44 # include <stdlib.h>
45 # include <stddef.h>
46 #else
47 # ifdef HAVE_STDLIB_H
48 #  include <stdlib.h>
49 # endif
50 #endif /* STDC_HEADERS */
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 */
58 #ifdef HAVE_UNISTD_H
59 # include <unistd.h>
60 #endif /* HAVE_UNISTD_H */
61 #ifdef HAVE_FNMATCH
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>
73 #ifdef HAVE_DIRENT_H
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
79 # ifdef HAVE_SYS_NDIR_H
80 #  include <sys/ndir.h>
81 # endif
82 # ifdef HAVE_SYS_DIR_H
83 #  include <sys/dir.h>
84 # endif
85 # ifdef HAVE_NDIR_H
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
99 static const char rcsid[] = "$Sudo: parse.c,v 1.137 2002/03/16 00:44:47 millert Exp $";
100 #endif /* lint */
101
102 /*
103  * Globals
104  */
105 int parse_error = FALSE;
106 extern int keepall;
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
120 sudoers_lookup(pwflag)
121     int pwflag;
122 {
123     int error;
124     int pwcheck;
125     int nopass;
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
138     /* If pwcheck *could* be PWCHECK_ALL or PWCHECK_ANY, keep more state. */
139     if (pwflag > 0)
140         keepall = TRUE;
141
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     /*
154      * The pw options may have changed during sudoers parse so we
155      * wait until now to set this.
156      */
157     if (pwflag)
158         pwcheck = (pwflag == -1) ? PWCHECK_NEVER : def_ival(pwflag);
159     else
160         pwcheck = 0;
161
162     /*
163      * Assume the worst.  If the stack is empty the user was
164      * not mentioned at all.
165      */
166     if (def_flag(I_AUTHENTICATE))
167         error = VALIDATE_NOT_OK;
168     else
169         error = VALIDATE_NOT_OK | FLAG_NOPASS;
170     if (pwcheck) {
171         error |= FLAG_NO_CHECK;
172     } else {
173         error |= FLAG_NO_HOST;
174         if (!top)
175             error |= FLAG_NO_USER;
176     }
177
178     /*
179      * Only check the actual command if pwcheck flag is not set.
180      * It is set for the "validate", "list" and "kill" pseudo-commands.
181      * Always check the host and user.
182      */
183     nopass = -1;
184     if (pwcheck) {
185         int found;
186
187         if (pwcheck == PWCHECK_NEVER || !def_flag(I_AUTHENTICATE))
188             nopass = FLAG_NOPASS;
189         found = 0;
190         while (top) {
191             if (host_matches == TRUE) {
192                 found = 1;
193                 if (pwcheck == PWCHECK_ANY && no_passwd == TRUE)
194                     nopass = FLAG_NOPASS;
195                 else if (pwcheck == PWCHECK_ALL && nopass != 0)
196                     nopass = (no_passwd == TRUE) ? FLAG_NOPASS : 0;
197             }
198             top--;
199         }
200         if (found) {
201             if (nopass == -1)
202                 nopass = 0;
203             return(VALIDATE_OK | nopass);
204         }
205     } else {
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         }
232     }
233
234     /*
235      * The user was not explicitly granted nor denied access.
236      */
237     if (nopass == -1)
238         nopass = 0;
239     return(error | nopass);
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);
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         }
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 /*
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 /*
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",
471  * else return FALSE.  Either of "host", "shost" or "user" may be NULL
472  * in which case that argument is not checked...
473  */
474 int
475 netgr_matches(netgr, host, shost, user)
476     char *netgr;
477     char *host;
478     char *shost;
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
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
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 {
520     char *t;
521     
522     for (t = s; *t; t++) {
523         if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']')
524             return(TRUE);
525     }
526     return(FALSE);
527 }