Hi there,

I am working on Ubuntu 10.04 LTS and try get apache chrooting its vhosts. I followed the HowTo from

http://e.metaclarity.org/268/httpdsuexecchrootfastcgiphp/.

It describes the way to get apache chrooting two different vhosts with suexec,php5 and fastcgi and provides some patches to get suexec working with chroot _and_ php5.

In opposite to the HowTo I use the Debianpackage of apache, php and fastcgi from the Ubuntu repositories for 10.04, so the versions are different. The patches provided by the HowTo are included to the package buildsystem as one patch (I will attach it here).

Ok, I adapted the HowTo for my construction, die paths and names for domains and users are not the same as in the HowTo.

Static html pages work, but php5 not. The following will be seen in my logs:

Access:
213.178.67.141 - - [20/Jul/2011:15:45:37 +0200] "GET /index.php HTTP/1.1" 500 635 "-" "Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0"

Error:
[Wed Jul 20 15:46:11 2011] [error] [client 213.178.67.141] FastCGI: comm with (dynamic) server "/storage/60/2060/usr/local/wrappers/php5-fcgi" aborted: (first read) idle timeout (30 sec) [Wed Jul 20 15:46:11 2011] [error] [client 213.178.67.141] FastCGI: incomplete headers (0 bytes) received from server "/storage/60/2060/usr/local/wrappers/php5-fcgi" [Wed Jul 20 15:46:11 2011] [debug] mod_deflate.c(615): [client 213.178.67.141] Zlib: Compressed 618 to 378 : URL /cgi-wrapper/php5-fcgi/index.php

suexec:
[2011-07-20 15:45:37]: uid: (2060/2060) gid: (2060/2060) cmd: php5-fcgi
[2011-07-20 15:45:42]: uid: (2060/2060) gid: (2060/2060) cmd: php5-fcgi
[2011-07-20 15:45:47]: uid: (2060/2060) gid: (2060/2060) cmd: php5-fcgi
[2011-07-20 15:45:52]: uid: (2060/2060) gid: (2060/2060) cmd: php5-fcgi

I tried to attached strace to the apache processes, but the output did not gave any hints to me for further debbugging.

So, does anyone know or please gives me some hints for further debugging?


Many thanks in advance.

Jan-Hendrik Palic
#! /bin/sh /usr/share/dpatch/dpatch-run
##
## All lines beginning with `## DP:' are a description of the patch.
## DP: Fix race condition with chdir
## DP: Fix /var/www* being accepted as docroot instead of /var/www/*
## DP: (the same for public_html* instead of public_html/* )

diff -Nurd apache2-2.2.14/support/Makefile.in 
apache2-2.2.14_p/support/Makefile.in
--- apache2-2.2.14/support/Makefile.in  2005-07-07 01:15:34.000000000 +0200
+++ apache2-2.2.14_p/support/Makefile.in        2011-07-15 18:16:34.457753529 
+0200
@@ -59,9 +59,13 @@
        $(LINK) $(checkgid_LTFLAGS) $(checkgid_OBJECTS) $(PROGRAM_LDADD)
 
 suexec_OBJECTS = suexec.lo
-suexec: $(suexec_OBJECTS)
+suexec: $(suexec_OBJECTS) suexec-phpfcgi
        $(LINK) $(suexec_OBJECTS)
 
+suexec-phpfcgi_OBJECTS = suexec-phpfcgi.lo
+suexec-phpfcgi: $(suexec-phpfcgi_OBJECTS)
+       $(LINK) $(suexec-phpfcgi_OBJECTS)
+
 htcacheclean_OBJECTS = htcacheclean.lo
 htcacheclean: $(htcacheclean_OBJECTS)
        $(LINK) $(htcacheclean_LTFLAGS) $(htcacheclean_OBJECTS) $(PROGRAM_LDADD)
