0.6.0-1 release
[manu/suphp.git] / src / apache / mod_suphp.c
index 878169c..357719d 100644 (file)
@@ -1,68 +1,25 @@
-/* ====================================================================
- * The Apache Software License, Version 1.1
- *
- * Copyright (c) 2000 The Apache Software Foundation.  All rights
- * reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * 3. The end-user documentation included with the redistribution,
- *    if any, must include the following acknowledgment:
- *       "This product includes software developed by the
- *        Apache Software Foundation (http://www.apache.org/)."
- *    Alternately, this acknowledgment may appear in the software itself,
- *    if and wherever such third-party acknowledgments normally appear.
- *
- * 4. The names "Apache" and "Apache Software Foundation" must
- *    not be used to endorse or promote products derived from this
- *    software without prior written permission. For written
- *    permission, please contact apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache",
- *    nor may "Apache" appear in their name, without prior written
- *    permission of the Apache Software Foundation.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- * Portions of this software are based upon public domain software
- * originally written at the National Center for Supercomputing Applications,
- * University of Illinois, Urbana-Champaign.
- */
-
 /*
- * mod_suphp is based on mod_cgi from the original Apache sources
- * Code for authorization environment variables is partly taken from mod_php
- *
- * mod_suphp was written by Sebastian Marsching <sebastian@marsching.com>
- * Feel free to contact me if you have bug-reports or suggestions
- */
+    suPHP - (c)2002-2005 Sebastian Marsching <sebastian@marsching.com>
+
+    This file is part of suPHP.
+
+    suPHP 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.
+
+    suPHP 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 suPHP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+
+#define CORE_PRIVATE
 
 #include "httpd.h"
 #include "http_config.h"
 #include "http_main.h"
 #include "http_log.h"
 #include "util_script.h"
-#include "http_conf_globals.h"
 
-#ifndef PATH_TO_SUPHP
-#define PATH_TO_SUPHP "/usr/sbin/suphp"
-#endif
-
-module MODULE_VAR_EXPORT suphp_module;
 
-/* KLUDGE --- for back-combatibility, we don't have to check ExecCGI
- * in ScriptAliased directories, which means we need to know if this
- * request came through ScriptAlias or not... so the Alias module
- * leaves a note for us.
- */
+#define SUPHP_CONFIG_MODE_SERVER 1
+#define SUPHP_CONFIG_MODE_DIRECTORY 2
 
-static int is_scriptaliased(request_rec *r)
-{
-    const char *t = ap_table_get(r->notes, "alias-forced-type");
-    return t && (!strcasecmp(t, "cgi-script"));
-}
-
-
-/*
- * We have to use an own version of ap_call_exec() because suExec
- * does not allow to execute setuid-root programs
- */
-
-static char **suphp_create_argv(pool *p, char *path, char *user, char *group,
-                          char *av0, const char *args)
-{
-    int x, numwords;
-    char **av;
-    char *w;
-    int idx = 0;
-
-    /* count the number of keywords */
-
-    for (x = 0, numwords = 1; args[x]; x++) {
-        if (args[x] == '+') {
-            ++numwords;
-        }
-    }
-
-    if (numwords > APACHE_ARG_MAX - 5) {
-        numwords = APACHE_ARG_MAX - 5;  /* Truncate args to prevent overrun */
-    }
-    av = (char **) ap_palloc(p, (numwords + 5) * sizeof(char *));
-
-    if (path) {
-        av[idx++] = path;
-    }
-    if (user) {
-        av[idx++] = user;
-    }
-    if (group) {
-        av[idx++] = group;
-    }
+#define SUPHP_ENGINE_OFF 0
+#define SUPHP_ENGINE_ON 1
+#define SUPHP_ENGINE_UNDEFINED 2
 
-    av[idx++] = av0;
+#ifndef SUPHP_PATH_TO_SUPHP
+#define SUPHP_PATH_TO_SUPHP "/usr/sbin/suphp"
+#endif
 
-    for (x = 1; x <= numwords; x++) {
-        w = ap_getword_nulls(p, &args, '+');
-        ap_unescape_url(w);
-        av[idx++] = ap_escape_shell_cmd(p, w);
-    }
-    av[idx] = NULL;
-    return av;
-}
 
