Imported Upstream version 1.6.9p6
[debian/sudo] / parse.c
diff --git a/parse.c b/parse.c
index c045b9c31b2149c0d5893ed35f638e8f2e108ec3..7e31ff3530b66381845e500fb7b55b86dd088702 100644 (file)
--- a/parse.c
+++ b/parse.c
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 1996, 1998-2004 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 1996, 1998-2005, 2007
+ *     Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
  */
 
-#include "config.h"
+#include <config.h>
 
 #include <sys/types.h>
 #include <sys/param.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <stdio.h>
 #ifdef STDC_HEADERS
@@ -47,6 +49,9 @@
 #ifdef HAVE_FNMATCH
 # include <fnmatch.h>
 #endif /* HAVE_FNMATCH */
+#ifdef HAVE_EXTENDED_GLOB
+# include <glob.h>
+#endif /* HAVE_EXTENDED_GLOB */
 #ifdef HAVE_NETGROUP_H
 # include <netgroup.h>
 #endif /* HAVE_NETGROUP_H */
 #ifndef HAVE_FNMATCH
 # include "emul/fnmatch.h"
 #endif /* HAVE_FNMATCH */
+#ifndef HAVE_EXTENDED_GLOB
+# include "emul/glob.h"
+#endif /* HAVE_EXTENDED_GLOB */
 
 #ifndef lint
-static const char rcsid[] = "$Sudo: parse.c,v 1.161 2004/08/24 18:01:13 millert Exp $";
+__unused static const char rcsid[] = "$Sudo: parse.c,v 1.160.2.13 2007/08/25 02:37:11 millert Exp $";
 #endif /* lint */
 
 /*
@@ -107,7 +115,6 @@ sudoers_lookup(pwflag)
     int pwflag;
 {
     int error, nopass;
-    enum def_tupple pwcheck;
 
     /* We opened _PATH_SUDOERS in check_sudoers() so just rewind it. */
     rewind(sudoers_fp);
@@ -117,7 +124,7 @@ sudoers_lookup(pwflag)
     /* Allocate space for data structures in the parser. */
     init_parser();
 
-    /* If pwcheck *could* be "all" or "any", keep more state. */
+    /* Keep more state for pseudo-commands so that listpw and verifypw work */
     if (pwflag > 0)
        keepall = TRUE;
 
@@ -134,15 +141,6 @@ sudoers_lookup(pwflag)
        return(VALIDATE_ERROR);
     }
 
-    /*
-     * The pw options may have changed during sudoers parse so we
-     * wait until now to set this.
-     */
-    if (pwflag)
-       pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple;
-    else
-       pwcheck = 0;
-
     /*
      * Assume the worst.  If the stack is empty the user was
      * not mentioned at all.
@@ -151,7 +149,7 @@ sudoers_lookup(pwflag)
        error = VALIDATE_NOT_OK;
     else
        error = VALIDATE_NOT_OK | FLAG_NOPASS;
-    if (pwcheck) {
+    if (pwflag) {
        SET(error, FLAG_NO_CHECK);
     } else {
        SET(error, FLAG_NO_HOST);
@@ -167,6 +165,9 @@ sudoers_lookup(pwflag)
     nopass = -1;
     if (pwflag) {
        int found;
+       enum def_tupple pwcheck;
+
+       pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple;
 
        if (pwcheck == always && def_authenticate)
            nopass = FLAG_CHECK_USER;
@@ -200,7 +201,8 @@ sudoers_lookup(pwflag)
                    set_perms(PERM_ROOT);
                    return(VALIDATE_OK |
                        (no_passwd == TRUE ? FLAG_NOPASS : 0) |
-                       (no_execve == TRUE ? FLAG_NOEXEC : 0));
+                       (no_execve == TRUE ? FLAG_NOEXEC : 0) |
+                       (setenv_ok == TRUE ? FLAG_SETENV : 0));
                } else if ((runas_matches == TRUE && cmnd_matches == FALSE) ||
                    (runas_matches == FALSE && cmnd_matches == TRUE)) {
                    /*
@@ -209,7 +211,8 @@ sudoers_lookup(pwflag)
                    set_perms(PERM_ROOT);
                    return(VALIDATE_NOT_OK |
                        (no_passwd == TRUE ? FLAG_NOPASS : 0) |
-                       (no_execve == TRUE ? FLAG_NOEXEC : 0));
+                       (no_execve == TRUE ? FLAG_NOEXEC : 0) |
+                       (setenv_ok == TRUE ? FLAG_SETENV : 0));
                }
            }
            top--;
@@ -236,7 +239,8 @@ command_matches(sudoers_cmnd, sudoers_args)
 {
     struct stat sudoers_stat;
     struct dirent *dent;
-    char buf[PATH_MAX];
+    char **ap, *base, buf[PATH_MAX];
+    glob_t gl;
     DIR *dirp;
 
     /* Check for pseudo-commands */