diff -Nurd apache2-2.2.14/support/suexec.c apache2-2.2.14_p/support/suexec.c
--- apache2-2.2.14/support/suexec.c     2009-08-28 16:37:37.000000000 +0200
+++ apache2-2.2.14_p/support/suexec.c   2011-07-15 18:16:27.369752955 +0200
@@ -259,6 +259,7 @@
     char *cmd;              /* command to be executed    */
     char cwd[AP_MAXPATH];   /* current working directory */
     char dwd[AP_MAXPATH];   /* docroot working directory */
+    char nwd[AP_MAXPATH];   /* docroot working directory */
     struct passwd *pw;      /* password entry holder     */
     struct group *gr;       /* group entry holder        */
     struct stat dir_info;   /* directory info holder     */
@@ -466,6 +467,38 @@
         exit(108);
     }
 
+    int striplen = strlen (target_homedir);
+
+    char* tlen = strchr(target_homedir, '/');
+    char* hlen = strchr(tlen+1, '/');
+    char* ulen = strchr(hlen+1, '/');
+    char* chroot_dir = strndup(target_homedir, ulen-target_homedir);
+    char* pt = getenv("PATH_TRANSLATED");
+    if (pt != 0) {
+      setenv("PATH_TRANSLATED", pt + (ulen - target_homedir), 1);
+    }
+
+    setenv("DOCUMENT_ROOT", "/", 1);
+
+    if (getcwd(nwd, AP_MAXPATH) == NULL) {
+        log_err("cannot get current working directory (prechroot)\n");
+        exit(111);
+    }
+
+    char* trunc_nwd = nwd+(ulen-target_homedir);
+
+    if (chdir(chroot_dir)) {
+        log_err("crit: can't chdir to chroot dir (%s)",chroot_dir);
+        exit(121);
+    }
+
+    if (chroot(chroot_dir) != 0) {
+      log_err("emerg: failed to chroot (%s, %s)\n", chroot_dir, cmd);
+      exit(122);
+    }
+
+    chdir (trunc_nwd);
+
     /*
      * Change UID/GID here so that the following tests work over NFS.
      *
diff -Nurd apache2-2.2.14/support/suexec.h apache2-2.2.14_p/support/suexec.h 
--- apache2-2.2.14/support/suexec.h     2006-07-12 05:38:44.000000000 +0200
+++ apache2-2.2.14_p/support/suexec.h   2011-07-19 15:49:18.054095576 +0200
@@ -36,7 +36,7 @@
  *               this program.
  */
 #ifndef AP_HTTPD_USER
-#define AP_HTTPD_USER "www"
+#define AP_HTTPD_USER "www-data"
 #endif
 
 /*
@@ -86,7 +86,7 @@
  *             debugging purposes.
  */
 #ifndef AP_LOG_EXEC
-#define AP_LOG_EXEC DEFAULT_EXP_LOGFILEDIR "/suexec_log" /* Need me? */
+#define AP_LOG_EXEC "/var/log/apache2/suexec_log" /* Need me? */
 #endif
 
 /*
@@ -95,8 +95,9 @@
  *             that can be used for suEXEC behavior.
  */
 #ifndef AP_DOC_ROOT
-#define AP_DOC_ROOT DEFAULT_EXP_HTDOCSDIR
+/*#define AP_DOC_ROOT DEFAULT_EXP_HTDOCSDIR*/
 #endif