-int suphp_call_exec(request_rec *r, child_info *pinfo, char *argv0,
-                            char **env, int shellcmd)
-{
-    int pid = 0;
+/* Module declaration */
+module MODULE_VAR_EXPORT suphp_module;
 
-/*
 
-#if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
-    defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
+/* Configuration structure */
+typedef struct {
+    int engine; // Status of suPHP_Engine
+    char *php_config;
+    int cmode;  // Server of directory configuration?
+#ifdef SUPHP_USE_USERGROUP
+    char *target_user;
+    char *target_group;
+#endif
+    table *handlers;
+} suphp_conf;
 
-    core_dir_config *conf;
-    conf = (core_dir_config *) ap_get_module_config(r->per_dir_config,
-                                                   &core_module);
 
-#endif
+/* Configuration mergers/creators */
 
-#ifdef RLIMIT_CPU
-    if (conf->limit_cpu != NULL) {
-        if ((setrlimit(RLIMIT_CPU, conf->limit_cpu)) != 0) {
-           ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
-                        "setrlimit: failed to set CPU usage limit");
-       }
-    }
-#endif
-#ifdef RLIMIT_NPROC
-    if (conf->limit_nproc != NULL) {
-        if ((setrlimit(RLIMIT_NPROC, conf->limit_nproc)) != 0) {
-           ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
-                        "setrlimit: failed to set process limit");
-       }
-    }
-#endif
-#if defined(RLIMIT_AS)
-    if (conf->limit_mem != NULL) {
-        if ((setrlimit(RLIMIT_AS, conf->limit_mem)) != 0) {
-           ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
-                        "setrlimit(RLIMIT_AS): failed to set memory "
-                        "usage limit");
-       }
-    }
-#elif defined(RLIMIT_DATA)
-    if (conf->limit_mem != NULL) {
-        if ((setrlimit(RLIMIT_DATA, conf->limit_mem)) != 0) {
-           ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
-                        "setrlimit(RLIMIT_DATA): failed to set memory "
-                        "usage limit");
-       }
-    }
-#elif defined(RLIMIT_VMEM)
-    if (conf->limit_mem != NULL) {
-        if ((setrlimit(RLIMIT_VMEM, conf->limit_mem)) != 0) {
-           ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
-                        "setrlimit(RLIMIT_VMEM): failed to set memory "
-                        "usage limit");
-       }
-    }
+static void *suphp_create_dir_config(pool *p, char *dir) {
+    suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
+    cfg->php_config = NULL;
+    cfg->engine = SUPHP_ENGINE_UNDEFINED;
+    cfg->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
+    
+#ifdef SUPHP_USE_USERGRPUP
+    cfg->target_user = NULL;
+    cfg->target_group = NULL;
 #endif
 
-*/
-
-//         execve("/usr/sbin/suphp",
-//                suphp_create_argv(r->pool, NULL, NULL, NULL, "suphp", r->args),
-//                env);
-           execle(PATH_TO_SUPHP, "suphp", NULL, env); 
-    return (pid);
+    /* Create table with 0 initial elements */
+    /* This size may be increased for performance reasons */
+    cfg->handlers = ap_make_table(p, 0);
+    
+    return (void *) cfg;
 }
 
