/*
    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_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_main.h"
#include "http_log.h"
#include "util_script.h"


#define SUPHP_CONFIG_MODE_SERVER 1
#define SUPHP_CONFIG_MODE_DIRECTORY 2

#define SUPHP_ENGINE_OFF 0
#define SUPHP_ENGINE_ON 1
#define SUPHP_ENGINE_UNDEFINED 2

#ifndef SUPHP_PATH_TO_SUPHP
#define SUPHP_PATH_TO_SUPHP "/usr/sbin/suphp"
#endif


/* Module declaration */
module MODULE_VAR_EXPORT suphp_module;


/* 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;
    char *php_path;
} suphp_conf;


/* Configuration mergers/creators */

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_USERGROUP
    cfg->target_user = NULL;
    cfg->target_group = NULL;
#endif

    /* 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;

#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;

    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);

    return (void *) merged;
}


static void *suphp_create_server_config(pool *p, server_rec *s) {
    suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));

    cfg->engine = SUPHP_ENGINE_UNDEFINED;
    cfg->php_path = NULL;
    cfg->cmode = SUPHP_CONFIG_MODE_SERVER;

#ifdef SUPHP_USE_USERGROUP
    cfg->target_user = NULL;
    cfg->target_group = NULL;
#endif

    /* 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_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));

    if (child->engine != SUPHP_ENGINE_UNDEFINED)
        merged->engine = child->engine;
    else
        merged->engine = parent->engine;

    if (child->php_path != NULL)
        merged->php_path = ap_pstrdup(p, child->php_path);
    else
        merged->php_path = ap_pstrdup(p, parent->php_path);

#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;

    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);

    return (void *) merged;
}


/* Command handlers */

static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
                                           int flag) {
    suphp_conf *cfg;

    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;

    return NULL;
}


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;
}


#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;

    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);

    return NULL;
}
#endif


static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig,
                                                const char *arg) {
    suphp_conf *cfg;
    if (mconfig)
        cfg = (suphp_conf *) mconfig;
    else
        cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);

    // Mark active handlers with '1'
    ap_table_set(cfg->handlers, arg, "1");

    return NULL;
}

static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd,
                                                   void *mconfig,
                                                   const char *arg) {
    suphp_conf *cfg;
    if (mconfig)
        cfg = (suphp_conf *) mconfig;
    else
        cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);

    // Mark deactivated handlers with '0'
    ap_table_set(cfg->handlers, arg, "0");

    return NULL;
}


static const char *suphp_handle_cmd_phppath(cmd_parms *cmd, void* mconfig, const char *arg)
{
    suphp_conf *cfg;

    cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config, &suphp_module);

    cfg->php_path = ap_pstrdup(cmd->pool, arg);

    return NULL;
}


/* 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"},
    {"suPHP_PHPPath", suphp_handle_cmd_phppath, NULL, RSRC_CONF, TAKE1, "Path to the PHP binary used to render source view"},
    {NULL}
};


/* Helper function which is called when spawning child process */

int suphp_source_child(void *rp, child_info *cinfo) {
    request_rec *r = (request_rec *) rp;
    suphp_conf *conf;
    pool *p = r->main ? r->main->pool : r->pool;
    char **argv, **env;
    table *empty_table = ap_make_table(p, 0);

    conf = ap_get_module_config(r->server->module_config, &suphp_module);

    /* We want to log output written to stderr */
    ap_error_log2stderr(r->server);

    /* prepare argv for new process */

    argv = ap_palloc(p, 4 * sizeof(char *));
    argv[0] = ap_pstrdup(p, conf->php_path);
    argv[1] = "-s";
    argv[2] = ap_pstrdup(p, r->filename);
    argv[3] = NULL;

    /* prepare environment */

    env = ap_create_environment(p, empty_table);

    /* We cannot use ap_call_exec because of the interference with suExec */
    /* So we do everything ourselves */

    /* mandatory cleanup before execution */
    ap_cleanup_for_exec();

    execve(ap_pstrdup(p, conf->php_path), argv, env);

    /* We are still here? Okay - exec failed */
    ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed",
                 conf->php_path);
    exit(0);
    /* NOT REACHED */
    return (0);
}


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;

    core_conf = (core_dir_config *) ap_get_module_config(
        r->per_dir_config, &core_module);

    /* We want to log output written to stderr */
    ap_error_log2stderr(r->server);

    /* prepare argv for new process */

    argv = ap_palloc(p, 2 * sizeof(char *));
    argv[0] = SUPHP_PATH_TO_SUPHP;
    argv[1] = NULL;

    /* prepare environment */

    env = ap_create_environment(p, r->subprocess_env);

    /* 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 */

    /* mandatory cleanup before execution */
    ap_cleanup_for_exec();

    execve(SUPHP_PATH_TO_SUPHP, argv, env);

    /* 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);
}


/* Handlers */

