# scripts.mit.edu httpd suexec patch
# Copyright (C) 2006, 2007, 2008  Jeff Arnold <jbarnold@mit.edu>,
#                                 Joe Presbrey <presbrey@mit.edu>,
#                                 Anders Kaseorg <andersk@mit.edu>,
#                                 Geoffrey Thomas <geofft@mit.edu>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
#
# See /COPYRIGHT in this repository for more information.
#
--- httpd-2.2.2/support/Makefile.in.old	2005-07-06 19:15:34.000000000 -0400
+++ httpd-2.2.2/support/Makefile.in	2007-01-20 17:12:51.000000000 -0500
@@ -60,7 +60,7 @@

 suexec_OBJECTS = suexec.lo
 suexec: $(suexec_OBJECTS)
-	$(LINK) $(suexec_OBJECTS)
+	$(LINK) -lselinux $(suexec_OBJECTS)

 htcacheclean_OBJECTS = htcacheclean.lo
 htcacheclean: $(htcacheclean_OBJECTS)
--- httpd-2.2.2/configure.in.old	2007-07-17 10:48:25.000000000 -0400
+++ httpd-2.2.2/configure.in	2008-08-29 08:15:41.000000000 -0400
@@ -559,6 +559,10 @@
 APACHE_HELP_STRING(--with-suexec-userdir,User subdirectory),[
   AC_DEFINE_UNQUOTED(AP_USERDIR_SUFFIX, "$withval", [User subdirectory] ) ] )
 
+AC_ARG_WITH(suexec-trusteddir,
+APACHE_HELP_STRING(--with-suexec-trusteddir,Trusted SuExec directory),[
+  AC_DEFINE_UNQUOTED(AP_TRUSTED_DIRECTORY, "$withval", [Trusted SuExec directory] ) ] )
+
 AC_ARG_WITH(suexec-docroot,
 APACHE_HELP_STRING(--with-suexec-docroot,SuExec root directory),[
   AC_DEFINE_UNQUOTED(AP_DOC_ROOT, "$withval", [SuExec root directory] ) ] )
--- httpd-2.2.11/support/suexec.c.old	2008-11-30 10:47:31.000000000 -0500
+++ httpd-2.2.11/support/suexec.c	2009-06-08 09:02:17.000000000 -0400
@@ -30,6 +30,9 @@
  *
  */
 
+#define STATIC_CAT_PATH "/usr/bin/static-cat"
+#define PHP_PATH "/usr/bin/php-cgi"
+
 #include "apr.h"
 #include "ap_config.h"
 #include "suexec.h"
@@ -46,6 +49,7 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#include <selinux/selinux.h>
 
 #ifdef HAVE_PWD_H
 #include <pwd.h>
@@ -95,6 +99,7 @@
 {
     /* variable name starts with */
     "HTTP_",
+    "HTTPS_",
     "SSL_",
 
     /* variable name is */
@@ -245,9 +250,108 @@
     environ = cleanenv;
 }
 