+static void *suphp_merge_dir_config(pool *p, void *base, void *overrides) {
+    suphp_conf *parent = (suphp_conf *) base;
+    suphp_conf *child = (suphp_conf *) overrides;
+    suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
 
+    merged->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
+    
+    if (child->php_config)
+       merged->php_config = ap_pstrdup(p, child->php_config);
+    else if (parent->php_config)
+       merged->php_config = ap_pstrdup(p, parent->php_config);
+    else 
+       merged->php_config = NULL;
+    
+    if (child->engine != SUPHP_ENGINE_UNDEFINED)
+       merged->engine = child->engine;
+    else
+       merged->engine = parent->engine;
 
-/* Configuration stuff */
-
-#define DEFAULT_LOGBYTES 10385760
-#define DEFAULT_BUFBYTES 1024
-
-#define CONFIG_MODE_SERVER 1
-#define CONFIG_MODE_DIRECTORY 2
-#define CONFIG_MODE_COMBO 3    /* Shouldn't ever happen. */
-
-#define SUPHP_ENGINE_OFF 0
-#define SUPHP_ENGINE_ON 1
-#define SUPHP_ENGINE_UNDEFINED 2
+#ifdef SUPHP_USE_USERGROUP
+    if (child->target_user)
+       merged->target_user = ap_pstrdup(p, child->target_user);
+    else if (parent->target_user)
+       merged->target_user = ap_pstrdup(p, parent->target_user);
+    else
+       merged->target_user = NULL;
 
-typedef struct {
-    int cmode;
-    char *logname;
-    long logbytes;
-    int bufbytes;
-    int engine;
-    char *php_config;
-} suphp_conf;
+    if (child->target_group)
+       merged->target_group = ap_pstrdup(p, child->target_group);
+    else if (parent->target_group)
+       merged->target_group = ap_pstrdup(p, parent->target_group);
+    else
+       merged->target_group = NULL;
+#endif
+    
+    merged->handlers = ap_overlay_tables(p, child->handlers, parent->handlers);
 
-static void *suphp_create_dir_config(pool *p, char *dirspec)
-{
- suphp_conf *cfg;
- cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
- cfg->php_config = NULL;
- cfg->cmode = CONFIG_MODE_DIRECTORY;
- return (void *) cfg;
+    return (void *) merged;
 }
 
-static void *suphp_merge_dir_config(pool *p, void *base_conf, void *new_conf)
-{
- suphp_conf *parent = (suphp_conf *) base_conf;
- suphp_conf *child = (suphp_conf *) new_conf;
- suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
- merged->php_config = child->php_config ? child->php_config : parent->php_config;
- return merged;
-}
 
-static void *suphp_create_server_config(pool *p, server_rec *s)
-{
+static void *suphp_create_server_config(pool *p, server_rec *s) {
     suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
-
-    cfg->logname = NULL;
-    cfg->logbytes = DEFAULT_LOGBYTES;
-    cfg->bufbytes = DEFAULT_BUFBYTES;
+    
     cfg->engine = SUPHP_ENGINE_UNDEFINED;
+    cfg->cmode = SUPHP_CONFIG_MODE_SERVER;
+    
+#ifdef SUPHP_USE_USERGRPUP
+    cfg->target_user = NULL;
+    cfg->target_group = NULL;
+#endif
 
     return (void *) cfg;
 }
 
-static void *suphp_merge_server_config(pool *p, void *base_conf, void *new_conf)
-{
+
+static void *suphp_merge_server_config(pool *p, void *base, void *overrides) {
+    suphp_conf *parent = (suphp_conf *) base;
+    suphp_conf *child = (suphp_conf *) overrides;
     suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
-    suphp_conf *parent = (suphp_conf *) base_conf;
-    suphp_conf *child = (suphp_conf *) new_conf;
     
-    if (child->logname)
-    {
-     merged->logname = child->logname;
-     merged->logbytes = child->logbytes;
-     merged->bufbytes = child->bufbytes;
-    }
+    if (child->engine != SUPHP_ENGINE_UNDEFINED)
+       merged->engine = child->engine;
     else
-    {
-     merged->logname = parent->logname;
-     merged->logbytes = parent->logbytes;
-     merged->bufbytes = parent->bufbytes;
-    }
+       merged->engine = parent->engine;
 
-    if (child->engine != SUPHP_ENGINE_UNDEFINED)
-     merged->engine = child->engine;
+#ifdef SUPHP_USE_USERGROUP
+    if (child->target_user)
+       merged->target_user = ap_pstrdup(p, child->target_user);
+    else if (parent->target_user)
+       merged->target_user = ap_pstrdup(p, parent->target_user);
     else
-     merged->engine = parent->engine;
-    
-    return (void *) merged;
-}
+       merged->target_user = NULL;
 
-static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig, int flag)
-{
- suphp_conf *cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config, &suphp_module);
- if (flag)
-  cfg->engine = SUPHP_ENGINE_ON;
- else
-  cfg->engine = SUPHP_ENGINE_OFF;
- return NULL;
-}
+    if (child->target_group)
+       merged->target_group = ap_pstrdup(p, child->target_group);
+    else if (parent->target_group)
+       merged->target_group = ap_pstrdup(p, parent->target_group);
+    else
+       merged->target_group = NULL;
+#endif
 
-static const char *suphp_handle_cmd_config(cmd_parms *parms, void *mconfig, const char *arg)
-{
- suphp_conf *cfg = (suphp_conf *) mconfig;
- cfg->php_config = (char*)ap_pstrdup(parms->pool, arg);
- return NULL;
+    return (void *) merged;
 }
 
-static const command_rec suphp_cmds[] =
-{
-    {"suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF, FLAG, "Whether PHP is on or off, default is off"},
-    {"suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS, TAKE1, "Where the php.ini resists, default is to use PHP's default configuration"},
-    {NULL}
-};
 
-static int log_scripterror(request_rec *r, suphp_conf * conf, int ret,
-                          int show_errno, char *error)
-{
-    FILE *f;
-    struct stat finfo;
+/* Command handlers */
 
-    ap_log_rerror(APLOG_MARK, show_errno|APLOG_ERR, r, 
-               "%s: %s", error, r->filename);
+static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
+                                          int flag) {
+    suphp_conf *cfg;
 
-    if (!conf->logname ||
-       ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
-        &&   (finfo.st_size > conf->logbytes)) ||
-         ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
-                     "a")) == NULL)) {
-       return ret;
-    }
+    if (mconfig)
+       cfg = (suphp_conf *) mconfig;
+    else
+       cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config,
+                                                 &suphp_module);
+    
+    if (flag)
+       cfg->engine = SUPHP_ENGINE_ON;
+    else
+       cfg->engine = SUPHP_ENGINE_OFF;
 
-    /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
-    fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
-           r->args ? "?" : "", r->args ? r->args : "", r->protocol);
-    /* "%% 500 /usr/local/apache/cgi-bin */
-    fprintf(f, "%%%% %d %s\n", ret, r->filename);
+    return NULL;
+}
 
-    fprintf(f, "%%error\n%s\n", error);
 
-    ap_pfclose(r->pool, f);
-    return ret;
+static const char *suphp_handle_cmd_config(cmd_parms *cmd, void *mconfig,
+                                          const char *arg) {
+    suphp_conf *cfg = (suphp_conf *) mconfig;
+    
+    cfg->php_config = ap_pstrdup(cmd->pool, arg);
+    
+    return NULL;
 }
 