static int suphp_source_handler(request_rec *r) {
    suphp_conf *conf;
    int rv;
    pool *p;
    int fd;
    BUFF *script_in, *script_out, *script_err;
    char buffer[HUGE_STRING_LEN];

    if (strcmp(r->method, "GET")) {
        return DECLINED;
    }

    conf = ap_get_module_config(r->server->module_config, &suphp_module);
    if (conf->php_path == NULL) {
        return DECLINED;
    }

    p = r->main ? r->main->pool : r->pool;

    fd = open(r->filename, O_NOCTTY, O_RDONLY);
    if (fd != -1) {
        close(fd);
    } 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 open file: %s",
                      r->filename);
        return HTTP_NOT_FOUND;
    }

    /* Fork child process */

    if (!ap_bspawn_child(p, suphp_source_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;
    }

    /* Read request body */

    if (ap_should_client_block(r)) {
        char buffer[HUGE_STRING_LEN];
        int len_read;

        ap_hard_timeout("reading request body", r);

        while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
            ap_reset_timeout(r);
            // Ignore input
        }

        ap_bflush(script_in);
        ap_kill_timeout(r);
    }

    ap_bclose(script_in);

    /* Transfer output from PHP to client */

    if (script_out) {
        /* Output headers and body */

        r->content_type = "text/html";
        ap_send_http_header(r);
        if (!r->header_only) {
            ap_send_fb(script_out, r);
        }
        ap_bclose(script_out);
        /* Errors have already been logged by child */
        ap_bclose(script_err);
    }

    return OK;

}


static int suphp_handler(request_rec *r) {
    suphp_conf *sconf;
    suphp_conf *dconf;

#ifdef SUPHP_USE_USERGROUP
    char *ud_user = NULL;
    char *ud_group = NULL;
#endif

    struct stat finfo;

    int rv;

    char *auth_user = NULL;
    char *auth_pass = NULL;

    pool *p;

    BUFF *script_in, *script_out, *script_err;

    const char *handler;

    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 (r->handler != NULL) {
        handler = r->handler;
    } else {
        handler = r->content_type;
    }
    if ((ap_table_get(dconf->handlers, handler) == NULL)) {
        if ((ap_table_get(sconf->handlers, handler) == NULL)
            || (*(ap_table_get(sconf->handlers, handler)) == '0')) {
            return DECLINED;
        }
    } else if (*(ap_table_get(dconf->handlers, handler)) == '0') {
        return DECLINED;
    }

    /* check if suPHP is enabled for this request */

    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)) {

        /* Identify mod_userdir request
           As Apache 1.3 does not yet provide a clean way to see
           whether a request was handled by mod_userdir, we assume
           this is true for any request beginning with ~ */

        int ud_success = 0; /* set to 1 on success */

        if (!strncmp("/~", r->uri, 2)) {
            char *username = ap_pstrdup(r->pool, r->uri + 2);
            char *pos = strchr(username, '/');
            if (pos) {
                *pos = 0;
                if (strlen(username)) {
                    struct passwd *pw;
                    struct group *gr;
                    gid_t gid;
                    char *grpname;
                    if ((pw = getpwnam(username)) != NULL) {
                        gid = pw->pw_gid;

                        if ((gr = getgrgid(gid)) != NULL) {
                            grpname = gr->gr_name;
                        } else {
                            if ((grpname = ap_palloc(r->pool, 16)) == NULL) {
                                return HTTP_INTERNAL_SERVER_ERROR;
                            }
                            ap_snprintf(grpname, 16, "#%ld", (long) gid);
                        }

                        ud_user = username;
                        ud_group = grpname;
                        ud_success = 1;
                    }
                }
            }
        }

        if (!ud_success) {
            /* This is not a userdir request and user/group are not
               set, so log the error and return */
            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 */


    /* prepare environment for new process */

    ap_add_common_vars(r);
    ap_add_cgi_vars(r);

    ap_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
    ap_table_unset(r->subprocess_env, "SUPHP_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 */

    if (dconf->php_config) {
        ap_table_set(r->subprocess_env, "SUPHP_PHP_CONFIG", dconf->php_config);
    }

    ap_table_set(r->subprocess_env, "SUPHP_HANDLER", 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);
                }
            }
        }
    }

    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 if (sconf->target_user) {
        ap_table_set(r->subprocess_env, "SUPHP_USER", sconf->target_user);
    } else {
        ap_table_set(r->subprocess_env, "SUPHP_USER", ud_user);
    }

    if (dconf->target_group) {
        ap_table_set(r->subprocess_env, "SUPHP_GROUP", dconf->target_group);
    } else if (sconf->target_group) {
        ap_table_set(r->subprocess_env, "SUPHP_GROUP", sconf->target_group);
    } else {
        ap_table_set(r->subprocess_env, "SUPHP_GROUP", ud_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_in, buffer, len_read) < len_read) {
                /* silly script stopped reading, soak up remaining message */
                while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
                    /* dump it */
                }
                break;
            }
        }

        ap_bflush(script_in);
        ap_kill_timeout(r);
    }

    ap_bclose(script_in);

    /* Transfer output from script to client */

    if (script_out) {
        const char *location;
        char hbuffer[MAX_STRING_LEN];
        char buffer[HUGE_STRING_LEN];

        rv = ap_scan_script_header_err_buff(r, script_out, hbuffer);
        if (rv == HTTP_NOT_MODIFIED) {
            return rv;
        } else if (rv) {
            return HTTP_INTERNAL_SERVER_ERROR;
        }

        location = ap_table_get(r->headers_out, "Location");
        if (location && r->status == 200) {
            /* Soak up all the script output */
            ap_hard_timeout("reading from script", r);
            while (ap_bgets(buffer, HUGE_STRING_LEN, script_out) > 0) {
                continue;
            }
            ap_kill_timeout(r);
            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_out, r);
        }
        ap_bclose(script_out);
        /* Errors have already been logged by child */
        ap_bclose(script_err);
    }

    return OK;
}


/* Handlers table */

static handler_rec suphp_handlers[] = {
    {"*", suphp_handler},
    {"x-httpd-php-source", suphp_source_handler},
    {"application/x-httpd-php-source", suphp_source_handler},
    {NULL}
};

/* Module definition */

module MODULE_VAR_EXPORT suphp_module = {
    STANDARD_MODULE_STUFF,
    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 */
};