+#define AP_DOC_ROOT "/"
 
 /*
  * SAFE_PATH -- Define a safe PATH environment to pass to CGI executables.
diff -Nurd apache2-2.2.14/support/suexec-phpfcgi.c 
apache2-2.2.14_p/support/suexec-phpfcgi.c
--- apache2-2.2.14/support/suexec-phpfcgi.c     1970-01-01 01:00:00.000000000 
+0100
+++ apache2-2.2.14_p/support/suexec-phpfcgi.c   2011-07-15 18:16:21.245753152 
+0200
@@ -0,0 +1,660 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
+ *
+ ***********************************************************************
+ *
+ * NOTE! : DO NOT edit this code!!!  Unless you know what you are doing,
+ *         editing this code might open up your system in unexpected
+ *         ways to would-be crackers.  Every precaution has been taken
+ *         to make this code as safe as possible; alter it at your own
+ *         risk.
+ *
+ ***********************************************************************
+ *
+ *
+ */
+
+#include "apr.h"
+#include "ap_config.h"
+#include "suexec.h"
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <string.h>
+#include <time.h>
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+/*
+ ***********************************************************************
+ * There is no initgroups() in QNX, so I believe this is safe :-)
+ * Use cc -osuexec -3 -O -mf -DQNX suexec.c to compile.
+ *
+ * May 17, 1997.
+ * Igor N. Kovalenko -- infoh mail.wplus.net
+ ***********************************************************************
+ */
+
+#if defined(NEED_INITGROUPS)
+int initgroups(const char *name, gid_t basegid)
+{
+    /* QNX and MPE do not appear to support supplementary groups. */
+    return 0;
+}
+#endif
+
+#if defined(SUNOS4)
+extern char *sys_errlist[];
+#define strerror(x) sys_errlist[(x)]
+#endif
+
+#if defined(PATH_MAX)
+#define AP_MAXPATH PATH_MAX
+#elif defined(MAXPATHLEN)
+#define AP_MAXPATH MAXPATHLEN
+#else
+#define AP_MAXPATH 8192
+#endif
+
+#define AP_ENVBUF 256
+
+extern char **environ;
+static FILE *log = NULL;
+
+static const char *const safe_env_lst[] =
+{
+    /* variable name starts with */
+    "HTTP_",
+    "SSL_",
+
+    /* variable name is */
+    "AUTH_TYPE=",
+    "CONTENT_LENGTH=",
+    "CONTENT_TYPE=",
+    "DATE_GMT=",
+    "DATE_LOCAL=",
+    "DOCUMENT_NAME=",
+    "DOCUMENT_PATH_INFO=",
+    "DOCUMENT_ROOT=",
+    "DOCUMENT_URI=",
+    "GATEWAY_INTERFACE=",
+    "HTTPS=",
+    "LAST_MODIFIED=",
+    "PATH_INFO=",
+    "PATH_TRANSLATED=",
+    "QUERY_STRING=",
+    "QUERY_STRING_UNESCAPED=",
+    "REMOTE_ADDR=",
+    "REMOTE_HOST=",
+    "REMOTE_IDENT=",
+    "REMOTE_PORT=",
+    "REMOTE_USER=",
+    "REDIRECT_HANDLER=",
+    "REDIRECT_QUERY_STRING=",
+    "REDIRECT_REMOTE_USER=",
+    "REDIRECT_STATUS=",
+    "REDIRECT_URL=",
+    "REQUEST_METHOD=",
+    "REQUEST_URI=",
+    "SCRIPT_FILENAME=",
+    "SCRIPT_NAME=",
+    "SCRIPT_URI=",
+    "SCRIPT_URL=",
+    "SERVER_ADMIN=",
+    "SERVER_NAME=",
+    "SERVER_ADDR=",
+    "SERVER_PORT=",
+    "SERVER_PROTOCOL=",
+    "SERVER_SIGNATURE=",
+    "SERVER_SOFTWARE=",
+    "UNIQUE_ID=",
+    "USER_NAME=",
+    "TZ=",
+    NULL
+};
+
+
+static void err_output(int is_error, const char *fmt, va_list ap)
+{
+#ifdef AP_LOG_EXEC
+    time_t timevar;
+    struct tm *lt;
+
+    if (!log) {
+        if ((log = fopen(AP_LOG_EXEC, "a")) == NULL) {
+            fprintf(stderr, "suexec failure: could not open log file\n");
+            perror("fopen");
+            exit(1);
+        }
+    }
+
+    if (is_error) {
+        fprintf(stderr, "suexec policy violation: see suexec log for more "
+                        "details\n");
+    }
+
+    time(&timevar);
+    lt = localtime(&timevar);
+
+    fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ",
+            lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
+            lt->tm_hour, lt->tm_min, lt->tm_sec);
+
+    vfprintf(log, fmt, ap);
+
+    fflush(log);
+#endif /* AP_LOG_EXEC */
+    return;
+}
+
+static void log_err(const char *fmt,...)
+{
+#ifdef AP_LOG_EXEC
+    va_list ap;
+
+    va_start(ap, fmt);
+    err_output(1, fmt, ap); /* 1 == is_error */
+    va_end(ap);
+#endif /* AP_LOG_EXEC */
+    return;
+}
+
+static void log_no_err(const char *fmt,...)
+{
+#ifdef AP_LOG_EXEC
+    va_list ap;
+
+    va_start(ap, fmt);
+    err_output(0, fmt, ap); /* 0 == !is_error */
+    va_end(ap);
+#endif /* AP_LOG_EXEC */
+    return;
+}
+
+static void clean_env(void)
+{
+    char pathbuf[512];
+    char **cleanenv;
+    char **ep;
+    int cidx = 0;
+    int idx;
+
+    /* While cleaning the environment, the environment should be clean.
+     * (e.g. malloc() may get the name of a file for writing debugging info.
+     * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd.  Sprintf() may be
+     * susceptible to bad locale settings....)
+     * (from PR 2790)
+     */
+    char **envp = environ;
+    char *empty_ptr = NULL;
+
+    environ = &empty_ptr; /* VERY safe environment */
+
+    if ((cleanenv = (char **) calloc(AP_ENVBUF, sizeof(char *))) == NULL) {
+        log_err("failed to malloc memory for environment\n");
+        exit(120);
+    }
+
+    sprintf(pathbuf, "PATH=%s", AP_SAFE_PATH);
+    cleanenv[cidx] = strdup(pathbuf);
+    cidx++;
+
+    for (ep = envp; *ep && cidx < AP_ENVBUF-1; ep++) {
+        for (idx = 0; safe_env_lst[idx]; idx++) {
+            if (!strncmp(*ep, safe_env_lst[idx],
+                         strlen(safe_env_lst[idx]))) {
+                cleanenv[cidx] = *ep;
+                cidx++;
+                break;
+            }
+        }
+    }
+
+    cleanenv[cidx] = NULL;
+
+    environ = cleanenv;
+}
+
+int main(int argc, char *argv[])
+{
+    int userdir = 0;        /* ~userdir flag             */
+    uid_t uid;              /* user information          */
+    gid_t gid;              /* target group placeholder  */
+    char *target_uname;     /* target user name          */
+    char *target_gname;     /* target group name         */
+    char *target_homedir;   /* target home directory     */
+    char *actual_uname;     /* actual user name          */
+    char *actual_gname;     /* actual group name         */
+    char *prog;             /* name of this program      */
+    char *cmd;              /* command to be executed    */
+    char cwd[AP_MAXPATH];   /* current working directory */
+    char dwd[AP_MAXPATH];   /* docroot working directory */
+    char nwd[AP_MAXPATH];   /* after-chroot working dir  */
+    struct passwd *pw;      /* password entry holder     */
+    struct group *gr;       /* group entry holder        */
+    struct stat dir_info;   /* directory info holder     */
+    struct stat prg_info;   /* program info holder       */
+
+    /*
+     * Start with a "clean" environment
+     */
+    clean_env();
+
+    prog = argv[0];
+    /*
+     * Check existence/validity of the UID of the user
+     * running this program.  Error out if invalid.
+     */
+    uid = getuid();
+    if ((pw = getpwuid(uid)) == NULL) {
+        log_err("crit: invalid uid: (%ld)\n", uid);
+        exit(102);
+    }
+    /*
+     * See if this is a 'how were you compiled' request, and
+     * comply if so.
+     */
+    if ((argc > 1)
+        && (! strcmp(argv[1], "-V"))
+        && ((uid == 0)
+#ifdef _OSD_POSIX
+        /* User name comparisons are case insensitive on BS2000/OSD */
+            || (! strcasecmp(AP_HTTPD_USER, pw->pw_name)))
+#else  /* _OSD_POSIX */
+            || (! strcmp(AP_HTTPD_USER, pw->pw_name)))
+#endif /* _OSD_POSIX */
+        ) {
+#ifdef AP_DOC_ROOT
+        fprintf(stderr, " -D AP_DOC_ROOT=\"%s\"\n", AP_DOC_ROOT);
+#endif
+#ifdef AP_GID_MIN
+        fprintf(stderr, " -D AP_GID_MIN=%d\n", AP_GID_MIN);
+#endif
+#ifdef AP_HTTPD_USER
+        fprintf(stderr, " -D AP_HTTPD_USER=\"%s\"\n", AP_HTTPD_USER);
+#endif
+#ifdef AP_LOG_EXEC
+        fprintf(stderr, " -D AP_LOG_EXEC=\"%s\"\n", AP_LOG_EXEC);
+#endif
+#ifdef AP_SAFE_PATH
+        fprintf(stderr, " -D AP_SAFE_PATH=\"%s\"\n", AP_SAFE_PATH);
+#endif
+#ifdef AP_SUEXEC_UMASK
+        fprintf(stderr, " -D AP_SUEXEC_UMASK=%03o\n", AP_SUEXEC_UMASK);
+#endif
+#ifdef AP_UID_MIN
+        fprintf(stderr, " -D AP_UID_MIN=%d\n", AP_UID_MIN);
+#endif
+#ifdef AP_USERDIR_SUFFIX
+        fprintf(stderr, " -D AP_USERDIR_SUFFIX=\"%s\"\n", AP_USERDIR_SUFFIX);
+#endif
+        exit(0);
+    }
+    /*
+     * If there are a proper number of arguments, set
+     * all of them to variables.  Otherwise, error out.
+     */
+    if (argc < 4) {
+        log_err("too few arguments\n");
+        exit(101);
+    }
+    target_uname = argv[1];
+    target_gname = argv[2];
+    cmd = argv[3];
+
+    /*
+     * Check to see if the user running this program
+     * is the user allowed to do so as defined in
+     * suexec.h.  If not the allowed user, error out.
+     */
+#ifdef _OSD_POSIX
+    /* User name comparisons are case insensitive on BS2000/OSD */
+    if (strcasecmp(AP_HTTPD_USER, pw->pw_name)) {
+        log_err("user mismatch (%s instead of %s)\n", pw->pw_name, 
AP_HTTPD_USER);
+        exit(103);
+    }
+#else  /*_OSD_POSIX*/
+    if (strcmp(AP_HTTPD_USER, pw->pw_name)) {
+        log_err("user mismatch (%s instead of %s)\n", pw->pw_name, 
AP_HTTPD_USER);
+        exit(103);
+    }
+#endif /*_OSD_POSIX*/
+
+    /*
+     * 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
+     * found, error out.  Naughty naughty crackers.
+     */
+    if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
+        || (strstr(cmd, "/../") != NULL)) {
+        log_err("invalid command (%s)\n", cmd);
+        exit(104);
+    }
+
+    /*
+     * Check to see if this is a ~userdir request.  If
+     * so, set the flag, and remove the '~' from the
+     * target username.
+     */
+    if (!strncmp("~", target_uname, 1)) {
+        target_uname++;
+        userdir = 1;
+    }
+
+    /*
+     * Error out if the target username is invalid.
+     */
+    if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
+        if ((pw = getpwnam(target_uname)) == NULL) {
+            log_err("invalid target user name: (%s)\n", target_uname);
+            exit(105);
+        }
+    }
+    else {
+        if ((pw = getpwuid(atoi(target_uname))) == NULL) {
+            log_err("invalid target user id: (%s)\n", target_uname);
+            exit(121);
+        }
+    }
+
+    /*
+     * Error out if the target group name is invalid.
+     */
+    if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
+        if ((gr = getgrnam(target_gname)) == NULL) {
+            log_err("invalid target group name: (%s)\n", target_gname);
+            exit(106);
+        }
+    }
+    else {
+        if ((gr = getgrgid(atoi(target_gname))) == NULL) {
+            log_err("invalid target group id: (%s)\n", target_gname);
+            exit(106);
+        }
+    }
+    gid = gr->gr_gid;
+    actual_gname = strdup(gr->gr_name);
+
+#ifdef _OSD_POSIX
+    /*
+     * Initialize BS2000 user environment
+     */
+    {
+        pid_t pid;
+        int status;
+
+        switch (pid = ufork(target_uname)) {
+        case -1:    /* Error */
+            log_err("failed to setup bs2000 environment for user %s: %s\n",
+                    target_uname, strerror(errno));
+            exit(150);
+        case 0:     /* Child */
+            break;
+        default:    /* Father */
+            while (pid != waitpid(pid, &status, 0))
+                ;
+            /* @@@ FIXME: should we deal with STOP signals as well? */
+            if (WIFSIGNALED(status)) {
+                kill (getpid(), WTERMSIG(status));
+            }
+            exit(WEXITSTATUS(status));
+        }
+    }
+#endif /*_OSD_POSIX*/
+
+    /*
+     * Save these for later since initgroups will hose the struct
+     */
+    uid = pw->pw_uid;
+    actual_uname = strdup(pw->pw_name);
+    target_homedir = strdup(pw->pw_dir);
+
+    /*
+     * Log the transaction here to be sure we have an open log
+     * before we setuid().
+     */
+    log_no_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n",
+               target_uname, actual_uname,
+               target_gname, actual_gname,
+               cmd);
+
+    /*
+     * 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)) {
+        log_err("cannot run as forbidden uid (%d/%s)\n", uid, cmd);
+        exit(107);
+    }
+    /*
+     * Error out if attempt is made to execute as root group
+     * or as a GID less than AP_GID_MIN.  Tsk tsk.
+     */
+    if ((gid == 0) || (gid < AP_GID_MIN)) {
+        log_err("cannot run as forbidden gid (%d/%s)\n", gid, cmd);
+        exit(108);
+    }
+
+
+    int striplen = strlen (target_homedir);
+
+    char* tlen = strchr(target_homedir, '/');
+    char* hlen = strchr(tlen+1, '/');
+    char* ulen = strchr(hlen+1, '/');
+    char* chroot_dir = strndup(target_homedir, ulen-target_homedir);
+    char* pt = getenv("PATH_TRANSLATED");
+    if (pt != 0) {
+      setenv("PATH_TRANSLATED", pt + (ulen - target_homedir), 1);
+    }
+
+    setenv("DOCUMENT_ROOT", "/", 1);
+
+    if (getcwd(nwd, AP_MAXPATH) == NULL) {
+        log_err("cannot get current working directory (prechroot)\n");
+        exit(111);
+    }
+
+    char* trunc_nwd = nwd+(ulen-target_homedir);
+
+    if (chdir(chroot_dir)) {
+        log_err("crit: can't chdir to chroot dir (%s)",chroot_dir);
+        exit(121);
+    }
+
+    if (chroot(chroot_dir) != 0) {
+      log_err("emerg: failed to chroot (%s, %s)\n", chroot_dir, cmd);
+      exit(122);
+    }
+
+    chdir (trunc_nwd);
+
+    /*
+     * Change UID/GID here so that the following tests work over NFS.
+     *
+     * Initialize the group access list for the target user,
+     * and setgid() to the target group. If unsuccessful, error out.
+     */
+    if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
+        log_err("failed to setgid (%ld: %s)\n", gid, cmd);
+        exit(109);
+    }
+
+    /*
+     * setuid() to the target user.  Error out on fail.
+     */
+    if ((setuid(uid)) != 0) {
+        log_err("failed to setuid (%ld: %s)\n", uid, cmd);
+        exit(110);
+    }
+
+    /*
+     * Get the current working directory, as well as the proper
+     * document root (dependant upon whether or not it is a
+     * ~userdir request).  Error out if we cannot get either one,
+     * or if the current working directory is not in the docroot.
+     * Use chdir()s and getcwd()s to avoid problems with symlinked
+     * directories.  Yuck.
+     */
+    if (getcwd(cwd, AP_MAXPATH) == NULL) {
+        log_err("cannot get current working directory\n");
+        exit(111);
+    }
+
+    if (((chdir(getenv("DOCUMENT_ROOT"))) != 0) ||
+        ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
+        ((chdir(cwd)) != 0)) {
+        log_err("cannot get docroot information (%s)\n", AP_DOC_ROOT);
+        exit(113);
+    }
+
+    if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
+        log_err("command not in docroot (%s/%s)\n", cwd, cmd);
+        exit(114);
+    }
+
+    /*
+     * Stat the cwd and verify it is a directory, or error out.
+     */
+    if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
+        log_err("cannot stat directory: (%s)\n", cwd);
+        exit(115);
+    }
+
+    /*
+     * Error out if cwd is writable by others.
+     */
+    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);
+    }
+
+    /*
+     * Error out if we cannot stat the program.
+     */
+    if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
+        log_err("cannot stat program: (%s)\n", cmd);
+        exit(117);
+    }
+
+    /*
+     * Error out if the program is writable by others.
+     */
+    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);
+    }
+
+    /*
+     * Error out if the file is setuid or setgid.
+     */
+    if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
+        log_err("file is either setuid or setgid: (%s/%s)\n", cwd, cmd);
+        exit(119);
+    }
+
+    /*
+     * Error out if the target name/group is different from
+     * the name/group of the cwd or the program.
+     */
+/*    if ((uid != dir_info.st_uid) ||
+        (gid != dir_info.st_gid) ||
+        (uid != prg_info.st_uid) ||
+        (gid != prg_info.st_gid)) {
+        log_err("target uid/gid (%ld/%ld) mismatch "
+                "with directory (%ld/%ld) or program (%ld/%ld)\n",
+                uid, gid,
+                dir_info.st_uid, dir_info.st_gid,
+                prg_info.st_uid, prg_info.st_gid);
+        exit(120);
+    }*/
+    /*
+     * 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)) {
+        log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
+        exit(121);
+    }
+
+#ifdef AP_SUEXEC_UMASK
+    /*
+     * umask() uses inverse logic; bits are CLEAR for allowed access.
+     */
+    if ((~AP_SUEXEC_UMASK) & 0022) {
+        log_err("notice: AP_SUEXEC_UMASK of %03o allows "
+                "write permission to group and/or other\n", AP_SUEXEC_UMASK);
+    }
+    umask(AP_SUEXEC_UMASK);
+#endif /* AP_SUEXEC_UMASK */
+
+    /*
+     * Be sure to close the log file so the CGI can't
+     * mess with it.  If the exec fails, it will be reopened
+     * automatically when log_err is called.  Note that the log
+     * might not actually be open if AP_LOG_EXEC isn't defined.
+     * However, the "log" cell isn't ifdef'd so let's be defensive
+     * and assume someone might have done something with it
+     * outside an ifdef'd AP_LOG_EXEC block.
+     */
+    if (log != NULL) {
+        fclose(log);
+        log = NULL;
+    }
+
+    /*
+     * Execute the command, replacing our image with its own.
+     */
+#ifdef NEED_HASHBANG_EMUL
+    /* We need the #! emulation when we want to execute scripts */
+    {
+        extern char **environ;
+
+        ap_execve(cmd, &argv[3], environ);
+    }
+#else /*NEED_HASHBANG_EMUL*/
+    execv(cmd, &argv[3]);
+#endif /*NEED_HASHBANG_EMUL*/
+
+    /*
+     * (I can't help myself...sorry.)
+     *
+     * Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
+     * EARTH-shattering kaboom!
+     *
+     * Oh well, log the failure and error out.
+     */
+    log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd);
+    exit(255);
+}

---------------------------------------------------------------------
The official User-To-User support forum of the Apache HTTP Server Project.
See <URL:http://httpd.apache.org/userslist.html> for more info.
To unsubscribe, e-mail: users-unsubscr...@httpd.apache.org
   "   from the digest: users-digest-unsubscr...@httpd.apache.org
For additional commands, e-mail: users-h...@httpd.apache.org

Reply via email to