-static int log_script(request_rec *r, suphp_conf * conf, int ret,
-                 char *dbuf, const char *sbuf, BUFF *script_in, BUFF *script_err)
-{
-    array_header *hdrs_arr = ap_table_elts(r->headers_in);
-    table_entry *hdrs = (table_entry *) hdrs_arr->elts;
-    char argsbuffer[HUGE_STRING_LEN];
-    FILE *f;
-    int i;
-    struct stat finfo;
 
-    if (!conf->logname ||
-       ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
-        &&   (finfo.st_size > conf->logbytes)) ||
-         ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
-                     "a")) == NULL)) {
-       /* Soak up script output */
-       while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
-           continue;
-#if defined(WIN32) || defined(NETWARE)
-        /* Soak up stderr and redirect it to the error log.
-         * Script output to stderr is already directed to the error log
-         * on Unix, thanks to the magic of fork().
-         */
-        while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
-                          "%s", argsbuffer);            
-        }
-#else
-       while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
-           continue;
-#endif
-       return ret;
-    }
+#ifdef SUPHP_USE_USERGROUP
+static const char *suphp_handle_cmd_user_group(cmd_parms *cmd, void *mconfig,
+                                              const char *arg1, 
+                                              const char *arg2) {
+    suphp_conf *cfg;
 
-    /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
-    fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
-           r->args ? "?" : "", r->args ? r->args : "", r->protocol);
-    /* "%% 500 /usr/local/apache/cgi-bin" */
-    fprintf(f, "%%%% %d %s\n", ret, r->filename);
-
-    fputs("%request\n", f);
-    for (i = 0; i < hdrs_arr->nelts; ++i) {
-       if (!hdrs[i].key)
-           continue;
-       fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
-    }
-    if ((r->method_number == M_POST || r->method_number == M_PUT)
-       && *dbuf) {
-       fprintf(f, "\n%s\n", dbuf);
-    }
+    if (mconfig)
+       cfg = (suphp_conf *) mconfig;
+    else
+       cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);
+    
+    cfg->target_user = ap_pstrdup(cmd->pool, arg1);
+    cfg->target_group = ap_pstrdup(cmd->pool, arg2);
 
-    fputs("%response\n", f);
-    hdrs_arr = ap_table_elts(r->err_headers_out);
-    hdrs = (table_entry *) hdrs_arr->elts;
+    return NULL;
+}
+#endif
 
-    for (i = 0; i < hdrs_arr->nelts; ++i) {
-       if (!hdrs[i].key)
-           continue;
-       fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
-    }
 
-    if (sbuf && *sbuf)
-       fprintf(f, "%s\n", sbuf);
+static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig,
+                                               const char *arg) {
+    suphp_conf *cfg = (suphp_conf *) mconfig;
 
-    if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
-       fputs("%stdout\n", f);
-       fputs(argsbuffer, f);
-       while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
-           fputs(argsbuffer, f);
-       fputs("\n", f);
-    }
+    // Mark active handlers with '1'
+    ap_table_set(cfg->handlers, arg, "1");
 
-    if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
-       fputs("%stderr\n", f);
-       fputs(argsbuffer, f);
-       while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
-           fputs(argsbuffer, f);
-       fputs("\n", f);
-    }
+    return NULL;
+}
 
-    ap_bclose(script_in);
-    ap_bclose(script_err);
+static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd, 
+                                                  void *mconfig, 
+                                                  const char *arg) {
+    suphp_conf *cfg = (suphp_conf *) mconfig;
+    
+    // Mark deactivated handlers with '0'
+    ap_table_set(cfg->handlers, arg, "0");
 
-    ap_pfclose(r->pool, f);
-    return ret;
+    return NULL;
 }
 
-/****************************************************************
- *
- * Actual suPHP handling...
- */
-
 