@@ -254,8 +258,7 @@ command_matches(sudoers_cmnd, sudoers_args)
            (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
            (sudoers_args &&
             fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
-           if (safe_cmnd)
-               free(safe_cmnd);
+           efree(safe_cmnd);
            safe_cmnd = estrdup(sudoers_cmnd);
            return(TRUE);
        } else
@@ -268,20 +271,45 @@ command_matches(sudoers_cmnd, sudoers_args)
      */
     if (has_meta(sudoers_cmnd)) {
        /*
-        * Return true if fnmatch(3) succeeds AND
+        * Return true if we find a match in the glob(3) results AND
         *  a) there are no args in sudoers OR
         *  b) there are no args on command line and none required by sudoers OR
         *  c) there are args in sudoers and on command line and they match
         * else return false.
+        *
+        * Could optimize patterns ending in "/*" to "/user_base"
         */
-       if (fnmatch(sudoers_cmnd, user_cmnd, FNM_PATHNAME) != 0)
+#define GLOB_FLAGS     (GLOB_NOSORT | GLOB_MARK | GLOB_BRACE | GLOB_TILDE)
+       if (glob(sudoers_cmnd, GLOB_FLAGS, NULL, &gl) != 0) {
+           globfree(&gl);
+           return(FALSE);
+       }
+       /* For each glob match, compare basename, st_dev and st_ino. */
+       for (ap = gl.gl_pathv; *ap != NULL; ap++) {
+           /* only stat if basenames are the same */
+           if ((base = strrchr(*ap, '/')) != NULL)
+               base++;
+           else
+               base = *ap;
+           if (strcmp(user_base, base) != 0 ||
+               stat(*ap, &sudoers_stat) == -1)
+               continue;
+           if (user_stat->st_dev == sudoers_stat.st_dev &&
+               user_stat->st_ino == sudoers_stat.st_ino) {
+               efree(safe_cmnd);
+               safe_cmnd = estrdup(*ap);
+               break;
+           }
+       }
+       globfree(&gl);
+       if (*ap == NULL)
            return(FALSE);
+
        if (!sudoers_args ||
            (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
            (sudoers_args &&
             fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
-           if (safe_cmnd)
-               free(safe_cmnd);
+           efree(safe_cmnd);
            safe_cmnd = estrdup(user_cmnd);
            return(TRUE);
        } else
@@ -294,8 +322,6 @@ command_matches(sudoers_cmnd, sudoers_args)
         * Check to make sure this is not a directory spec (doesn't end in '/')
         */
        if (sudoers_cmnd[dlen - 1] != '/') {
-           char *base;
-
            /* Only proceed if user_base and basename(sudoers_cmnd) match */
            if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
                base = sudoers_cmnd;
@@ -318,8 +344,7 @@ command_matches(sudoers_cmnd, sudoers_args)
                (!user_args && sudoers_args && !strcmp("\"\"", sudoers_args)) ||
                (sudoers_args &&
                 fnmatch(sudoers_args, user_args ? user_args : "", 0) == 0)) {
-               if (safe_cmnd)
-                   free(safe_cmnd);
+               efree(safe_cmnd);
                safe_cmnd = estrdup(sudoers_cmnd);
                return(TRUE);
            } else
@@ -347,8 +372,7 @@ command_matches(sudoers_cmnd, sudoers_args)
                continue;
            if (user_stat->st_dev == sudoers_stat.st_dev &&
                user_stat->st_ino == sudoers_stat.st_ino) {
-               if (safe_cmnd)
-                   free(safe_cmnd);
+               efree(safe_cmnd);
                safe_cmnd = estrdup(buf);
                break;
            }
@@ -359,22 +383,83 @@ command_matches(sudoers_cmnd, sudoers_args)
     }
 }
 
-/*
- * Returns TRUE if "n" is one of our ip addresses or if
- * "n" is a network that we are on, else returns FALSE.
- */
-int
-addr_matches(n)
+static int
+addr_matches_if(n)
     char *n;
 {
     int i;
+    struct in_addr addr;
+    struct interface *ifp;
+#ifdef AF_INET6
+    struct in6_addr addr6;
+    int j;
+#endif
+    int family;
+
+#ifdef AF_INET6
+    if (inet_pton(AF_INET6, n, &addr6) > 0) {
+       family = AF_INET6;
+    } else
+#endif
+    {
+       family = AF_INET;
+       addr.s_addr = inet_addr(n);
+    }
+
+    for (i = 0; i < num_interfaces; i++) {
+       ifp = &interfaces[i];
+       if (ifp->family != family)
+           continue;
+       switch(family) {
+           case AF_INET:
+               if (ifp->addr.ip4.s_addr == addr.s_addr ||
+                   (ifp->addr.ip4.s_addr & ifp->netmask.ip4.s_addr)
+                   == addr.s_addr)
+                   return(TRUE);
+               break;
+#ifdef AF_INET6
+           case AF_INET6:
+               if (memcmp(ifp->addr.ip6.s6_addr, addr6.s6_addr,
+                   sizeof(addr6.s6_addr)) == 0)
+                   return(TRUE);
+               for (j = 0; j < sizeof(addr6.s6_addr); j++) {
+                   if ((ifp->addr.ip6.s6_addr[j] & ifp->netmask.ip6.s6_addr[j]) != addr6.s6_addr[j])
+                       break;
+               }
+               if (j == sizeof(addr6.s6_addr))
+                   return(TRUE);
+#endif /* AF_INET6 */
+       }
+    }
+
+    return(FALSE);
+}
+
+static int
+addr_matches_if_netmask(n, m)
+    char *n;
     char *m;
+{
+    int i;
     struct in_addr addr, mask;
+    struct interface *ifp;
+#ifdef AF_INET6
+    struct in6_addr addr6, mask6;
+    int j;
+#endif
+    int family;
 
-    /* If there's an explicit netmask, use it. */
-    if ((m = strchr(n, '/'))) {
-       *m++ = '\0';
+#ifdef AF_INET6
+    if (inet_pton(AF_INET6, n, &addr6) > 0)
+       family = AF_INET6;
+    else
+#endif
+    {
+       family = AF_INET;
        addr.s_addr = inet_addr(n);
+    }
+
+    if (family == AF_INET) {
        if (strchr(m, '.'))
            mask.s_addr = inet_addr(m);
        else {
@@ -384,24 +469,68 @@ addr_matches(n)
            mask.s_addr <<= i;
            mask.s_addr = htonl(mask.s_addr);
        }
-       *(m - 1) = '/';
-
-       for (i = 0; i < num_interfaces; i++)
-           if ((interfaces[i].addr.s_addr & mask.s_addr) == addr.s_addr)
-               return(TRUE);
-    } else {
-       addr.s_addr = inet_addr(n);
-
-       for (i = 0; i < num_interfaces; i++)
-           if (interfaces[i].addr.s_addr == addr.s_addr ||
-               (interfaces[i].addr.s_addr & interfaces[i].netmask.s_addr)
-               == addr.s_addr)
-               return(TRUE);
+    }
+#ifdef AF_INET6
+    else {
+       if (inet_pton(AF_INET6, m, &mask6) <= 0) {
+           j = atoi(m);
+           for (i = 0; i < 16; i++) {
+               if (j < i * 8)
+                   mask6.s6_addr[i] = 0;
+               else if (i * 8 + 8 <= j)
+                   mask6.s6_addr[i] = 0xff;
+               else
+                   mask6.s6_addr[i] = 0xff00 >> (j - i * 8);
+           }
+       }
+    }
+#endif /* AF_INET6 */
+
+    for (i = 0; i < num_interfaces; i++) {
+       ifp = &interfaces[i];
+       if (ifp->family != family)
+           continue;
+       switch(family) {
+           case AF_INET:
+               if ((ifp->addr.ip4.s_addr & mask.s_addr) == addr.s_addr)
+                   return(TRUE);
+#ifdef AF_INET6
+           case AF_INET6:
+               for (j = 0; j < sizeof(addr6.s6_addr); j++) {
+                   if ((ifp->addr.ip6.s6_addr[j] & mask6.s6_addr[j]) != addr6.s6_addr[j])
+                       break;
+               }
+               if (j == sizeof(addr6.s6_addr))
+                   return(TRUE);
+#endif /* AF_INET6 */
+       }
     }
 
     return(FALSE);
 }
 
+/*
+ * Returns TRUE if "n" is one of our ip addresses or if
+ * "n" is a network that we are on, else returns FALSE.
+ */
+int
+addr_matches(n)
+    char *n;
+{
+    char *m;
+    int retval;
+
+    /* If there's an explicit netmask, use it. */
+    if ((m = strchr(n, '/'))) {
+       *m++ = '\0';
+       retval = addr_matches_if_netmask(n, m);
+       *(m - 1) = '/';
+    } else
+       retval = addr_matches_if(n);
+
+    return(retval);
+}
+
 /*
  * Returns 0 if the hostname matches the pattern and non-zero otherwise.
  */
@@ -456,6 +585,7 @@ usergr_matches(group, user, pw)
     struct group *grp;
     gid_t pw_gid;
     char **cur;
+    int i;
 
     /* make sure we have a valid usergroup, sudo style */
     if (*group++ != '%')
@@ -473,11 +603,19 @@ usergr_matches(group, user, pw)
     if (grp->gr_gid == pw_gid)
        return(TRUE);
 
-    /* check to see if user is explicitly listed in the group */
-    for (cur = grp->gr_mem; *cur; cur++) {
-       if (strcmp(*cur, user) == 0)
+    /*
+     * If the user has a supplementary group vector, check it first.
+     */
+    for (i = 0; i < user_ngroups; i++) {
+       if (grp->gr_gid == user_groups[i])
            return(TRUE);
     }
+    if (grp->gr_mem != NULL) {
+       for (cur = grp->gr_mem; *cur; cur++) {
+           if (strcmp(*cur, user) == 0)
+               return(TRUE);
+       }
+    }
 
     return(FALSE);
 }
@@ -494,11 +632,10 @@ netgr_matches(netgr, host, shost, user)
     char *shost;
     char *user;
 {
+    static char *domain;
 #ifdef HAVE_GETDOMAINNAME
-    static char *domain = (char *) -1;
-#else
-    static char *domain = NULL;
-#endif /* HAVE_GETDOMAINNAME */
+    static int initialized;
+#endif
 
     /* make sure we have a valid netgroup, sudo style */
     if (*netgr++ != '+')
@@ -506,12 +643,13 @@ netgr_matches(netgr, host, shost, user)
 
 #ifdef HAVE_GETDOMAINNAME
     /* get the domain name (if any) */
-    if (domain == (char *) -1) {
+    if (!initialized) {
        domain = (char *) emalloc(MAXHOSTNAMELEN);
        if (getdomainname(domain, MAXHOSTNAMELEN) == -1 || *domain == '\0') {
-           free(domain);
+           efree(domain);
            domain = NULL;
        }
+       initialized = 1;
     }
 #endif /* HAVE_GETDOMAINNAME */