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