-struct suphp_child_stuff {
-#ifdef TPF
-    TPF_FORK_CHILD t;
-#endif
-    request_rec *r;
-    int nph;
-    int debug;
-    char *argv0;
+/* Command table */
+
+static const command_rec suphp_cmds[] = {
+    {"suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF|ACCESS_CONF,
+     FLAG, "Whether suPHP is on or off, default is off"},
+    {"suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS, TAKE1, 
+     "Where the php.ini resides, default is the PHP default"},
+#ifdef SUPHP_USE_USERGROUP
+    {"suPHP_UserGroup", suphp_handle_cmd_user_group, NULL, 
+     RSRC_CONF|ACCESS_CONF, TAKE2, "User and group scripts shall be run as"},
+#endif 
+    {"suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, RSRC_CONF|ACCESS_CONF,
+     ITERATE, "Tells mod_suphp to handle these MIME-types"},
+    {"suphp_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF|ACCESS_CONF,
+     ITERATE, "Tells mod_suphp not to handle these MIME-types"},
+    {NULL}
 };
 
-static int suphp_child(void *child_stuff, child_info *pinfo)
-{
-    struct suphp_child_stuff *cld = (struct suphp_child_stuff *) child_stuff;
-    request_rec *r = cld->r;
-    char *argv0 = cld->argv0;
-    int child_pid;
-    const char *authorization=NULL;
-    char *tmp=NULL;
-    char *auth_user=NULL;
-    char *auth_password=NULL;
-    suphp_conf *cfg = (suphp_conf *) ap_get_module_config(r->per_dir_config, &suphp_module);
-
-#ifdef DEBUG_SUPHP
-#ifdef OS2
-    /* Under OS/2 need to use device con. */
-    FILE *dbg = fopen("con", "w");
-#else
-    FILE *dbg = fopen("/dev/tty", "w");
-#endif
-    int i;
-#endif
 
-    char **env;
+/* Helper function which is called when spawning child process */
 
-    RAISE_SIGSTOP(SUPHP_CHILD);
-#ifdef DEBUG_SUPHP
-    fprintf(dbg, "Attempting to exec %s as %ssuPHP child (argv0 = %s)\n",
-           r->filename, cld->nph ? "NPH " : "", argv0);
-#endif
+int suphp_child(void *rp, child_info *cinfo) {
+    request_rec *r = (request_rec *) rp;
+    core_dir_config *core_conf;
+    pool *p = r->main ? r->main->pool : r->pool;
+     
+    char **argv, **env;
 
-    /*
-     * Set environment variables with authorization information like mod_php
-     */
+    core_conf = (core_dir_config *) ap_get_module_config(
+       r->per_dir_config, &core_module);
     
-    if (r->headers_in)
-    {
-     authorization = ap_table_get(r->headers_in, "Authorization");
-    }
-    if (authorization 
-        && !strcasecmp(ap_getword(r->pool, &authorization, ' '), "Basic"))
-    {
-     tmp = ap_uudecode(r->pool, authorization);
-     auth_user = ap_getword_nulls_nc(r->pool, &tmp, ':');
-/*
-     if (auth_user)
-     {
-      auth_user = estrdup(auth_user);
-     }
-*/
-     auth_password = tmp;
-/*
-     if (auth_password)
-     {
-      auth_password = estrdup(auth_password);
-     }
-*/
-     if (auth_user)
-     {
-      ap_table_setn(r->subprocess_env, "PHP_AUTH_USER", auth_user);
-     }
-     if (auth_password)
-     {
-      ap_table_setn(r->subprocess_env, "PHP_AUTH_PW", auth_password);
-     }
-    }
-    
-    if (cfg->php_config)
-    {
-     ap_table_setn(r->subprocess_env, "PHP_CONFIG", cfg->php_config);
-    }
-     
-    ap_add_cgi_vars(r);
-    env = ap_create_environment(r->pool, r->subprocess_env);
+    /* We want to log output written to stderr */
+    ap_error_log2stderr(r->server);
 
-#ifdef DEBUG_SUPHP
-    fprintf(dbg, "Environment: \n");
-    for (i = 0; env[i]; ++i)
-       fprintf(dbg, "'%s'\n", env[i]);
-#endif
+    /* prepare argv for new process */
+    
+    argv = ap_palloc(p, 2 * sizeof(char *));
+    argv[0] = SUPHP_PATH_TO_SUPHP;
+    argv[1] = NULL;
 
+    /* prepare environment */
 
-    ap_chdir_file(r->filename);
-    if (!cld->debug)
-       ap_error_log2stderr(r->server);
+    env = ap_create_environment(p, r->subprocess_env);
 
-    /* Transumute outselves into the script.
-     * NB only ISINDEX scripts get decoded arguments.
-     */
+    /* We cannot use ap_call_exec because of the interference with suExec */
+    /* So we do everything ourselves */
+    /* Set resource limits from core config */
+   
+#ifdef RLIMIT_CPU
+    if (core_conf->limit_cpu != NULL) {
+       if ((setrlimit(RLIMIT_CPU, core_conf->limit_cpu)) != 0) {
+           ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
+                        "setrlimit: failed to set CPU usage limit");
+       }
+    }
+#endif /* RLIMIT_CPU */
+#ifdef RLIMIT_NPROC
+    if (core_conf->limit_nproc != NULL) {
+       if ((setrlimit(RLIMIT_NPROC, core_conf->limit_nproc)) != 0) {
+           ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
+                        "setrlimit: failed to set process limit");
+       }
+    }
+#endif /* RLIMIT_NPROC */
+#ifdef RLIMIT_AS
+    if (core_conf->limit_mem != NULL) {
+       if ((setrlimit(RLIMIT_AS, core_conf->limit_mem)) != 0) {
+           ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
+                        "setrlimit: failed to set memory limit");
+       }
+    }
+#endif /* RLIMIT_VMEM */
+#ifdef RLIMIT_DATA
+    if (core_conf->limit_mem != NULL) {
+       if ((setrlimit(RLIMIT_DATA, core_conf->limit_mem)) != 0) {
+           ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
+                        "setrlimit: failed to set memory limit");
+       }
+    }
+#endif /* RLIMIT_VMEM */
+#ifdef RLIMIT_VMEM
+    if (core_conf->limit_mem != NULL) {
+       if ((setrlimit(RLIMIT_VMEM, core_conf->limit_mem)) != 0) {
+           ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
+                        "setrlimit: failed to set memory limit");
+       }
+    }
+#endif /* RLIMIT_VMEM */
 
-#ifdef TPF
-    return (0);
-#else
+    /* mandatory cleanup before execution */
     ap_cleanup_for_exec();
+    
+    execve(SUPHP_PATH_TO_SUPHP, argv, env);
 
-    child_pid = suphp_call_exec(r, pinfo, argv0, env, 0);
-    return (child_pid);
-
-    /* Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
-     * EARTH-shattering kaboom!
-     *
-     * Oh, well.  Muddle through as best we can...
-     *
-     * Note that only stderr is available at this point, so don't pass in
-     * a server to aplog_error.
-     */
-
-    ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed", r->filename);
+    /* We are still here? Okay - exec failed */
+    ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed",
+                SUPHP_PATH_TO_SUPHP);
     exit(0);
     /* NOT REACHED */
     return (0);