+static const char *static_extensions[] = {
+    "html",
+    "css",
+    "gif",
+    "jpg",
+    "png",
+    "htm",
+    "jpeg",
+    "js",
+    "ico",
+    "xml",
+    "xsl",
+    "tiff",
+    "tif",
+    "tgz",
+    "tar",
+    "jar",
+    "zip",
+    "pdf",
+    "ps",
+    "doc",
+    "xls",
+    "ppt",
+    "dot",
+    "docx",
+    "dotx",
+    "docm",
+    "dotm",
+    "xlt",
+    "xla",
+    "xlsx",
+    "xltx",
+    "xlsm",
+    "xltm",
+    "xlam",
+    "xlsb",
+    "pot",
+    "pps",
+    "ppa",
+    "pptx",
+    "potx",
+    "ppsx",
+    "ppam",
+    "pptm",
+    "potm",
+    "ppsm",
+    "swf",
+    "mp3",
+    "mov",
+    "wmv",
+    "mpg",
+    "mpeg",
+    "avi",
+    "il",
+    "xhtml",
+    "svg",
+    "xaml",
+    "xap",
+    "wav",
+    "mid",
+    "midi",
+    "ttf",
+    "otf",
+    "odc",
+    "odb",
+    "odf",
+    "odg",
+    "otg",
+    "odi",
+    "odp",
+    "otp",
+    "ods",
+    "ots",
+    "odt",
+    "odm",
+    "ott",
+    "oth",
+    NULL
+};
+
+static int is_static_extension(const char *file)
+{
+    const char *extension = strrchr(file, '.');
+    const char **p;
+    if (extension == NULL) return 0;
+    for (p = static_extensions; *p; ++p) {
+        if (strcasecmp(extension + 1, *p) == 0) return 1;
+    }
+    return 0;
+}
+
+static int is_php_extension(const char *file)
+{
+    const char *extension = strrchr(file, '.');
+    if (extension == NULL) return 0;
+    return strcmp(extension + 1, "php") == 0;
+}
+
 int main(int argc, char *argv[])
 {
     int userdir = 0;        /* ~userdir flag             */
+    int trusteddir = 0;     /* TRUSTED_DIRECTORY flag    */
     uid_t uid;              /* user information          */
     gid_t gid;              /* target group placeholder  */
     char *target_uname;     /* target user name          */
@@ -268,6 +368,7 @@
      * Start with a "clean" environment
      */
     clean_env();
+    setenv("JAVA_TOOL_OPTIONS", "-Xmx128M", 1); /* scripts.mit.edu local hack */
 
     prog = argv[0];
     /*
@@ -350,6 +451,20 @@
 #endif /*_OSD_POSIX*/
 
     /*
+     * First check if this is an absolute path to the directory
+     * of trusted executables. These are supposed to be security
+     * audited to check parameters and validity on their own...
+     */
+    if (strstr(cmd, AP_TRUSTED_DIRECTORY) == cmd) {
+        if (strstr(cmd, "/../") != NULL) {
+            log_err("invalid command (%s)\n", cmd);
+            exit(104);
+        }
+        trusteddir = 1;
+        goto TRUSTED_DIRECTORY;
+    }
+
+    /*
      * Check for a leading '/' (absolute path) in the command to be executed,
      * or attempts to back up out of the current directory,
      * to protect against attacks.  If any are
@@ -371,6 +486,7 @@
         userdir = 1;
     }
 
+TRUSTED_DIRECTORY:
     /*
      * Error out if the target username is invalid.
      */
@@ -452,7 +568,7 @@
      * Error out if attempt is made to execute as root or as
      * a UID less than AP_UID_MIN.  Tsk tsk.
      */
-    if ((uid == 0) || (uid < AP_UID_MIN)) {
+    if ((uid == 0) || (uid < AP_UID_MIN && uid != 102)) { /* uid 102 = signup  */
         log_err("cannot run as forbidden uid (%d/%s)\n", uid, cmd);
         exit(107);
     }
@@ -484,6 +599,7 @@
         log_err("failed to setuid (%ld: %s)\n", uid, cmd);
         exit(110);
     }
+    setenv("HOME", target_homedir, 1);
 
     /*
      * Get the current working directory, as well as the proper
@@ -506,6 +637,21 @@
             log_err("cannot get docroot information (%s)\n", target_homedir);
             exit(112);
         }
+        size_t expected_len = strlen(target_homedir)+1+strlen(AP_USERDIR_SUFFIX)+1;
+        char *expected = malloc(expected_len);
+        snprintf(expected, expected_len, "%s/%s", target_homedir, AP_USERDIR_SUFFIX);
+        if (strncmp(cwd, expected, expected_len-1) != 0) {
+            log_err("error: file's directory not a subdirectory of user's home directory (%s, %s)\n", cwd, expected);
+            exit(114);
+        }
+    }
+    else if (trusteddir) {
+        if (((chdir(AP_TRUSTED_DIRECTORY)) != 0) ||
+            ((getcwd(dwd, AP_MAXPATH)) == NULL) |
+            ((chdir(cwd)) != 0)) {
+            log_err("cannot get docroot information (%s)\n", AP_TRUSTED_DIRECTORY);
+            exit(112);
+        }
     }
     else {
         if (((chdir(AP_DOC_ROOT)) != 0) ||
@@ -532,15 +678,17 @@
     /*
      * Error out if cwd is writable by others.
      */
+#if 0
     if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
         log_err("directory is writable by others: (%s)\n", cwd);
         exit(116);
     }
+#endif
 
     /*
      * Error out if we cannot stat the program.
      */
-    if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
+    if (((lstat(cmd, &prg_info)) != 0) /*|| (S_ISLNK(prg_info.st_mode))*/) {
         log_err("cannot stat program: (%s)\n", cmd);
         exit(117);
     }
@@ -548,10 +696,12 @@
     /*
      * Error out if the program is writable by others.
      */
+#if 0
     if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
         log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
         exit(118);
     }
+#endif
 
     /*
      * Error out if the file is setuid or setgid.
@@ -565,6 +715,7 @@
      * Error out if the target name/group is different from
      * the name/group of the cwd or the program.
      */
+#if 0
     if ((uid != dir_info.st_uid) ||
         (gid != dir_info.st_gid) ||
         (uid != prg_info.st_uid) ||
@@ -576,12 +727,14 @@
                 prg_info.st_uid, prg_info.st_gid);
         exit(120);
     }
+#endif
     /*
      * Error out if the program is not executable for the user.
      * Otherwise, she won't find any error in the logs except for
      * "[error] Premature end of script headers: ..."
      */
-    if (!(prg_info.st_mode & S_IXUSR)) {
+    if (!is_static_extension(cmd) && !is_php_extension(cmd) &&
+        !(prg_info.st_mode & S_IXUSR)) {
         log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
         exit(121);
     }
@@ -614,6 +767,30 @@
     /*
      * Execute the command, replacing our image with its own.
      */
+    if (is_static_extension(cmd)) {
+        if (setenv("PATH_TRANSLATED", cmd, 1) != 0) {
+            log_err("setenv failed\n");
+            exit(255);
+        }
+        execl(STATIC_CAT_PATH, STATIC_CAT_PATH, (const char *)NULL);
+        log_err("(%d)%s: static-cat exec failed (%s)\n", errno, strerror(errno), STATIC_CAT_PATH);
+        exit(255);
+    }
+    if (is_php_extension(cmd)) {
+        setenv("PHPRC", ".", 1);
+        argv[1] = PHP_PATH;
+        argv[2] = "-f";
+        /*
+         * argv[3] is the command to run. argv[4] is either an argument or
+         * already null. We don't want to pass any arguments through from
+         * Apache (since they're untrusted), so we chop off the remainder
+         * of argv here.
+         */
+        argv[4] = 0;
+        execv(PHP_PATH, &argv[1]);
+        log_err("(%d)%s: php exec failed (%s)\n", errno, strerror(errno), argv[1]);
+        exit(255);
+    }
 #ifdef NEED_HASHBANG_EMUL
     /* We need the #! emulation when we want to execute scripts */
     {
