fix typo in changelog
[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.16 2008/02/09 14:44:48 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 #ifdef HAVE_SELINUX
202                     /* Set role and type if not specified on command line. */
203                     if (user_role == NULL) {
204                         if (match[top-1].role != NULL)
205                             user_role = match[top-1].role;
206                         else
207                             user_role = def_role;
208                     }
209                     if (user_type == NULL) {
210                         if (match[top-1].type != NULL)
211                             user_type = match[top-1].type;
212                         else
213                             user_type = def_type;
214                     }
215 #endif
216                     set_perms(PERM_ROOT);
217                     return(VALIDATE_OK |
218                         (no_passwd == TRUE ? FLAG_NOPASS : 0) |
219                         (no_execve == TRUE ? FLAG_NOEXEC : 0) |
220                         (setenv_ok >= TRUE ? FLAG_SETENV : 0));
221                 } else if ((runas_matches == TRUE && cmnd_matches == FALSE) ||
222                     (runas_matches == FALSE && cmnd_matches == TRUE)) {
223                     /*
224                      * User was explicitly denied access to cmnd on host.
225                      */
226                     set_perms(PERM_ROOT);
227                     return(VALIDATE_NOT_OK |
228                         (no_passwd == TRUE ? FLAG_NOPASS : 0) |
229                         (no_execve == TRUE ? FLAG_NOEXEC : 0) |
230                         (setenv_ok >= TRUE ? FLAG_SETENV : 0));
231                 }
232             }
233             top--;
234         }
235     }
236     set_perms(PERM_ROOT);
237
238     /*
239      * The user was neither explicitly granted nor denied access.
240      */
241     if (nopass == -1)
242         nopass = 0;
243     return(error | nopass);
244 }
245
246 /*
247  * If path doesn't end in /, return TRUE iff cmnd & path name the same inode;
248  * otherwise, return TRUE if user_cmnd names one of the inodes in path.
249  */
250 int
251 command_matches(sudoers_cmnd, sudoers_args)
252     char *sudoers_cmnd;
253     char *sudoers_args;
254 {
255     struct stat sudoers_stat;
256     struct dirent *dent;
257     char **ap, *base, buf[PATH_MAX];
258     glob_t gl;
259     DIR *dirp;
260
261     /* Check for pseudo-commands */
262     if (strchr(user_cmnd, '/') == NULL) {
263         /*
264          * Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND
265          *  a) there are no args in sudoers OR
266          *  b) there are no args on command line and none req by sudoers OR
267          *  c) there are args in sudoers and on command line and they match
268          */
269         if (strcmp(sudoers_cmnd, "sudoedit") != 0 ||
270             strcmp(user_cmnd, "sudoedit") != 0)
271             return(FALSE);
272         if (!sudoers_args ||
273             (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
274             (sudoers_args &&
275              fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
276             efree(safe_cmnd);
277             safe_cmnd = estrdup(sudoers_cmnd);
278             return(TRUE);
279         } else
280             return(FALSE);
281     }
282
283     /*
284      * If sudoers_cmnd has meta characters in it, use fnmatch(3)
285      * to do the matching.
286      */
287     if (has_meta(sudoers_cmnd)) {
288         /*
289          * Return true if we find a match in the glob(3) results AND
290          *  a) there are no args in sudoers OR
291          *  b) there are no args on command line and none required by sudoers OR
292          *  c) there are args in sudoers and on command line and they match
293          * else return false.
294          *
295          * Could optimize patterns ending in "/*" to "/user_base"
296          */
297 #define GLOB_FLAGS      (GLOB_NOSORT | GLOB_MARK | GLOB_BRACE | GLOB_TILDE)
298         if (glob(sudoers_cmnd, GLOB_FLAGS, NULL, &gl) != 0) {
299             globfree(&gl);
300             return(FALSE);
301         }
302         /* For each glob match, compare basename, st_dev and st_ino. */
303         for (ap = gl.gl_pathv; *ap != NULL; ap++) {
304             /* only stat if basenames are the same */
305             if ((base = strrchr(*ap, '/')) != NULL)
306                 base++;
307             else
308                 base = *ap;
309             if (strcmp(user_base, base) != 0 ||
310                 stat(*ap, &sudoers_stat) == -1)
311                 continue;
312             if (user_stat->st_dev == sudoers_stat.st_dev &&
313                 user_stat->st_ino == sudoers_stat.st_ino) {
314                 efree(safe_cmnd);
315                 safe_cmnd = estrdup(*ap);
316                 break;
317             }
318         }
319         globfree(&gl);
320         if (*ap == NULL)
321             return(FALSE);
322
323         if (!sudoers_args ||
324             (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
325             (sudoers_args &&
326              fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
327             efree(safe_cmnd);
328             safe_cmnd = estrdup(user_cmnd);
329             return(TRUE);
330         } else
331             return(FALSE);
332     } else {
333         size_t dlen = strlen(sudoers_cmnd);
334
335         /*
336          * No meta characters
337          * Check to make sure this is not a directory spec (doesn't end in '/')
338          */
339         if (sudoers_cmnd[dlen - 1] != '/') {
340             /* Only proceed if user_base and basename(sudoers_cmnd) match */
341             if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
342                 base = sudoers_cmnd;
343             else
344                 base++;
345             if (strcmp(user_base, base) != 0 ||
346                 stat(sudoers_cmnd, &sudoers_stat) == -1)
347                 return(FALSE);
348
349             /*
350              * Return true if inode/device matches AND
351              *  a) there are no args in sudoers OR
352              *  b) there are no args on command line and none req by sudoers OR
353              *  c) there are args in sudoers and on command line and they match
354              */
355             if (user_stat->st_dev != sudoers_stat.st_dev ||
356                 user_stat->st_ino != sudoers_stat.st_ino)
357                 return(FALSE);
358             if (!sudoers_args ||
359                 (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
360                 (sudoers_args &&
361                  fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
362                 efree(safe_cmnd);
363                 safe_cmnd = estrdup(sudoers_cmnd);
364                 return(TRUE);
365             } else
366                 return(FALSE);
367         }
368
369         /*
370          * Grot through sudoers_cmnd's directory entries, looking for user_base.
371          */
372         dirp = opendir(sudoers_cmnd);
373         if (dirp == NULL)
374             return(FALSE);
375
376         if (strlcpy(buf, sudoers_cmnd, sizeof(buf)) >= sizeof(buf))
377             return(FALSE);
378         while ((dent = readdir(dirp)) != NULL) {
379             /* ignore paths > PATH_MAX (XXX - log) */
380             buf[dlen] = '\0';
381             if (strlcat(buf, dent->d_name, sizeof(buf)) >= sizeof(buf))
382                 continue;
383
384             /* only stat if basenames are the same */
385             if (strcmp(user_base, dent->d_name) != 0 ||
386                 stat(buf, &sudoers_stat) == -1)
387                 continue;
388             if (user_stat->st_dev == sudoers_stat.st_dev &&
389                 user_stat->st_ino == sudoers_stat.st_ino) {
390                 efree(safe_cmnd);
391                 safe_cmnd = estrdup(buf);
392                 break;
393             }
394         }
395
396         closedir(dirp);
397         return(dent != NULL);
398     }
399 }
400
401 static int
402 addr_matches_if(n)
403     char *n;
404 {
405     int i;
406     struct in_addr addr;
407     struct interface *ifp;
408 #ifdef HAVE_IN6_ADDR
409     struct in6_addr addr6;
410     int j;
411 #endif
412     int family;
413
414 #ifdef HAVE_IN6_ADDR
415     if (inet_pton(AF_INET6, n, &addr6) > 0) {
416         family = AF_INET6;
417     } else
418 #endif
419     {
420         family = AF_INET;
421         addr.s_addr = inet_addr(n);
422     }
423
424     for (i = 0; i < num_interfaces; i++) {
425         ifp = &interfaces[i];
426         if (ifp->family != family)
427             continue;
428         switch(family) {
429             case AF_INET:
430                 if (ifp->addr.ip4.s_addr == addr.s_addr ||
431                     (ifp->addr.ip4.s_addr & ifp->netmask.ip4.s_addr)
432                     == addr.s_addr)
433                     return(TRUE);
434                 break;
435 #ifdef HAVE_IN6_ADDR
436             case AF_INET6:
437                 if (memcmp(ifp->addr.ip6.s6_addr, addr6.s6_addr,
438                     sizeof(addr6.s6_addr)) == 0)
439                     return(TRUE);
440                 for (j = 0; j < sizeof(addr6.s6_addr); j++) {
441                     if ((ifp->addr.ip6.s6_addr[j] & ifp->netmask.ip6.s6_addr[j]) != addr6.s6_addr[j])
442                         break;
443                 }
444                 if (j == sizeof(addr6.s6_addr))
445                     return(TRUE);
446 #endif /* HAVE_IN6_ADDR */
447         }
448     }
449
450     return(FALSE);
451 }
452
453 static int
454 addr_matches_if_netmask(n, m)
455     char *n;
456     char *m;
457 {
458     int i;
459     struct in_addr addr, mask;
460     struct interface *ifp;
461 #ifdef HAVE_IN6_ADDR
462     struct in6_addr addr6, mask6;
463     int j;
464 #endif
465     int family;
466
467 #ifdef HAVE_IN6_ADDR
468     if (inet_pton(AF_INET6, n, &addr6) > 0)
469         family = AF_INET6;
470     else
471 #endif
472     {
473         family = AF_INET;
474         addr.s_addr = inet_addr(n);
475     }
476
477     if (family == AF_INET) {
478         if (strchr(m, '.'))
479             mask.s_addr = inet_addr(m);
480         else {
481             i = 32 - atoi(m);
482             mask.s_addr = 0xffffffff;
483             mask.s_addr >>= i;
484             mask.s_addr <<= i;
485             mask.s_addr = htonl(mask.s_addr);
486         }
487     }
488 #ifdef HAVE_IN6_ADDR
489     else {
490         if (inet_pton(AF_INET6, m, &mask6) <= 0) {
491             j = atoi(m);
492             for (i = 0; i < 16; i++) {
493                 if (j < i * 8)
494                     mask6.s6_addr[i] = 0;
495                 else if (i * 8 + 8 <= j)
496                     mask6.s6_addr[i] = 0xff;
497                 else
498                     mask6.s6_addr[i] = 0xff00 >> (j - i * 8);
499             }
500         }
501     }
502 #endif /* HAVE_IN6_ADDR */
503
504     for (i = 0; i < num_interfaces; i++) {
505         ifp = &interfaces[i];
506         if (ifp->family != family)
507             continue;
508         switch(family) {
509             case AF_INET:
510                 if ((ifp->addr.ip4.s_addr & mask.s_addr) == addr.s_addr)
511                     return(TRUE);
512 #ifdef HAVE_IN6_ADDR
513             case AF_INET6:
514                 for (j = 0; j < sizeof(addr6.s6_addr); j++) {
515                     if ((ifp->addr.ip6.s6_addr[j] & mask6.s6_addr[j]) != addr6.s6_addr[j])
516                         break;
517                 }
518                 if (j == sizeof(addr6.s6_addr))
519                     return(TRUE);
520 #endif /* HAVE_IN6_ADDR */
521         }
522     }
523
524     return(FALSE);
525 }
526
527 /*
528  * Returns TRUE if "n" is one of our ip addresses or if
529  * "n" is a network that we are on, else returns FALSE.
530  */
531 int
532 addr_matches(n)
533     char *n;
534 {
535     char *m;
536     int retval;
537
538     /* If there's an explicit netmask, use it. */
539     if ((m = strchr(n, '/'))) {
540         *m++ = '\0';
541         retval = addr_matches_if_netmask(n, m);
542         *(m - 1) = '/';
543     } else
544         retval = addr_matches_if(n);
545
546     return(retval);
547 }
548
549 /*
550  * Returns 0 if the hostname matches the pattern and non-zero otherwise.
551  */
552 int
553 hostname_matches(shost, lhost, pattern)
554     char *shost;
555     char *lhost;
556     char *pattern;
557 {
558     if (has_meta(pattern)) {
559         if (strchr(pattern, '.'))
560             return(fnmatch(pattern, lhost, FNM_CASEFOLD));
561         else
562             return(fnmatch(pattern, shost, FNM_CASEFOLD));
563     } else {
564         if (strchr(pattern, '.'))
565             return(strcasecmp(lhost, pattern));
566         else
567             return(strcasecmp(shost, pattern));
568     }
569 }
570
571 /*
572  *  Returns TRUE if the user/uid from sudoers matches the specified user/uid,
573  *  else returns FALSE.
574  */
575 int
576 userpw_matches(sudoers_user, user, pw)
577     char *sudoers_user;
578     char *user;
579     struct passwd *pw;
580 {
581     if (pw != NULL && *sudoers_user == '#') {
582         uid_t uid = atoi(sudoers_user + 1);
583         if (uid == pw->pw_uid)
584             return(1);
585     }
586     return(strcmp(sudoers_user, user) == 0);
587 }
588
589 /*
590  *  Returns TRUE if the given user belongs to the named group,
591  *  else returns FALSE.
592  *  XXX - reduce the number of passwd/group lookups
593  */
594 int
595 usergr_matches(group, user, pw)
596     char *group;
597     char *user;
598     struct passwd *pw;
599 {
600     struct group *grp;
601     gid_t pw_gid;
602     char **cur;
603     int i;
604
605     /* make sure we have a valid usergroup, sudo style */
606     if (*group++ != '%')
607         return(FALSE);
608
609     /* look up user's primary gid in the passwd file */
610     if (pw == NULL && (pw = getpwnam(user)) == NULL)
611         return(FALSE);
612     pw_gid = pw->pw_gid;
613
614     if ((grp = getgrnam(group)) == NULL)
615         return(FALSE);
616
617     /* check against user's primary (passwd file) gid */
618     if (grp->gr_gid == pw_gid)
619         return(TRUE);
620
621     /*
622      * If the user has a supplementary group vector, check it first.
623      */
624     if (strcmp(user, user_name) == 0) {
625         for (i = 0; i < user_ngroups; i++) {
626             if (grp->gr_gid == user_groups[i])
627                 return(TRUE);
628         }
629     }
630     if (grp->gr_mem != NULL) {
631         for (cur = grp->gr_mem; *cur; cur++) {
632             if (strcmp(*cur, user) == 0)
633                 return(TRUE);
634         }
635     }
636
637     return(FALSE);
638 }
639
640 /*
641  * Returns TRUE if "host" and "user" belong to the netgroup "netgr",
642  * else return FALSE.  Either of "host", "shost" or "user" may be NULL
643  * in which case that argument is not checked...
644  */
645 int
646 netgr_matches(netgr, host, shost, user)
647     char *netgr;
648     char *host;
649     char *shost;
650     char *user;
651 {
652     static char *domain;
653 #ifdef HAVE_GETDOMAINNAME
654     static int initialized;
655 #endif
656
657     /* make sure we have a valid netgroup, sudo style */
658     if (*netgr++ != '+')
659         return(FALSE);
660
661 #ifdef HAVE_GETDOMAINNAME
662     /* get the domain name (if any) */
663     if (!initialized) {
664         domain = (char *) emalloc(MAXHOSTNAMELEN);
665         if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') {
666             efree(domain);
667             domain = NULL;
668         }
669         initialized = 1;
670     }
671 #endif /* HAVE_GETDOMAINNAME */
672
673 #ifdef HAVE_INNETGR
674     if (innetgr(netgr, host, user, domain))
675         return(TRUE);
676     else if (host != shost && innetgr(netgr, shost, user, domain))
677         return(TRUE);
678 #endif /* HAVE_INNETGR */
679
680     return(FALSE);
681 }
682
683 /*
684  * Returns TRUE if "s" has shell meta characters in it,
685  * else returns FALSE.
686  */
687 static int
688 has_meta(s)
689     char *s;
690 {
691     char *t;
692  
693     for (t = s; *t; t++) {
694         if (*t == '\\' || *t == '?' || *t == '*' || *t == '[' || *t == ']')
695             return(TRUE);
696     }
697     return(FALSE);
698 }