-#endif  /* TPF */
 }
 
-static int suphp_handler(request_rec *r)
-{
-    int retval, nph, dbpos = 0;
-    char *argv0, *dbuf = NULL;
-    BUFF *script_out, *script_in, *script_err;
-    char argsbuffer[HUGE_STRING_LEN];
-    int is_included = !strcmp(r->protocol, "INCLUDED");
-    void *sconf = r->server->module_config;
-    suphp_conf *conf =
-    (suphp_conf *) ap_get_module_config(sconf, &suphp_module);
-    suphp_conf *mconf = (suphp_conf *) ap_get_module_config(r->server->module_config, &suphp_module);
-
-    struct suphp_child_stuff cld;
-
-    if ((mconf->engine == SUPHP_ENGINE_OFF) || (mconf->engine == SUPHP_ENGINE_UNDEFINED))
-     return DECLINED;
+
+/* Handlers */
+
+static int suphp_handler(request_rec *r) {
+    suphp_conf *sconf;
+    suphp_conf *dconf;
     
-    if (r->method_number == M_OPTIONS) {
-       /* 99 out of 100 CGI scripts, this is all they support */
-       r->allowed |= (1 << M_GET);
-       r->allowed |= (1 << M_POST);
+    struct stat finfo;
+    
+    int rv;
+    
+    char *auth_user = NULL;
+    char *auth_pass = NULL;
+
+    pool *p;
+
+    BUFF *script_in, *script_out, *script_err;
+
+    sconf = ap_get_module_config(r->server->module_config, &suphp_module);
+    dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
+
+    p = r->main ? r->main->pool : r->pool;
+
+    /* only handle request if mod_suphp is active for this handler */
+    /* check only first byte of value (second has to be \0) */
+    if ((ap_table_get(dconf->handlers, r->handler) == NULL)
+       || (*(ap_table_get(dconf->handlers, r->handler)) == '0'))
        return DECLINED;
-    }
 
-    if ((argv0 = strrchr(r->filename, '/')) != NULL)
-       argv0++;
-    else
-       argv0 = r->filename;
-
-//    nph = !(strncmp(argv0, "nph-", 4));
-    nph = 0;
-
-//    if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r))
-//     return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
-//                            "Options ExecCGI is off in this directory");
-//    if (nph && is_included)
-//     return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
-//                            "attempt to include NPH CGI script");
-//
-    if (r->finfo.st_mode == 0)
-       return log_scripterror(r, conf, NOT_FOUND, APLOG_NOERRNO,
-                              "script not found or unable to stat");
-    if (S_ISDIR(r->finfo.st_mode))
-       return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
-                              "attempt to invoke directory as script");
-
-    if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
-       return retval;
+    /* check if suPHP is enabled for this request */
 
-    ap_add_common_vars(r);
-    cld.argv0 = argv0;
-    cld.r = r;
-    cld.nph = nph;
-    cld.debug = conf->logname ? 1 : 0;
-#ifdef TPF
-    cld.t.filename = r->filename;
-    cld.t.subprocess_env = r->subprocess_env;
-    cld.t.prog_type = FORK_FILE;
-#endif   /* TPF */
-
-#ifdef CHARSET_EBCDIC
-    /* The included MIME headers must ALWAYS be in text/ebcdic format.
-     * Only after reading the MIME headers, we check the Content-Type
-     * and switch to the necessary conversion mode.
-     * Until then (and in case an nph- script was called), use the
-     * configured default conversion:
-     */
-    ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out);
-#endif /*CHARSET_EBCDIC*/
-
-    /*
-     * we spawn out of r->main if it's there so that we can avoid
-     * waiting for free_proc_chain to cleanup in the middle of an
-     * SSI request -djg
-     */
-    if (!ap_bspawn_child(r->main ? r->main->pool : r->pool, suphp_child,
-                        (void *) &cld, kill_after_timeout,
-                        &script_out, &script_in, &script_err)) {
-       ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
-                   "couldn't spawn child process: %s", r->filename);
+    if (((sconf->engine != SUPHP_ENGINE_ON)
+        && (dconf->engine != SUPHP_ENGINE_ON))
+       || ((sconf->engine == SUPHP_ENGINE_ON)
+           && (dconf->engine == SUPHP_ENGINE_OFF)))
+       return DECLINED;
+
+    /* check if file is existing and accessible */
+    
+    rv = stat(ap_pstrdup(p, r->filename), &finfo);
+    if (rv == 0) {
+       ; /* do nothing */
+    } else if (errno == EACCES) {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "access to %s denied",
+                     r->filename);
+       return HTTP_FORBIDDEN;
+    } else if (errno == ENOENT || errno == ENOTDIR) {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "File does not exist: %s",
+                     r->filename);
+       return HTTP_NOT_FOUND;
+    } else {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "could not get fileinfo: %s",
+                     r->filename);
+       return HTTP_NOT_FOUND;
+    }
+
+#ifdef SUPHP_USE_USERGROUP
+    if ((sconf->target_user == NULL || sconf->target_group == NULL)
+       && (dconf->target_user == NULL || dconf->target_group == NULL)) {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR, r, 
+                     "No user or group set - set suPHP_UserGroup");
        return HTTP_INTERNAL_SERVER_ERROR;
     }
