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