+#endif /* SUPHP_USE_USERGROUP */
 
-    /* Transfer any put/post args, CERN style...
-     * Note that we already ignore SIGPIPE in the core server.
-     */
 
-    if (ap_should_client_block(r)) {
-       int dbsize, len_read;
+    /* prepare environment for new process */
+    
+    ap_add_common_vars(r);
+    ap_add_cgi_vars(r);
 
-       if (conf->logname) {
-           dbuf = ap_pcalloc(r->pool, conf->bufbytes + 1);
-           dbpos = 0;
-       }
+    ap_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
+    ap_table_unset(r->subprocess_env, "SUHP_AUTH_USER");
+    ap_table_unset(r->subprocess_env, "SUPHP_AUTH_PW");
+    
+#ifdef SUPHP_USE_USERGROUP
+    ap_table_unset(r->subprocess_env, "SUPHP_USER");
+    ap_table_unset(r->subprocess_env, "SUPHP_GROUP");
+#endif /* SUPHP_USE_USERGROUP */
 
-       ap_hard_timeout("copy script args", r);
+    if (dconf->php_config) {
+       ap_table_set(r->subprocess_env, "SUPHP_PHP_CONFIG", dconf->php_config);
+    }
 
-       while ((len_read =
-               ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)) > 0) {
-           if (conf->logname) {
-               if ((dbpos + len_read) > conf->bufbytes) {
-                   dbsize = conf->bufbytes - dbpos;
-               }
-               else {
-                   dbsize = len_read;
+    ap_table_set(r->subprocess_env, "SUPHP_HANDLER", r->handler);
+
+    if (r->headers_in) {
+       const char *auth;
+       auth = ap_table_get(r->headers_in, "Authorization");
+       if (auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0) {
+           char *user;
+           char *pass;
+           user = ap_pbase64decode(p, auth + 6);
+           if (user) {
+               pass = strchr(user, ':');
+               if (pass) {
+                   *pass++ = '\0';
+                   auth_user = ap_pstrdup(p, user);
+                   auth_pass = ap_pstrdup(p, pass);
                }
-               memcpy(dbuf + dbpos, argsbuffer, dbsize);
-               dbpos += dbsize;
            }
+       }
+    }
+    
+    if (auth_user && auth_pass) {
+       ap_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
+       ap_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
+    }
+
+#ifdef SUPHP_USE_USERGROUP
+    if (dconf->target_user) {
+       ap_table_set(r->subprocess_env, "SUPHP_USER", dconf->target_user);
+    } else {
+       ap_table_set(r->subprocess_env, "SUPHP_USER", sconf->target_user);
+    }
+
+    if (dconf->target_group) {
+       ap_table_set(r->subprocess_env, "SUPHP_GROUP", dconf->target_group);
+    } else {
+       ap_table_set(r->subprocess_env, "SUPHP_GROUP", sconf->target_group);
+    }
+#endif /* SUPHP_USE_USERGROUP */
+    
+    /* Fork child process */
+    
+    if (!ap_bspawn_child(p, suphp_child, (void *) r, kill_after_timeout,
+                        &script_in, &script_out, &script_err)) {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
+                     "couldn't spawn child process for: %s", r->filename);
+       return HTTP_INTERNAL_SERVER_ERROR;
+    }
+    
+    /* Transfer request body to script */
+    
+    if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) {
+       /* Call failed, return status */
+       return rv;
+    }
+    
+    if (ap_should_client_block(r)) {
+       char buffer[HUGE_STRING_LEN];
+       int len_read;
+
+       ap_hard_timeout("reading request body", r);
+
+       while ((len_read = ap_get_client_block(r, buffer, HUGE_STRING_LEN)) 
+              > 0) {
            ap_reset_timeout(r);
-           if (ap_bwrite(script_out, argsbuffer, len_read) < len_read) {
+           if (ap_bwrite(script_in, buffer, len_read) < len_read) {
                /* silly script stopped reading, soak up remaining message */
-               while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) {
+               while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
                    /* dump it */
                }
                break;
            }
        }
-
-       ap_bflush(script_out);
-
+       
+       ap_bflush(script_in);
        ap_kill_timeout(r);
     }
 
-    ap_bclose(script_out);
+    ap_bclose(script_in);
+    
+    /* Transfer output from script to client */
 
-    /* Handle script return... */
-    if (script_in && !nph) {
+    if (script_out) {
        const char *location;
-       char sbuf[MAX_STRING_LEN];
-       int ret;
-
-       if ((ret = ap_scan_script_header_err_buff(r, script_in, sbuf))) {
-           return log_script(r, conf, ret, dbuf, sbuf, script_in, script_err);
+       char hbuffer[MAX_STRING_LEN];
+       char buffer[HUGE_STRING_LEN];
+       
+       if (rv = ap_scan_script_header_err_buff(r, script_out, hbuffer)) {
+           return HTTP_INTERNAL_SERVER_ERROR;
        }
-
+       
        location = ap_table_get(r->headers_out, "Location");
-
-       if (location && location[0] == '/' && r->status == 200) {
-
+       if (location && r->status == 200) {
            /* Soak up all the script output */
-           ap_hard_timeout("read from script", r);
-           while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
+           ap_hard_timeout("reading from script", r);
+           while (ap_bgets(buffer, HUGE_STRING_LEN, script_out) > 0) {
                continue;
            }
-#if defined(WIN32) || defined(NETWARE)
-            /* Soak up stderr and redirect it to the error log.
-             * Script output to stderr is already directed to the error log
-             * on Unix, thanks to the magic of fork().
-             */
-            while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
-                              "%s", argsbuffer);            
-            }
-#else
-           while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
-               continue;
-           }
-#endif
            ap_kill_timeout(r);
-
-
-           /* This redirect needs to be a GET no matter what the original
-            * method was.
-            */
-           r->method = ap_pstrdup(r->pool, "GET");
-           r->method_number = M_GET;
-
-           /* We already read the message body (if any), so don't allow
-            * the redirected request to think it has one.  We can ignore 
-            * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
-            */
-           ap_table_unset(r->headers_in, "Content-Length");
-
-           ap_internal_redirect_handler(location, r);
-           return OK;
-       }
-       else if (location && r->status == 200) {
-           /* XX Note that if a script wants to produce its own Redirect
-            * body, it now has to explicitly *say* "Status: 302"
-            */
-           return REDIRECT;
+           ap_bclose(script_out);
+           ap_bclose(script_err);
+       
+           if (location[0] == '/') { 
+               /* Redirect has always GET method */
+               r->method = ap_pstrdup(p, "GET");
+               r->method_number = M_GET;
+               
+               /* Remove Content-Length - redirect should not read  *
+                * request body                                      */
+               ap_table_unset(r->headers_in, "Content-Length");
+               
+               /* Do the redirect */
+               ap_internal_redirect_handler(location, r);
+               return OK;
+           } else {
+               /* Script did not set status 302 - so it does not want *
+                * to send its own body. Simply set redirect status    */
+               return REDIRECT;
+           }
        }
+           
+       /* Output headers and body */
 
        ap_send_http_header(r);
        if (!r->header_only) {
-           ap_send_fb(script_in, r);
-       }
-       ap_bclose(script_in);
-
-       ap_soft_timeout("soaking script stderr", r);
-#if defined(WIN32) || defined(NETWARE)
-        /* Script output to stderr is already directed to the error log
-         * on Unix, thanks to the magic of fork().
-         */
-        while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
-                          "%s", argsbuffer);            
-        }
-#else
-       while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
-           continue;
+           ap_send_fb(script_out, r);
        }
-#endif
-       ap_kill_timeout(r);
+       ap_bclose(script_out);
+       /* Errors have already been logged by child */
        ap_bclose(script_err);
     }
+    
+    return OK;
+}
 
-    if (script_in && nph) {
-       ap_send_fb(script_in, r);
-    }
 
-    return OK;                 /* NOT r->status, even if it has changed. */
-}
+/* Handlers table */
 
-static const handler_rec suphp_handlers[] =
-{
-    {"x-httpd-php", suphp_handler},
+static handler_rec suphp_handlers[] = {
+    {"*", suphp_handler},
     {NULL}
 };
 
-module MODULE_VAR_EXPORT suphp_module =
-{
+/* Module definition */
+
+module MODULE_VAR_EXPORT suphp_module = {
     STANDARD_MODULE_STUFF,
-    NULL,                      /* initializer */
-    suphp_create_dir_config,   /* dir config creater */
-    suphp_merge_dir_config,    /* dir merger --- default is to override */
-    suphp_create_server_config,        /* server config */
-    suphp_merge_server_config, /* merge server config */
-    suphp_cmds,                        /* command table */
-    suphp_handlers,            /* handlers */
-    NULL,                      /* filename translation */
-    NULL,                      /* check_user_id */
-    NULL,                      /* check auth */
-    NULL,                      /* check access */
-    NULL,                      /* type_checker */
-    NULL,                      /* fixups */
-    NULL,                      /* logger */
-    NULL,                      /* header parser */
-    NULL,                      /* child_init */
-    NULL,                      /* child_exit */
-    NULL                       /* post read-request */
+    NULL, /* initializer */
+    suphp_create_dir_config,     /* create directory config */
+    suphp_merge_dir_config,      /* merge directory config */
+    suphp_create_server_config,  /* create server config */
+    suphp_merge_server_config,   /* merge server config */
+    suphp_cmds,                  /* command table */
+    suphp_handlers,              /* content handlers */
+    NULL,                        /* URI-to-filename translation */
+    NULL,                        /* check/validate user_id */
+    NULL,                        /* check user_id is valid *here* */
+    NULL,                        /* check access by host address */
+    NULL,                        /* MIME type checker/setter */
+    NULL,                        /* fixups */
+    NULL,                        /* logger */
+    NULL,                        /* header parser */
+    NULL,                        /* process initialaization */
+    NULL,                        /* process exit/cleanup */
+    NULL                         /* post read_request handling */
 };