/* ------------------------------------------------------------------------- */

/*
 * Copyright 2007-2022 GRAHAM DUMPLETON
 *
 * Licensed 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.
 */

/* ------------------------------------------------------------------------- */

#include "wsgi_apache.h"
#include "wsgi_python.h"

#ifdef HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif

#ifndef WIN32
#include <pwd.h>
#endif

static PyTypeObject Auth_Type;
#if AP_SERVER_MINORVERSION_NUMBER >= 2
#define MOD_WSGI_WITH_AUTHN_PROVIDER 1
#endif
#if AP_MODULE_MAGIC_AT_LEAST(20060110,0)
#define MOD_WSGI_WITH_AUTHZ_PROVIDER 1
#if AP_MODULE_MAGIC_AT_LEAST(20100919,0)
#define MOD_WSGI_WITH_AUTHZ_PROVIDER_PARSED 1
#endif
#endif

#if defined(MOD_WSGI_WITH_AUTHN_PROVIDER)
#include "mod_auth.h"
#include "ap_provider.h"
#ifndef AUTHN_PROVIDER_VERSION
#define AUTHN_PROVIDER_VERSION "0"
#endif
#endif

/* Local project header files. */

#include "wsgi_version.h"
#include "wsgi_convert.h"
#include "wsgi_validate.h"
#include "wsgi_interp.h"
#include "wsgi_server.h"
#include "wsgi_logger.h"
#include "wsgi_restrict.h"
#include "wsgi_stream.h"
#include "wsgi_metrics.h"
#include "wsgi_daemon.h"
#include "wsgi_buckets.h"
#include "wsgi_thread.h"

/* Module information. */

module AP_MODULE_DECLARE_DATA wsgi_module;

/* Process information. */

static int wsgi_multiprocess = 1;
static int wsgi_multithread = 1;

/* Daemon information. */

static apr_array_header_t *wsgi_daemon_list = NULL;

static apr_pool_t *wsgi_parent_pool = NULL;

int volatile wsgi_daemon_shutdown = 0;
static int volatile wsgi_daemon_graceful = 0;
static int wsgi_dump_stack_traces = 0;
static char *wsgi_shutdown_reason = "";

#if defined(MOD_WSGI_WITH_DAEMONS)
static apr_interval_time_t wsgi_startup_timeout = 0;
static apr_interval_time_t wsgi_deadlock_timeout = 0;
static apr_interval_time_t wsgi_idle_timeout = 0;
static apr_interval_time_t wsgi_request_timeout = 0;
static apr_interval_time_t wsgi_graceful_timeout = 0;
static apr_interval_time_t wsgi_eviction_timeout = 0;
static apr_interval_time_t wsgi_restart_interval = 0;
static apr_time_t volatile wsgi_startup_shutdown_time = 0;
static apr_time_t volatile wsgi_deadlock_shutdown_time = 0;
static apr_time_t volatile wsgi_idle_shutdown_time = 0;
static apr_time_t volatile wsgi_graceful_shutdown_time = 0;
static apr_time_t volatile wsgi_restart_shutdown_time = 0;
#endif

/* Script information. */

static apr_array_header_t *wsgi_import_list = NULL;

static void *wsgi_create_server_config(apr_pool_t *p, server_rec *s)
{
    WSGIServerConfig *config = NULL;

    config = newWSGIServerConfig(p);

    return config;
}

static void *wsgi_merge_server_config(apr_pool_t *p, void *base_conf,
                                      void *new_conf)
{
    WSGIServerConfig *config = NULL;
    WSGIServerConfig *parent = NULL;
    WSGIServerConfig *child = NULL;

    config = newWSGIServerConfig(p);

    parent = (WSGIServerConfig *)base_conf;
    child = (WSGIServerConfig *)new_conf;

    if (child->alias_list && parent->alias_list) {
        config->alias_list = apr_array_append(p, child->alias_list,
                                              parent->alias_list);
    }
    else if (child->alias_list) {
        config->alias_list = apr_array_make(p, 20, sizeof(WSGIAliasEntry));
        apr_array_cat(config->alias_list, child->alias_list);
    }
    else if (parent->alias_list) {
        config->alias_list = apr_array_make(p, 20, sizeof(WSGIAliasEntry));
        apr_array_cat(config->alias_list, parent->alias_list);
    }

    if (child->restrict_process)
        config->restrict_process = child->restrict_process;
    else
        config->restrict_process = parent->restrict_process;

    if (child->process_group)
        config->process_group = child->process_group;
    else
        config->process_group = parent->process_group;

    if (child->application_group)
        config->application_group = child->application_group;
    else
        config->application_group = parent->application_group;

    if (child->callable_object)
        config->callable_object = child->callable_object;
    else
        config->callable_object = parent->callable_object;

    if (child->dispatch_script)
        config->dispatch_script = child->dispatch_script;
    else
        config->dispatch_script = parent->dispatch_script;

    if (child->pass_apache_request != -1)
        config->pass_apache_request = child->pass_apache_request;
    else
        config->pass_apache_request = parent->pass_apache_request;

    if (child->pass_authorization != -1)
        config->pass_authorization = child->pass_authorization;
    else
        config->pass_authorization = parent->pass_authorization;

    if (child->script_reloading != -1)
        config->script_reloading = child->script_reloading;
    else
        config->script_reloading = parent->script_reloading;

    if (child->error_override != -1)
        config->error_override = child->error_override;
    else
        config->error_override = parent->error_override;

    if (child->chunked_request != -1)
        config->chunked_request = child->chunked_request;
    else
        config->chunked_request = parent->chunked_request;

    if (child->map_head_to_get != -1)
        config->map_head_to_get = child->map_head_to_get;
    else
        config->map_head_to_get = parent->map_head_to_get;

    if (child->ignore_activity != -1)
        config->ignore_activity = child->ignore_activity;
    else
        config->ignore_activity = parent->ignore_activity;

    if (child->trusted_proxy_headers)
        config->trusted_proxy_headers = child->trusted_proxy_headers;
    else
        config->trusted_proxy_headers = parent->trusted_proxy_headers;

    if (child->trusted_proxies)
        config->trusted_proxies = child->trusted_proxies;
    else
        config->trusted_proxies = parent->trusted_proxies;

    if (child->enable_sendfile != -1)
        config->enable_sendfile = child->enable_sendfile;
    else
        config->enable_sendfile = parent->enable_sendfile;

    if (!child->handler_scripts)
        config->handler_scripts = parent->handler_scripts;
    else if (!parent->handler_scripts)
        config->handler_scripts = child->handler_scripts;
    else {
        config->handler_scripts = apr_hash_overlay(p, child->handler_scripts,
                                                   parent->handler_scripts);
    }

    return config;
}

typedef struct {
    apr_pool_t *pool;

    apr_table_t *restrict_process;

    const char *process_group;
    const char *application_group;
    const char *callable_object;

    WSGIScriptFile *dispatch_script;

    int pass_apache_request;
    int pass_authorization;
    int script_reloading;
    int error_override;
    int chunked_request;
    int map_head_to_get;
    int ignore_activity;

    apr_array_header_t *trusted_proxy_headers;
    apr_array_header_t *trusted_proxies;

    int enable_sendfile;

    WSGIScriptFile *access_script;
    WSGIScriptFile *auth_user_script;
    WSGIScriptFile *auth_group_script;
    int user_authoritative;
    int group_authoritative;

    apr_hash_t *handler_scripts;
} WSGIDirectoryConfig;

static WSGIDirectoryConfig *newWSGIDirectoryConfig(apr_pool_t *p)
{
    WSGIDirectoryConfig *object = NULL;

    object = (WSGIDirectoryConfig *)apr_pcalloc(p, sizeof(WSGIDirectoryConfig));

    object->pool = p;

    object->process_group = NULL;
    object->application_group = NULL;
    object->callable_object = NULL;

    object->dispatch_script = NULL;

    object->pass_apache_request = -1;
    object->pass_authorization = -1;
    object->script_reloading = -1;
    object->error_override = -1;
    object->chunked_request = -1;
    object->map_head_to_get = -1;
    object->ignore_activity = -1;

    object->trusted_proxy_headers = NULL;
    object->trusted_proxies = NULL;

    object->enable_sendfile = -1;

    object->access_script = NULL;
    object->auth_user_script = NULL;
    object->auth_group_script = NULL;
    object->user_authoritative = -1;
    object->group_authoritative = -1;

    return object;
}

static void *wsgi_create_dir_config(apr_pool_t *p, char *dir)
{
    WSGIDirectoryConfig *config = NULL;

    config = newWSGIDirectoryConfig(p);

    return config;
}

static void *wsgi_merge_dir_config(apr_pool_t *p, void *base_conf,
                                   void *new_conf)
{
    WSGIDirectoryConfig *config = NULL;
    WSGIDirectoryConfig *parent = NULL;
    WSGIDirectoryConfig *child = NULL;

    config = newWSGIDirectoryConfig(p);

    parent = (WSGIDirectoryConfig *)base_conf;
    child = (WSGIDirectoryConfig *)new_conf;

    if (child->restrict_process)
        config->restrict_process = child->restrict_process;
    else
        config->restrict_process = parent->restrict_process;

    if (child->process_group)
        config->process_group = child->process_group;
    else
        config->process_group = parent->process_group;

    if (child->application_group)
        config->application_group = child->application_group;
    else
        config->application_group = parent->application_group;

    if (child->callable_object)
        config->callable_object = child->callable_object;
    else
        config->callable_object = parent->callable_object;

    if (child->dispatch_script)
        config->dispatch_script = child->dispatch_script;
    else
        config->dispatch_script = parent->dispatch_script;

    if (child->pass_apache_request != -1)
        config->pass_apache_request = child->pass_apache_request;
    else
        config->pass_apache_request = parent->pass_apache_request;

    if (child->pass_authorization != -1)
        config->pass_authorization = child->pass_authorization;
    else
        config->pass_authorization = parent->pass_authorization;

    if (child->script_reloading != -1)
        config->script_reloading = child->script_reloading;
    else
        config->script_reloading = parent->script_reloading;

    if (child->error_override != -1)
        config->error_override = child->error_override;
    else
        config->error_override = parent->error_override;

    if (child->chunked_request != -1)
        config->chunked_request = child->chunked_request;
    else
        config->chunked_request = parent->chunked_request;

    if (child->map_head_to_get != -1)
        config->map_head_to_get = child->map_head_to_get;
    else
        config->map_head_to_get = parent->map_head_to_get;

    if (child->ignore_activity != -1)
        config->ignore_activity = child->ignore_activity;
    else
        config->ignore_activity = parent->ignore_activity;

    if (child->trusted_proxy_headers)
        config->trusted_proxy_headers = child->trusted_proxy_headers;
    else
        config->trusted_proxy_headers = parent->trusted_proxy_headers;

    if (child->trusted_proxies)
        config->trusted_proxies = child->trusted_proxies;
    else
        config->trusted_proxies = parent->trusted_proxies;

    if (child->enable_sendfile != -1)
        config->enable_sendfile = child->enable_sendfile;
    else
        config->enable_sendfile = parent->enable_sendfile;

    if (child->access_script)
        config->access_script = child->access_script;
    else
        config->access_script = parent->access_script;

    if (child->auth_user_script)
        config->auth_user_script = child->auth_user_script;
    else
        config->auth_user_script = parent->auth_user_script;

    if (child->auth_group_script)
        config->auth_group_script = child->auth_group_script;
    else
        config->auth_group_script = parent->auth_group_script;

    if (child->user_authoritative != -1)
        config->user_authoritative = child->user_authoritative;
    else
        config->user_authoritative = parent->user_authoritative;

    if (child->group_authoritative != -1)
        config->group_authoritative = child->group_authoritative;
    else
        config->group_authoritative = parent->group_authoritative;

    if (!child->handler_scripts)
        config->handler_scripts = parent->handler_scripts;
    else if (!parent->handler_scripts)
        config->handler_scripts = child->handler_scripts;
    else {
        config->handler_scripts = apr_hash_overlay(p, child->handler_scripts,
                                                   parent->handler_scripts);
    }

    return config;
}

typedef struct {
    apr_pool_t *pool;

    apr_table_t *restrict_process;

    const char *process_group;
    const char *application_group;
    const char *callable_object;

    WSGIScriptFile *dispatch_script;

    int pass_apache_request;
    int pass_authorization;
    int script_reloading;
    int error_override;
    int chunked_request;
    int map_head_to_get;
    int ignore_activity;

    apr_array_header_t *trusted_proxy_headers;
    apr_array_header_t *trusted_proxies;

    int enable_sendfile;

    WSGIScriptFile *access_script;
    WSGIScriptFile *auth_user_script;
    WSGIScriptFile *auth_group_script;
    int user_authoritative;
    int group_authoritative;

    apr_hash_t *handler_scripts;
    const char *handler_script;

    int daemon_connects;
    int daemon_restarts;

    apr_time_t request_start;
    apr_time_t queue_start;
    apr_time_t daemon_start;
} WSGIRequestConfig;

static long wsgi_find_path_info(const char *uri, const char *path_info)
{
    long lu = strlen(uri);
    long lp = strlen(path_info);

    while (lu-- && lp-- && uri[lu] == path_info[lp]) {
        if (path_info[lp] == '/') {
            while (lu && uri[lu-1] == '/') lu--;
        }
    }

    if (lu == -1) {
        lu = 0;
    }

    while (uri[lu] != '\0' && uri[lu] != '/') {
        lu++;
    }
    return lu;
}

static const char *wsgi_script_name(request_rec *r)
{
    char *script_name = NULL;
    long path_info_start = 0;

    if (!r->path_info || !*r->path_info) {
        script_name = apr_pstrdup(r->pool, r->uri);
    }
    else {
        path_info_start = wsgi_find_path_info(r->uri, r->path_info);

        script_name = apr_pstrndup(r->pool, r->uri, path_info_start);
    }

    if (*script_name) {
        while (*script_name && (*(script_name+1) == '/'))
            script_name++;
        script_name = apr_pstrdup(r->pool, script_name);
        ap_no2slash((char*)script_name);
    }

    ap_str_tolower(script_name);

    return script_name;
}

static const char *wsgi_process_group(request_rec *r, const char *s)
{
    const char *name = NULL;
    const char *value = NULL;

    const char *h = NULL;
    apr_port_t p = 0;
    const char *n = NULL;

    if (!s)
        return "";

    if (*s != '%')
        return s;

    name = s + 1;

    if (*name) {
        if (!strcmp(name, "{GLOBAL}"))
            return "";

        if (!strcmp(name, "{RESOURCE}")) {
            h = r->server->server_hostname;
            p = ap_get_server_port(r);
            n = wsgi_script_name(r);

            if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT)
                return apr_psprintf(r->pool, "%s:%u|%s", h, p, n);
            else
                return apr_psprintf(r->pool, "%s|%s", h, n);
        }

        if (!strcmp(name, "{SERVER}")) {
            h = r->server->server_hostname;
            p = ap_get_server_port(r);

            if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT)
                return apr_psprintf(r->pool, "%s:%u", h, p);
            else
                return h;
        }

        if (!strcmp(name, "{HOST}")) {
            h = r->hostname;
            p = ap_get_server_port(r);

            /*
             * The Host header could be empty or absent for HTTP/1.0
             * or older. In that case fallback to ServerName.
             */

            if (h == NULL || *h == 0)
                h = r->server->server_hostname;

            if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT)
                return apr_psprintf(r->pool, "%s:%u", h, p);
            else
                return h;
        }

        if (strstr(name, "{ENV:") == name) {
            long len = 0;

            name = name + 5;
            len = strlen(name);

            if (len && name[len-1] == '}') {
                name = apr_pstrndup(r->pool, name, len-1);

                value = apr_table_get(r->notes, name);

                if (!value)
                    value = apr_table_get(r->subprocess_env, name);

                if (!value)
                    value = getenv(name);

                if (value) {
                    if (*value == '%' && strstr(value, "%{ENV:") != value)
                        return wsgi_process_group(r, value);

                    return value;
                }
            }
        }
    }

    return s;
}

static const char *wsgi_server_group(request_rec *r, const char *s)
{
    const char *name = NULL;

    const char *h = NULL;
    apr_port_t p = 0;

    if (!s)
        return "";

    if (*s != '%')
        return s;

    name = s + 1;

    if (*name) {
        if (!strcmp(name, "{GLOBAL}"))
            return "";

        if (!strcmp(name, "{SERVER}")) {
            h = r->server->server_hostname;
            p = ap_get_server_port(r);

            if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT)
                return apr_psprintf(r->pool, "%s:%u", h, p);
            else
                return h;
        }

        if (!strcmp(name, "{HOST}")) {
            h = r->hostname;
            p = ap_get_server_port(r);

            /*
             * The Host header could be empty or absent for HTTP/1.0
             * or older. In that case fallback to ServerName.
             */

            if (h == NULL || *h == 0)
                h = r->server->server_hostname;

            if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT)
                return apr_psprintf(r->pool, "%s:%u", h, p);
            else
                return h;
        }
    }

    return s;
}

static const char *wsgi_application_group(request_rec *r, const char *s)
{
    const char *name = NULL;
    const char *value = NULL;

    const char *h = NULL;
    apr_port_t p = 0;
    const char *n = NULL;

    if (!s) {
        h = r->server->server_hostname;
        p = ap_get_server_port(r);
        n = wsgi_script_name(r);

        if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT)
            return apr_psprintf(r->pool, "%s:%u|%s", h, p, n);
        else
            return apr_psprintf(r->pool, "%s|%s", h, n);
    }

    if (*s != '%')
        return s;

    name = s + 1;

    if (*name) {
        if (!strcmp(name, "{GLOBAL}"))
            return "";

        if (!strcmp(name, "{RESOURCE}")) {
            h = r->server->server_hostname;
            p = ap_get_server_port(r);
            n = wsgi_script_name(r);

            if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT)
                return apr_psprintf(r->pool, "%s:%u|%s", h, p, n);
            else
                return apr_psprintf(r->pool, "%s|%s", h, n);
        }

        if (!strcmp(name, "{SERVER}")) {
            h = r->server->server_hostname;
            p = ap_get_server_port(r);

            if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT)
                return apr_psprintf(r->pool, "%s:%u", h, p);
            else
                return h;
        }

        if (!strcmp(name, "{HOST}")) {
            h = r->hostname;
            p = ap_get_server_port(r);

            /*
             * The Host header could be empty or absent for HTTP/1.0
             * or older. In that case fallback to ServerName.
             */

            if (h == NULL || *h == 0)
                h = r->server->server_hostname;

            if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT)
                return apr_psprintf(r->pool, "%s:%u", h, p);
            else
                return h;
        }

        if (strstr(name, "{ENV:") == name) {
            long len = 0;

            name = name + 5;
            len = strlen(name);

            if (len && name[len-1] == '}') {
                name = apr_pstrndup(r->pool, name, len-1);

                value = apr_table_get(r->notes, name);

                if (!value)
                    value = apr_table_get(r->subprocess_env, name);

                if (!value)
                    value = getenv(name);

                if (value) {
                    if (*value == '%' && strstr(value, "%{ENV:") != value)
                        return wsgi_application_group(r, value);

                    return value;
                }
            }
        }
    }

    return s;
}

static const char *wsgi_callable_object(request_rec *r, const char *s)
{
    const char *name = NULL;
    const char *value = NULL;

    if (!s)
        return "application";

    if (*s != '%')
        return s;

    name = s + 1;

    if (!*name)
        return "application";

    if (strstr(name, "{ENV:") == name) {
        long len = 0;

        name = name + 5;
        len = strlen(name);

        if (len && name[len-1] == '}') {
            name = apr_pstrndup(r->pool, name, len-1);

            value = apr_table_get(r->notes, name);

            if (!value)
                value = apr_table_get(r->subprocess_env, name);

            if (!value)
                value = getenv(name);

            if (value)
                return value;
        }
    }

    return "application";
}

static WSGIRequestConfig *wsgi_create_req_config(apr_pool_t *p, request_rec *r)
{
    WSGIRequestConfig *config = NULL;
    WSGIServerConfig *sconfig = NULL;
    WSGIDirectoryConfig *dconfig = NULL;

    config = (WSGIRequestConfig *)apr_pcalloc(p, sizeof(WSGIRequestConfig));

    dconfig = ap_get_module_config(r->per_dir_config, &wsgi_module);
    sconfig = ap_get_module_config(r->server->module_config, &wsgi_module);

    config->pool = p;

    config->restrict_process = dconfig->restrict_process;

    if (!config->restrict_process)
        config->restrict_process = sconfig->restrict_process;

    config->process_group = dconfig->process_group;

    if (!config->process_group)
        config->process_group = sconfig->process_group;

    config->process_group = wsgi_process_group(r, config->process_group);

    config->application_group = dconfig->application_group;

    if (!config->application_group)
        config->application_group = sconfig->application_group;

    config->application_group = wsgi_application_group(r,
                                    config->application_group);

    config->callable_object = dconfig->callable_object;

    if (!config->callable_object)
        config->callable_object = sconfig->callable_object;

    config->callable_object = wsgi_callable_object(r, config->callable_object);

    config->dispatch_script = dconfig->dispatch_script;

    if (!config->dispatch_script)
        config->dispatch_script = sconfig->dispatch_script;

    config->pass_apache_request = dconfig->pass_apache_request;

    if (config->pass_apache_request < 0) {
        config->pass_apache_request = sconfig->pass_apache_request;
        if (config->pass_apache_request < 0)
            config->pass_apache_request = 0;
    }

    config->pass_authorization = dconfig->pass_authorization;

    if (config->pass_authorization < 0) {
        config->pass_authorization = sconfig->pass_authorization;
        if (config->pass_authorization < 0)
            config->pass_authorization = 0;
    }

    config->script_reloading = dconfig->script_reloading;

    if (config->script_reloading < 0) {
        config->script_reloading = sconfig->script_reloading;
        if (config->script_reloading < 0)
            config->script_reloading = 1;
    }

    config->error_override = dconfig->error_override;

    if (config->error_override < 0) {
        config->error_override = sconfig->error_override;
        if (config->error_override < 0)
            config->error_override = 0;
    }

    config->chunked_request = dconfig->chunked_request;

    if (config->chunked_request < 0) {
        config->chunked_request = sconfig->chunked_request;
        if (config->chunked_request < 0)
            config->chunked_request = 0;
    }

    config->map_head_to_get = dconfig->map_head_to_get;

    if (config->map_head_to_get < 0) {
        config->map_head_to_get = sconfig->map_head_to_get;
        if (config->map_head_to_get < 0)
            config->map_head_to_get = 2;
    }

    config->ignore_activity = dconfig->ignore_activity;

    if (config->ignore_activity < 0) {
        config->ignore_activity = sconfig->ignore_activity;
        if (config->ignore_activity < 0)
            config->ignore_activity = 0;
    }

    config->trusted_proxy_headers = dconfig->trusted_proxy_headers;

    if (!config->trusted_proxy_headers)
        config->trusted_proxy_headers = sconfig->trusted_proxy_headers;

    config->trusted_proxies = dconfig->trusted_proxies;

    if (!config->trusted_proxies)
        config->trusted_proxies = sconfig->trusted_proxies;

    config->enable_sendfile = dconfig->enable_sendfile;

    if (config->enable_sendfile < 0) {
        config->enable_sendfile = sconfig->enable_sendfile;
        if (config->enable_sendfile < 0)
            config->enable_sendfile = 0;
    }

    config->access_script = dconfig->access_script;

    config->auth_user_script = dconfig->auth_user_script;

    config->auth_group_script = dconfig->auth_group_script;

    config->user_authoritative = dconfig->user_authoritative;

    if (config->user_authoritative == -1)
        config->user_authoritative = 1;

    config->group_authoritative = dconfig->group_authoritative;

    if (config->group_authoritative == -1)
        config->group_authoritative = 1;

    if (!dconfig->handler_scripts)
        config->handler_scripts = sconfig->handler_scripts;
    else if (!sconfig->handler_scripts)
        config->handler_scripts = dconfig->handler_scripts;
    else {
        config->handler_scripts = apr_hash_overlay(p, dconfig->handler_scripts,
                                                   sconfig->handler_scripts);
    }

    config->handler_script = "";

    config->daemon_connects = 0;
    config->daemon_restarts = 0;

    config->request_start = 0;
    config->queue_start = 0;
    config->daemon_start = 0;

    return config;
}

/* Error reporting. */

static void wsgi_log_script_error(request_rec *r, const char *e, const char *n)
{
    char *message = NULL;

    if (!n)
        n = r->filename;

    message = apr_psprintf(r->pool, "%s: %s", e, n);

    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", message);
}

/* Class objects used by response handler. */

static PyTypeObject Dispatch_Type;

typedef struct {
        PyObject_HEAD
        request_rec *r;
        int init;
        int done;
        char *buffer;
        apr_off_t size;
        apr_off_t offset;
        apr_off_t length;
        apr_bucket_brigade *bb;
        int seen_eos;
        int seen_error;
        apr_off_t bytes;
        apr_off_t reads;
        apr_time_t time;
        int ignore_activity;
} InputObject;

static PyTypeObject Input_Type;

static InputObject *newInputObject(request_rec *r, int ignore_activity)
{
    InputObject *self;

    self = PyObject_New(InputObject, &Input_Type);
    if (self == NULL)
        return NULL;

    self->r = r;
    self->init = 0;
    self->done = 0;

    self->buffer = NULL;
    self->size = 0;
    self->offset = 0;
    self->length = 0;

    self->bb = NULL;

    self->seen_eos = 0;
    self->seen_error = 0;

    self->bytes = 0;
    self->reads = 0;
    self->time = 0;

    self->ignore_activity = ignore_activity;

    return self;
}

static void Input_dealloc(InputObject *self)
{
    if (self->buffer)
        free(self->buffer);

    PyObject_Del(self);
}

static void Input_finish(InputObject *self)
{
    if (self->bb) {
        Py_BEGIN_ALLOW_THREADS
        apr_brigade_destroy(self->bb);
        Py_END_ALLOW_THREADS

        self->bb = NULL;
    }

    self->r = NULL;
}

static PyObject *Input_close(InputObject *self, PyObject *args)
{
    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    Py_INCREF(Py_None);
    return Py_None;
}

static apr_status_t wsgi_strtoff(apr_off_t *offset, const char *nptr,
                                 char **endptr, int base)
{
   errno = 0;
   if (sizeof(apr_off_t) == 4) {
       *offset = strtol(nptr, endptr, base);
   }
   else {
       *offset = apr_strtoi64(nptr, endptr, base);
   }
   return APR_FROM_OS_ERROR(errno);
}

static apr_int64_t Input_read_from_input(InputObject *self, char *buffer,
                                  apr_size_t bufsiz)
{
    request_rec *r = self->r;
    apr_bucket_brigade *bb = self->bb;

    apr_status_t rv;

    apr_status_t error_status = 0;
    const char *error_message = NULL;

    apr_time_t start = 0;
    apr_time_t finish = 0;

    /* If have already seen end of input, return an empty string. */

    if (self->seen_eos)
        return 0;

    /* If have already encountered an error, then raise a new error. */

    if (self->seen_error) {
        PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi request data read "
                "error: Input is already in error state.");

        return -1;
    }

    /*
     * When reaading the request content we will be saying that we
     * should block if there is no input data available at that
     * point but not all data has been exhausted. We therefore need
     * to ensure that we do not cause Python as a whole to block by
     * releasing the GIL, but also must remember to reacquire the GIL
     * when we exit.
     */

    Py_BEGIN_ALLOW_THREADS

    start = apr_time_now();

    self->reads += 1;

    /*
     * Create the bucket brigade the first time it is required and
     * save it against the input object. We need to make sure we
     * perform a cleanup, but not destroy, the bucket brigade each
     * time we exit this function.
     */

    if (!bb) {
        bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);

        if (bb == NULL) {
            r->connection->keepalive = AP_CONN_CLOSE;
            error_message = "Unable to create bucket brigade";
            goto finally;
        }

        self->bb = bb;
    }

    /* Force the required amount of input to be read. */

    rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
                        APR_BLOCK_READ, bufsiz);

    if (rv != APR_SUCCESS) {
        /*
         * If we actually fail here, we want to just return and
         * stop trying to read data from the client. The HTTP_IN
         * input filter is a bit of a pain here as it can return
         * EAGAIN in various strange situations where it isn't
         * believed that it means to retry, but that it is still
         * a permanent failure. This can include timeouts and
         * errors in chunked encoding format. To avoid a message
         * of 'Resource temporarily unavailable' which could be
         * confusing, replace it with a generic message that the
         * connection was terminated.
         */

        r->connection->keepalive = AP_CONN_CLOSE;

        if (APR_STATUS_IS_EAGAIN(rv))
            error_message = "Connection was terminated";
        else
            error_status = rv;

        goto finally;
    }

    /*
     * If this fails, it means that a filter is written incorrectly and
     * that it needs to learn how to properly handle APR_BLOCK_READ
     * requests by returning data when requested.
     */

    AP_DEBUG_ASSERT(!APR_BRIGADE_EMPTY(bb));

    /*
     * Check to see if EOS terminates the brigade. If so, we remember
     * this to avoid any attempts to read more data in future calls.
     */

    if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb)))
        self->seen_eos = 1;

    /* Now extract the actual data from the bucket brigade. */

    rv = apr_brigade_flatten(bb, buffer, &bufsiz);

    if (rv != APR_SUCCESS) {
        error_status = rv;
        goto finally;
    }

finally:
    /*
     * We must always cleanup up, not destroy, the brigade after
     * each call.
     */

    if (bb)
        apr_brigade_cleanup(bb);

    finish = apr_time_now();

    if (finish > start)
        self->time += (finish - start);

    /* Make sure we reacquire the GIL when all done. */

    Py_END_ALLOW_THREADS

    /*
     * Set any Python exception when an error has occurred and
     * remember there was an error so can flag on subsequent
     * reads that already in an error state.
     */

    if (error_status) {
        char status_buffer[512];

        error_message = apr_psprintf(r->pool, "Apache/mod_wsgi request "
                "data read error: %s.", apr_strerror(error_status,
                status_buffer, sizeof(status_buffer)-1));

        PyErr_SetString(PyExc_IOError, error_message);

        self->seen_error = 1;

        return -1;
    }
    else if (error_message) {
        error_message = apr_psprintf(r->pool, "Apache/mod_wsgi request "
                "data read error: %s.", error_message);

        PyErr_SetString(PyExc_IOError, error_message);

        self->seen_error = 1;

        return -1;
    }

    /*
     * Finally return the amount of data that was read. This will be
     * zero if all data has been consumed.
     */

    return bufsiz;
}

static PyObject *Input_read(InputObject *self, PyObject *args)
{
#if defined(HAVE_LONG_LONG)
    PY_LONG_LONG size = -1;
#else
    long size = -1;
#endif

    PyObject *result = NULL;
    char *buffer = NULL;
    apr_off_t length = 0;
    int init = 0;

    apr_int64_t n;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

#if defined(HAVE_LONG_LONG)
    if (!PyArg_ParseTuple(args, "|L:read", &size))
        return NULL;
#else
    if (!PyArg_ParseTuple(args, "|l:read", &size))
        return NULL;
#endif

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (wsgi_idle_timeout && !self->ignore_activity) {
        apr_thread_mutex_lock(wsgi_monitor_lock);

        if (wsgi_idle_timeout) {
            wsgi_idle_shutdown_time = apr_time_now();
            wsgi_idle_shutdown_time += wsgi_idle_timeout;
        }

        apr_thread_mutex_unlock(wsgi_monitor_lock);
    }
#endif

    if (self->seen_error) {
        PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi request data read "
                "error: Input is already in error state.");

        return NULL;
    }

    init = self->init;

    if (!self->init)
        self->init = 1;

    /* No point continuing if no more data to be consumed. */

    if (self->done && self->length == 0)
        return PyString_FromString("");

    /*
     * If requested size is zero bytes, then still need to pass
     * this through to Apache input filters so that any
     * 100-continue response is triggered. Only do this if very
     * first attempt to read data. Note that this will cause an
     * assertion failure in HTTP_IN input filter when Apache
     * maintainer mode is enabled. It is arguable that the
     * assertion check, which prohibits a zero length read,
     * shouldn't exist, as why should a zero length read be not
     * allowed if input filter processing still works when it
     * does occur.
     */

    if (size == 0) {
        if (!init) {
            char dummy[1];

            n = Input_read_from_input(self, dummy, 0);

            if (n == -1)
                return NULL;
        }

        return PyString_FromString("");
    }

    /*
     * First deal with case where size has been specified. After
     * that deal with case where expected that all remaining
     * data is to be read in and returned as one string.
     */

    if (size > 0) {
        /* Allocate string of the exact size required. */

        result = PyString_FromStringAndSize(NULL, size);

        if (!result)
            return NULL;

        buffer = PyString_AS_STRING((PyStringObject *)result);

        /* Copy any residual data from use of readline(). */

        if (self->buffer && self->length) {
            if (size >= self->length) {
                length = self->length;
                memcpy(buffer, self->buffer + self->offset, length);
                self->offset = 0;
                self->length = 0;
            }
            else {
                length = size;
                memcpy(buffer, self->buffer + self->offset, length);
                self->offset += length;
                self->length -= length;
            }
        }

        /* If all data residual buffer consumed then free it. */

        if (!self->length) {
            free(self->buffer);
            self->buffer = NULL;
        }

        /* Read in remaining data required to achieve size. */

        if (length < size) {
            while (length != size) {
                n = Input_read_from_input(self, buffer+length, size-length);

                if (n == -1) {
                    Py_DECREF(result);
                    return NULL;
                }
                else if (n == 0) {
                    /* Have exhausted all the available input data. */

                    self->done = 1;
                    break;
                }

                length += n;
            }

            /*
             * Resize the final string. If the size reduction is
             * by more than 25% of the string size, then Python
             * will allocate a new block of memory and copy the
             * data into it.
             */

            if (length != size) {
                if (_PyString_Resize(&result, length))
                    return NULL;
            }
        }
    }
    else {
        /*
         * Here we are going to try and read in all the
         * remaining data. First we have to allocate a suitably
         * large string, but we can't fully trust the amount
         * that the request structure says is remaining based on
         * the original content length though, as an input
         * filter can insert/remove data from the input stream
         * thereby invalidating the original content length.
         * What we do is allow for an extra 25% above what we
         * have already buffered and what the request structure
         * says is remaining. A value of 25% has been chosen so
         * as to match best how Python handles resizing of
         * strings. Note that even though we do this and allow
         * all available content, strictly speaking the WSGI
         * specification says we should only read up until content
         * length. This though is because the WSGI specification
         * is deficient in dealing with the concept of mutating
         * input filters. Since read() with no argument is also
         * not allowed by WSGI specification implement it in the
         * way which is most logical and ensure that input data
         * is not truncated.
         */

        if (self->buffer) {
            size = self->length;
            size = size + (size >> 2);

            if (size < HUGE_STRING_LEN)
                size = HUGE_STRING_LEN;
        }
        else
            size = HUGE_STRING_LEN;

        /* Allocate string of the estimated size. */

        result = PyString_FromStringAndSize(NULL, size);

        if (!result)
            return NULL;

        buffer = PyString_AS_STRING((PyStringObject *)result);

        /*
         * Copy any residual data from use of readline(). The
         * residual should always be less in size than the
         * string we have allocated to hold it, so can consume
         * all of it.
         */

        if (self->buffer && self->length) {
            length = self->length;
            memcpy(buffer, self->buffer + self->offset, length);
            self->offset = 0;
            self->length = 0;

            free(self->buffer);
            self->buffer = NULL;
        }

        /* Now make first attempt at reading remaining data. */

        n = Input_read_from_input(self, buffer+length, size-length);

        if (n == -1) {
            Py_DECREF(result);
            return NULL;
        }
        else if (n == 0) {
            /* Have exhausted all the available input data. */

            self->done = 1;
        }

        length += n;

        /*
         * Don't just assume that all data has been read if
         * amount read was less than that requested. Still must
         * perform a read which returns that no more data found.
         */

        while (!self->done) {
            if (length == size) {
                /* Increase the size of the string by 25%. */

                size = size + (size >> 2);

                if (_PyString_Resize(&result, size))
                    return NULL;

                buffer = PyString_AS_STRING((PyStringObject *)result);
            }

            /* Now make succesive attempt at reading data. */

            n = Input_read_from_input(self, buffer+length, size-length);

            if (n == -1) {
                Py_DECREF(result);
                return NULL;
            }
            else if (n == 0) {
                /* Have exhausted all the available input data. */

                self->done = 1;
            }

            length += n;
        }

        /*
         * Resize the final string. If the size reduction is by
         * more than 25% of the string size, then Python will
         * allocate a new block of memory and copy the data into
         * it.
         */

        if (length != size) {
            if (_PyString_Resize(&result, length))
                return NULL;
        }
    }

    self->bytes += length;

    return result;
}

static PyObject *Input_readline(InputObject *self, PyObject *args)
{
#if defined(HAVE_LONG_LONG)
    PY_LONG_LONG size = -1;
#else
    long size = -1;
#endif

    PyObject *result = NULL;
    char *buffer = NULL;
    apr_off_t length = 0;

    apr_int64_t n;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

#if defined(HAVE_LONG_LONG)
    if (!PyArg_ParseTuple(args, "|L:readline", &size))
        return NULL;
#else
    if (!PyArg_ParseTuple(args, "|l:readline", &size))
        return NULL;
#endif

    if (self->seen_error) {
        PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi request data read "
                "error: Input is already in error state.");

        return NULL;
    }

    if (!self->init)
        self->init = 1;

    /*
     * No point continuing if requested size is zero or if no
     * more data to read and no buffered data.
     */

    if ((self->done && self->length == 0) || size == 0)
        return PyString_FromString("");

    /*
     * First deal with case where size has been specified. After
     * that deal with case where expected that a complete line
     * is returned regardless of the size.
     */

    if (size > 0) {
        /* Allocate string of the exact size required. */

        result = PyString_FromStringAndSize(NULL, size);

        if (!result)
            return NULL;

        buffer = PyString_AS_STRING((PyStringObject *)result);

        /* Copy any residual data from use of readline(). */

        if (self->buffer && self->length) {
            char *p = NULL;
            const char *q = NULL;

            p = buffer;
            q = self->buffer + self->offset;

            while (self->length && length < size) {
                self->offset++;
                self->length--;
                length++;
                if ((*p++ = *q++) == '\n')
                    break;
            }

            /* If all data in residual buffer consumed then free it. */

            if (!self->length) {
                free(self->buffer);
                self->buffer = NULL;
            }
        }

        /*
         * Read in remaining data required to achieve size. Note
         * that can't just return whatever the first read might
         * have returned if no EOL encountered as must return
         * exactly the required size if no EOL unless that would
         * have exhausted all input.
         */

        while ((!length || buffer[length-1] != '\n') &&
               !self->done && length < size) {

            char *p = NULL;
            char *q = NULL;

            n = Input_read_from_input(self, buffer+length, size-length);

            if (n == -1) {
                Py_DECREF(result);
                return NULL;
            }
            else if (n == 0) {
                /* Have exhausted all the available input data. */

                self->done = 1;
            }
            else {
                /*
                 * Search for embedded EOL in what was read and if
                 * found copy any residual into a buffer for use
                 * next time the read functions are called.
                 */

                p = buffer + length;
                q = p + n;

                while (p != q) {
                    length++;
                    if (*p++ == '\n')
                        break;
                }

                if (p != q) {
                    self->size = q - p;
                    self->buffer = (char *)malloc(self->size);
                    self->offset = 0;
                    self->length = self->size;

                    memcpy(self->buffer, p, self->size);
                }
            }
        }

        /*
         * Resize the final string. If the size reduction is
         * by more than 25% of the string size, then Python
         * will allocate a new block of memory and copy the
         * data into it.
         */

        if (length != size) {
            if (_PyString_Resize(&result, length))
                return NULL;
        }
    }
    else {
        /*
         * Here we have to read in a line but where we have no
         * idea how long it may be. What we can do first is if
         * we have any residual data from a previous read
         * operation, see if it contains an EOL. This means we
         * have to do a search, but this is likely going to be
         * better than having to resize and copy memory later on.
         */

        if (self->buffer && self->length) {
            const char *p = NULL;
            const char *q = NULL;

            p = self->buffer + self->offset;
            q = memchr(p, '\n', self->length);

            if (q)
                size = q - p;
        }

        /*
         * If residual data buffer didn't contain an EOL, all we
         * can do is allocate a reasonably sized string and if
         * that isn't big enough keep increasing it in size. For
         * this we will start out with a buffer 25% greater in
         * size than what is stored in the residual data buffer
         * or one the same size as Apache string size, whichever
         * is greater.
         */

        if (self->buffer && size < 0) {
            size = self->length;
            size = size + (size >> 2);
        }

        if (size < HUGE_STRING_LEN)
            size = HUGE_STRING_LEN;

        /* Allocate string of the initial size. */

        result = PyString_FromStringAndSize(NULL, size);

        if (!result)
            return NULL;

        buffer = PyString_AS_STRING((PyStringObject *)result);

        /* Copy any residual data from use of readline(). */

        if (self->buffer && self->length) {
            char *p = NULL;
            const char *q = NULL;

            p = buffer;
            q = self->buffer + self->offset;

            while (self->length && length < size) {
                self->offset++;
                self->length--;
                length++;
                if ((*p++ = *q++) == '\n')
                    break;
            }

            /* If all data in residual buffer consumed then free it. */

            if (!self->length) {
                free(self->buffer);
                self->buffer = NULL;
            }
        }

        /*
         * Read in remaining data until find an EOL, or until all
         * data has been consumed.
         */

        while ((!length || buffer[length-1] != '\n') && !self->done) {

            char *p = NULL;
            char *q = NULL;

            n = Input_read_from_input(self, buffer+length, size-length);

            if (n == -1) {
                Py_DECREF(result);
                return NULL;
            }
            else if (n == 0) {
                /* Have exhausted all the available input data. */

                self->done = 1;
            }
            else {
                /*
                 * Search for embedded EOL in what was read and if
                 * found copy any residual into a buffer for use
                 * next time the read functions are called.
                 */

                p = buffer + length;
                q = p + n;

                while (p != q) {
                    length++;
                    if (*p++ == '\n')
                        break;
                }

                if (p != q) {
                    self->size = q - p;
                    self->buffer = (char *)malloc(self->size);
                    self->offset = 0;
                    self->length = self->size;

                    memcpy(self->buffer, p, self->size);
                }

                if (buffer[length-1] != '\n' && length == size) {
                    /* Increase size of string and keep going. */

                    size = size + (size >> 2);

                    if (_PyString_Resize(&result, size))
                        return NULL;

                    buffer = PyString_AS_STRING((PyStringObject *)result);
                }
            }
        }

        /*
         * Resize the final string. If the size reduction is by
         * more than 25% of the string size, then Python will
         * allocate a new block of memory and copy the data into
         * it.
         */

        if (length != size) {
            if (_PyString_Resize(&result, length))
                return NULL;
        }
    }

    self->bytes += length;

    return result;
}

static PyObject *Input_readlines(InputObject *self, PyObject *args)
{
    long hint = 0;
    long length = 0;

    PyObject *result = NULL;
    PyObject *line = NULL;
    PyObject *rlargs = NULL;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    if (!PyArg_ParseTuple(args, "|l:readlines", &hint))
        return NULL;

    result = PyList_New(0);
    if (!result)
        return NULL;

    rlargs = PyTuple_New(0);
    if (!rlargs) {
        Py_DECREF(result);
        return NULL;
    }

    while (1) {
        long n;

        if (!(line = Input_readline(self, rlargs))) {
            Py_DECREF(result);
            result = NULL;
            break;
        }

        if ((n = PyString_Size(line)) == 0) {
            Py_DECREF(line);
            break;
        }

        if (PyList_Append(result, line) == -1) {
            Py_DECREF(line);
            Py_DECREF(result);
            result = NULL;
            break;
        }

        Py_DECREF(line);

        length += n;
        if (hint > 0 && length >= hint)
            break;
    }

    Py_DECREF(rlargs);

    return result;
}

static PyMethodDef Input_methods[] = {
    { "close",     (PyCFunction)Input_close,     METH_NOARGS, 0 },
    { "read",      (PyCFunction)Input_read,      METH_VARARGS, 0 },
    { "readline",  (PyCFunction)Input_readline,  METH_VARARGS, 0 },
    { "readlines", (PyCFunction)Input_readlines, METH_VARARGS, 0 },
    { NULL, NULL}
};

static PyObject *Input_iter(InputObject *self)
{
    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    Py_INCREF(self);
    return (PyObject *)self;
}

static PyObject *Input_iternext(InputObject *self)
{
    PyObject *line = NULL;
    PyObject *rlargs = NULL;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    rlargs = PyTuple_New(0);

    if (!rlargs)
      return NULL;

    line = Input_readline(self, rlargs);

    Py_DECREF(rlargs);

    if (!line)
        return NULL;

    if (PyString_GET_SIZE(line) == 0) {
        PyErr_SetObject(PyExc_StopIteration, Py_None);
        Py_DECREF(line);
        return NULL;
    }

    return line;
}

static PyTypeObject Input_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "mod_wsgi.Input",       /*tp_name*/
    sizeof(InputObject),    /*tp_basicsize*/
    0,                      /*tp_itemsize*/
    /* methods */
    (destructor)Input_dealloc, /*tp_dealloc*/
    0,                      /*tp_print*/
    0,                      /*tp_getattr*/
    0,                      /*tp_setattr*/
    0,                      /*tp_compare*/
    0,                      /*tp_repr*/
    0,                      /*tp_as_number*/
    0,                      /*tp_as_sequence*/
    0,                      /*tp_as_mapping*/
    0,                      /*tp_hash*/
    0,                      /*tp_call*/
    0,                      /*tp_str*/
    0,                      /*tp_getattro*/
    0,                      /*tp_setattro*/
    0,                      /*tp_as_buffer*/
#if defined(Py_TPFLAGS_HAVE_ITER)
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, /*tp_flags*/
#else
    Py_TPFLAGS_DEFAULT,     /*tp_flags*/
#endif
    0,                      /*tp_doc*/
    0,                      /*tp_traverse*/
    0,                      /*tp_clear*/
    0,                      /*tp_richcompare*/
    0,                      /*tp_weaklistoffset*/
    (getiterfunc)Input_iter, /*tp_iter*/
    (iternextfunc)Input_iternext, /*tp_iternext*/
    Input_methods,          /*tp_methods*/
    0,                      /*tp_members*/
    0,                      /*tp_getset*/
    0,                      /*tp_base*/
    0,                      /*tp_dict*/
    0,                      /*tp_descr_get*/
    0,                      /*tp_descr_set*/
    0,                      /*tp_dictoffset*/
    0,                      /*tp_init*/
    0,                      /*tp_alloc*/
    0,                      /*tp_new*/
    0,                      /*tp_free*/
    0,                      /*tp_is_gc*/
};

typedef struct {
        PyObject_HEAD
        int result;
        request_rec *r;
        apr_bucket_brigade *bb;
        WSGIRequestConfig *config;
        InputObject *input;
        PyObject *log_buffer;
        PyObject *log;
        int status;
        const char *status_line;
        PyObject *headers;
        PyObject *sequence;
        int content_length_set;
        apr_off_t content_length;
        apr_off_t output_length;
        apr_off_t output_writes;
        apr_time_t output_time;
        apr_time_t start_time;
} AdapterObject;

static PyTypeObject Adapter_Type;

static AdapterObject *newAdapterObject(request_rec *r)
{
    AdapterObject *self;

    self = PyObject_New(AdapterObject, &Adapter_Type);
    if (self == NULL)
        return NULL;

    self->result = HTTP_INTERNAL_SERVER_ERROR;

    self->r = r;

    self->bb = NULL;

    self->config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
                                                             &wsgi_module);

    self->status = HTTP_INTERNAL_SERVER_ERROR;
    self->status_line = NULL;
    self->headers = NULL;
    self->sequence = NULL;

    self->content_length_set = 0;
    self->content_length = 0;
    self->output_length = 0;
    self->output_writes = 0;

    self->output_time = 0;

    self->input = newInputObject(r, self->config->ignore_activity);

    self->log_buffer = newLogBufferObject(r, APLOG_ERR, "<wsgi.errors>", 0);
    self->log = newLogWrapperObject(self->log_buffer);

    return self;
}

static void Adapter_dealloc(AdapterObject *self)
{
    Py_XDECREF(self->headers);
    Py_XDECREF(self->sequence);

    Py_DECREF(self->input);

    Py_DECREF(self->log_buffer);
    Py_DECREF(self->log);

    PyObject_Del(self);
}

static PyObject *Adapter_start_response(AdapterObject *self, PyObject *args)
{
    PyObject *result = NULL;

    PyObject *status_line = NULL;
    PyObject *headers = NULL;
    PyObject *exc_info = Py_None;

    PyObject *status_line_as_bytes = NULL;
    PyObject *headers_as_bytes = NULL;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    if (!PyArg_ParseTuple(args, "OO!|O:start_response",
        &status_line, &PyList_Type, &headers, &exc_info)) {
        return NULL;
    }

    if (exc_info != Py_None && !PyTuple_Check(exc_info)) {
        PyErr_SetString(PyExc_RuntimeError, "exception info must be a tuple");
        return NULL;
    }

    if (exc_info != Py_None) {
        if (self->status_line && !self->headers) {
            PyObject *type = NULL;
            PyObject *value = NULL;
            PyObject *traceback = NULL;

            if (!PyArg_ParseTuple(exc_info, "OOO", &type,
                                  &value, &traceback)) {
                return NULL;
            }

            Py_INCREF(type);
            Py_INCREF(value);
            Py_INCREF(traceback);

            PyErr_Restore(type, value, traceback);

            return NULL;
        }
    }
    else if (self->status_line && !self->headers) {
        PyErr_SetString(PyExc_RuntimeError, "headers have already been sent");
        return NULL;
    }

    /* Publish event for the start of the response. */

    if (wsgi_event_subscribers()) {
        WSGIThreadInfo *thread_info;

        PyObject *event = NULL;
        PyObject *value = NULL;

        thread_info = wsgi_thread_info(0, 0);

        event = PyDict_New();

#if AP_MODULE_MAGIC_AT_LEAST(20100923,2)
        if (self->r->log_id) {
#if PY_MAJOR_VERSION >= 3
	    value = PyUnicode_DecodeLatin1(self->r->log_id,
                                           strlen(self->r->log_id), NULL);
#else
	    value = PyString_FromString(self->r->log_id);
#endif
            PyDict_SetItemString(event, "request_id", value);
            Py_DECREF(value);
        }
#endif

        PyDict_SetItemString(event, "response_status", status_line);
        PyDict_SetItemString(event, "response_headers", headers);
        PyDict_SetItemString(event, "exception_info", exc_info);

        PyDict_SetItemString(event, "request_data", thread_info->request_data);

        wsgi_publish_event("response_started", event);

        Py_DECREF(event);
    }

    status_line_as_bytes = wsgi_convert_status_line_to_bytes(status_line);

    if (!status_line_as_bytes)
        goto finally;

    headers_as_bytes = wsgi_convert_headers_to_bytes(headers);

    if (!headers_as_bytes)
        goto finally;

    self->status_line = apr_pstrdup(self->r->pool, PyString_AsString(
                                    status_line_as_bytes));
    self->status = (int)strtol(self->status_line, NULL, 10);

    Py_XDECREF(self->headers);
    self->headers = headers_as_bytes;
    Py_INCREF(headers_as_bytes);

    result = PyObject_GetAttrString((PyObject *)self, "write");

finally:
    Py_XDECREF(status_line_as_bytes);
    Py_XDECREF(headers_as_bytes);

    return result;
}

static int Adapter_output(AdapterObject *self, const char *data,
                          apr_off_t length, PyObject *string_object,
                          int exception_when_aborted)
{
    int i = 0;
    apr_status_t rv;
    request_rec *r;

    apr_time_t output_start = 0;
    apr_time_t output_finish = 0;

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (wsgi_idle_timeout && !self->config->ignore_activity) {
        apr_thread_mutex_lock(wsgi_monitor_lock);

        if (wsgi_idle_timeout) {
            wsgi_idle_shutdown_time = apr_time_now();
            wsgi_idle_shutdown_time += wsgi_idle_timeout;
        }

        apr_thread_mutex_unlock(wsgi_monitor_lock);
    }
#endif

    if (!self->status_line) {
        PyErr_SetString(PyExc_RuntimeError, "response has not been started");
        return 0;
    }

    r = self->r;

    /* Remember we started sending this block of output. */

    output_start = apr_time_now();

    /* Count how many separate blocks have been output. */

    if (string_object)
        self->output_writes++;

    /* Have response headers yet been sent. */

    if (self->headers) {
        /*
         * Apache prior to Apache 2.2.8 has a bug in it
         * whereby it doesn't force '100 Continue'
         * response before responding with headers if no
         * read. So, force a zero length read before
         * sending the headers if haven't yet attempted
         * to read anything. This will ensure that if no
         * request content has been read that any '100
         * Continue' response will be flushed and sent
         * back to the client if client was expecting
         * one. Only want to do this for 2xx and 3xx
         * status values. Note that even though Apple
         * supplied version of Apache on MacOS X Leopard
         * is newer than version 2.2.8, the header file
         * has never been patched when they make updates
         * and so anything compiled against it thinks it
         * is older.
         */

#if (AP_SERVER_MAJORVERSION_NUMBER == 2 && \
     AP_SERVER_MINORVERSION_NUMBER < 2) || \
    (AP_SERVER_MAJORVERSION_NUMBER == 2 && \
     AP_SERVER_MINORVERSION_NUMBER == 2 && \
     AP_SERVER_PATCHLEVEL_NUMBER < 8)

        if (!self->input->init) {
            if (self->status >= 200 && self->status < 400) {
                PyObject *args = NULL;
                PyObject *result = NULL;
                args = Py_BuildValue("(i)", 0);
                result = Input_read(self->input, args);
                if (PyErr_Occurred())
                    PyErr_Clear();
                Py_DECREF(args);
                Py_XDECREF(result);
            }
        }

#endif

        /*
         * Now setup the response headers in request object. We
         * have already converted any native strings in the
         * headers to byte strings and validated the format of
         * the header names and values so can skip all the error
         * checking.
         */

        r->status = self->status;
        r->status_line = self->status_line;

        for (i = 0; i < PyList_Size(self->headers); i++) {
            PyObject *tuple = NULL;

            PyObject *object1 = NULL;
            PyObject *object2 = NULL;

            char *name = NULL;
            char *value = NULL;

            tuple = PyList_GetItem(self->headers, i);

            object1 = PyTuple_GetItem(tuple, 0);
            object2 = PyTuple_GetItem(tuple, 1);

            name = PyBytes_AsString(object1);
            value = PyBytes_AsString(object2);

            if (!strcasecmp(name, "Content-Type")) {
                /*
                 * In a daemon child process we cannot call the
                 * function ap_set_content_type() as want to
                 * avoid adding any output filters based on the
                 * type of file being served as this will be
                 * done in the main Apache child process which
                 * proxied the request to the daemon process.
                 */

                if (*self->config->process_group)
                    r->content_type = apr_pstrdup(r->pool, value);
                else
                    ap_set_content_type(r, apr_pstrdup(r->pool, value));
            }
            else if (!strcasecmp(name, "Content-Length")) {
                char *endstr;
                apr_off_t length;

                if (wsgi_strtoff(&length, value, &endstr, 10)
                    || *endstr || length < 0) {

                    PyErr_SetString(PyExc_ValueError,
                                    "invalid content length");

                    output_finish = apr_time_now();

                    if (output_finish > output_start)
                        self->output_time += (output_finish - output_start);

                    return 0;
                }

                ap_set_content_length(r, length);

                self->content_length_set = 1;
                self->content_length = length;
            }
            else if (!strcasecmp(name, "WWW-Authenticate")) {
                apr_table_add(r->err_headers_out, name, value);
            }
            else {
                apr_table_add(r->headers_out, name, value);
            }
        }

        /*
         * Reset flag indicating whether '100 Continue' response
         * expected. If we don't do this then if an attempt to read
         * input for the first time is after headers have been
         * sent, then Apache is wrongly generate the '100 Continue'
         * response into the response content. Not sure if this is
         * a bug in Apache, or that it truly believes that input
         * will never be read after the response headers have been
         * sent.
         */

        r->expecting_100 = 0;

        /* No longer need headers now that they have been sent. */

        Py_DECREF(self->headers);
        self->headers = NULL;
    }

    /*
     * If content length was specified, ensure that we don't
     * actually output more data than was specified as being
     * sent as otherwise technically in violation of HTTP RFC.
     */

    if (length) {
        apr_off_t output_length = length;

        if (self->content_length_set) {
            if (self->output_length < self->content_length) {
                if (self->output_length + length > self->content_length) {
                    length = self->content_length - self->output_length;
                }
            }
            else
                length = 0;
        }

        self->output_length += output_length;
    }

    /* Now output any data. */

    if (length) {
        apr_bucket *b;

        /*
         * When using Apache 2.X can use lower level
         * bucket brigade APIs. This is preferred as
         * ap_rwrite()/ap_rflush() will grow memory in
         * the request pool on each call, which will
         * result in an increase in memory use over time
         * when streaming of data is being performed.
         * The memory is still reclaimed, but only at
         * the end of the request. Using bucket brigade
         * API avoids this, and also avoids any copying
         * of response data due to buffering performed
         * by ap_rwrite().
         */

        if (r->connection->aborted) {
            if (!exception_when_aborted) {
                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, self->r,
                              "mod_wsgi (pid=%d): Client closed connection.",
                              getpid());
            }
            else
                PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi client "
                                "connection closed.");

            output_finish = apr_time_now();

            if (output_finish > output_start)
                self->output_time += (output_finish - output_start);

            return 0;
        }

        if (!self->bb) {
            self->bb = apr_brigade_create(r->pool,
                                          r->connection->bucket_alloc);
        }

#if 0
        if (string_object) {
            b = wsgi_apr_bucket_python_create(data, length,
                    self->config->application_group, string_object,
                    r->connection->bucket_alloc);
        }
        else {
#endif
            b = apr_bucket_transient_create(data, (apr_size_t)length,
                                            r->connection->bucket_alloc);
#if 0
        }
#endif

        APR_BRIGADE_INSERT_TAIL(self->bb, b);

        b = apr_bucket_flush_create(r->connection->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(self->bb, b);

        Py_BEGIN_ALLOW_THREADS
        rv = ap_pass_brigade(r->output_filters, self->bb);
        Py_END_ALLOW_THREADS

        if (rv != APR_SUCCESS) {
            char status_buffer[512];
            const char *error_message;

            if (!exception_when_aborted) {
                error_message = apr_psprintf(r->pool, "Failed to write "
                        "response data: %s", apr_strerror(rv, status_buffer,
                        sizeof(status_buffer)-1));

                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, self->r,
                              "mod_wsgi (pid=%d): %s.", getpid(),
                              error_message);
            }
            else {
                error_message = apr_psprintf(r->pool, "Apache/mod_wsgi "
                        "failed to write response data: %s",
                        apr_strerror(rv, status_buffer,
                        sizeof(status_buffer)-1));

                PyErr_SetString(PyExc_IOError, error_message);
            }

            output_finish = apr_time_now();

            if (output_finish > output_start)
                self->output_time += (output_finish - output_start);

            return 0;
        }

        Py_BEGIN_ALLOW_THREADS
        apr_brigade_cleanup(self->bb);
        Py_END_ALLOW_THREADS
    }

    /* Add how much time we spent send this block of output. */

    output_finish = apr_time_now();

    if (output_finish > output_start)
        self->output_time += (output_finish - output_start);

    /*
     * Check whether aborted connection was found when data
     * being written, otherwise will not be flagged until next
     * time that data is being written. Early detection is
     * better as it may have been the last data block being
     * written and application may think that data has all
     * been written. In a streaming application, we also want
     * to avoid any additional data processing to generate any
     * successive data.
     */

    if (r->connection->aborted) {
        if (!exception_when_aborted) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, self->r,
                          "mod_wsgi (pid=%d): Client closed connection.",
                          getpid());
        }
        else
            PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi client "
                            "connection closed.");

        return 0;
    }

    return 1;
}

/* Split buckets at 1GB when sending large files. */

#define MAX_BUCKET_SIZE (0x40000000)

static int Adapter_output_file(AdapterObject *self, apr_file_t* tmpfile,
                               apr_off_t offset, apr_off_t len)
{
    request_rec *r;
    apr_bucket *b;
    apr_status_t rv;
    apr_bucket_brigade *bb;

    apr_file_t* dupfile = NULL;

    r = self->r;

    if (r->connection->aborted) {
        PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi client "
                        "connection closed.");
        return 0;
    }

    if (len == 0)
        return 1;

    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);

    apr_file_dup(&dupfile, tmpfile, r->pool);

    if (sizeof(apr_off_t) == sizeof(apr_size_t) || len < MAX_BUCKET_SIZE) {
        /* Can use a single bucket to send file. */

#if 0
        b = apr_bucket_file_create(tmpfile, offset, (apr_size_t)len, r->pool,
                                   r->connection->bucket_alloc);
#endif
        b = apr_bucket_file_create(dupfile, offset, (apr_size_t)len, r->pool,
                                   r->connection->bucket_alloc);
    }
    else {
        /* Need to create multiple buckets to send file. */

#if 0
        b = apr_bucket_file_create(tmpfile, offset, MAX_BUCKET_SIZE, r->pool,
                                   r->connection->bucket_alloc);
#endif
        b = apr_bucket_file_create(dupfile, offset, MAX_BUCKET_SIZE, r->pool,
                                   r->connection->bucket_alloc);

        while (len > MAX_BUCKET_SIZE) {
            apr_bucket *cb;
            apr_bucket_copy(b, &cb);
            APR_BRIGADE_INSERT_TAIL(bb, cb);
            b->start += MAX_BUCKET_SIZE;
            len -= MAX_BUCKET_SIZE;
        }

        /* Resize just the last bucket */

        b->length = (apr_size_t)len;
    }

    APR_BRIGADE_INSERT_TAIL(bb, b);

    b = apr_bucket_flush_create(r->connection->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bb, b);

    b = apr_bucket_eos_create(r->connection->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bb, b);

    Py_BEGIN_ALLOW_THREADS
    rv = ap_pass_brigade(r->output_filters, bb);
    Py_END_ALLOW_THREADS

    if (rv != APR_SUCCESS) {
        char status_buffer[512];
        const char *error_message;

        error_message = apr_psprintf(r->pool, "Apache/mod_wsgi failed "
                "to write response data: %s.", apr_strerror(rv,
                status_buffer, sizeof(status_buffer)-1));

        PyErr_SetString(PyExc_IOError, error_message);
        return 0;
    }

    Py_BEGIN_ALLOW_THREADS
    apr_brigade_destroy(bb);
    Py_END_ALLOW_THREADS

    if (r->connection->aborted) {
        PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi client connection "
                        "closed.");
        return 0;
    }

    return 1;
}

static APR_OPTIONAL_FN_TYPE(ssl_is_https) *wsgi_is_https = NULL;

static PyObject *Adapter_environ(AdapterObject *self)
{
    request_rec *r = NULL;

    PyObject *vars = NULL;
    PyObject *object = NULL;

    const apr_array_header_t *head = NULL;
    const apr_table_entry_t *elts = NULL;

    int i = 0;

    const char *scheme = NULL;

    /* Create the WSGI environment dictionary. */

    vars = PyDict_New();

    /* Merge the CGI environment into the WSGI environment. */

    r = self->r;

    head = apr_table_elts(r->subprocess_env);
    elts = (apr_table_entry_t *)head->elts;

    for (i = 0; i < head->nelts; ++i) {
        if (elts[i].key) {
            if (elts[i].val) {
#if PY_MAJOR_VERSION >= 3
                if (!strcmp(elts[i].val, "DOCUMENT_ROOT")) {
                    object = PyUnicode_Decode(elts[i].val, strlen(elts[i].val),
                                             Py_FileSystemDefaultEncoding,
                                             "surrogateescape");
                }
                else if (!strcmp(elts[i].val, "SCRIPT_FILENAME")) {
                    object = PyUnicode_Decode(elts[i].val, strlen(elts[i].val),
                                             Py_FileSystemDefaultEncoding,
                                             "surrogateescape");
                }
                else {
                    object = PyUnicode_DecodeLatin1(elts[i].val,
                                                    strlen(elts[i].val), NULL);
                }
#else
                object = PyString_FromString(elts[i].val);
#endif
                PyDict_SetItemString(vars, elts[i].key, object);
                Py_DECREF(object);
            }
            else
                PyDict_SetItemString(vars, elts[i].key, Py_None);
        }
    }

    PyDict_DelItemString(vars, "PATH");

    /* Now setup all the WSGI specific environment values. */

    object = Py_BuildValue("(ii)", 1, 0);
    PyDict_SetItemString(vars, "wsgi.version", object);
    Py_DECREF(object);

    object = PyBool_FromLong(wsgi_multithread);
    PyDict_SetItemString(vars, "wsgi.multithread", object);
    Py_DECREF(object);

    object = PyBool_FromLong(wsgi_multiprocess);
    PyDict_SetItemString(vars, "wsgi.multiprocess", object);
    Py_DECREF(object);

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (wsgi_daemon_process) {
        if (wsgi_daemon_process->group->threads == 1 &&
            wsgi_daemon_process->group->maximum_requests == 1) {
            PyDict_SetItemString(vars, "wsgi.run_once", Py_True);
        }
        else
            PyDict_SetItemString(vars, "wsgi.run_once", Py_False);
    }
    else
        PyDict_SetItemString(vars, "wsgi.run_once", Py_False);
#else
    PyDict_SetItemString(vars, "wsgi.run_once", Py_False);
#endif

    scheme = apr_table_get(r->subprocess_env, "HTTPS");

    if (scheme && (!strcasecmp(scheme, "On") || !strcmp(scheme, "1"))) {
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_FromString("https");
#else
        object = PyString_FromString("https");
#endif
        PyDict_SetItemString(vars, "wsgi.url_scheme", object);
        Py_DECREF(object);
    }
    else {
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_FromString("http");
#else
        object = PyString_FromString("http");
#endif
        PyDict_SetItemString(vars, "wsgi.url_scheme", object);
        Py_DECREF(object);
    }

    /*
     * We remove the HTTPS variable because WSGI compliant
     * applications shouldn't rely on it. Instead they should
     * use wsgi.url_scheme. We do this even if SetEnv was
     * used to set HTTPS from Apache configuration. That is
     * we convert it into the correct variable and remove the
     * original.
     */

    if (scheme)
        PyDict_DelItemString(vars, "HTTPS");

    /*
     * Setup log object for WSGI errors. Don't decrement
     * reference to log object as keep reference to it.
     */

    object = (PyObject *)self->log;
    PyDict_SetItemString(vars, "wsgi.errors", object);

    /* Setup input object for request content. */

    object = (PyObject *)self->input;
    PyDict_SetItemString(vars, "wsgi.input", object);

    PyDict_SetItemString(vars, "wsgi.input_terminated", Py_True);

    /* Setup file wrapper object for efficient file responses. */

    PyDict_SetItemString(vars, "wsgi.file_wrapper", (PyObject *)&Stream_Type);

    /* Add Apache and mod_wsgi version information. */

    object = Py_BuildValue("(iii)", AP_SERVER_MAJORVERSION_NUMBER,
                           AP_SERVER_MINORVERSION_NUMBER,
                           AP_SERVER_PATCHLEVEL_NUMBER);
    PyDict_SetItemString(vars, "apache.version", object);
    Py_DECREF(object);

    object = Py_BuildValue("(iii)", MOD_WSGI_MAJORVERSION_NUMBER,
                           MOD_WSGI_MINORVERSION_NUMBER,
                           MOD_WSGI_MICROVERSION_NUMBER);
    PyDict_SetItemString(vars, "mod_wsgi.version", object);
    Py_DECREF(object);

    /*
     * If Apache extensions are enabled and running in embedded
     * mode add a CObject reference to the Apache request_rec
     * structure instance.
     */

    if (!wsgi_daemon_pool && self->config->pass_apache_request) {
#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2) || \
    (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7)
        object = PyCapsule_New(self->r, 0, 0);
#else
        object = PyCObject_FromVoidPtr(self->r, 0);
#endif
        PyDict_SetItemString(vars, "apache.request_rec", object);
        Py_DECREF(object);
    }

    /*
     * Extensions for accessing SSL certificate information from
     * mod_ssl when in use.
     */

#if 0
    if (!wsgi_daemon_pool) {
        object = PyObject_GetAttrString((PyObject *)self, "ssl_is_https");
        PyDict_SetItemString(vars, "mod_ssl.is_https", object);
        Py_DECREF(object);

        object = PyObject_GetAttrString((PyObject *)self, "ssl_var_lookup");
        PyDict_SetItemString(vars, "mod_ssl.var_lookup", object);
        Py_DECREF(object);
    }
#endif

    return vars;
}

static int Adapter_process_file_wrapper(AdapterObject *self)
{
    int done = 0;

#ifndef WIN32
    PyObject *filelike = NULL;
    PyObject *method = NULL;
    PyObject *object = NULL;

    apr_status_t rv = 0;

    apr_os_file_t fd = -1;
    apr_file_t *tmpfile = NULL;
    apr_finfo_t finfo;

    apr_off_t fd_offset = 0;
    apr_off_t fo_offset = 0;

    apr_off_t length = 0;

    /* Perform file wrapper optimisations where possible. */

    if (!PyObject_IsInstance(self->sequence, (PyObject *)&Stream_Type))
        return 0;

    /*
     * Only attempt to perform optimisations if the
     * write() function returned by start_response()
     * function has not been called with non zero length
     * data. In other words if no prior response content
     * generated. Technically it could be done, but want
     * to have a consistent rule about how specifying a
     * content length affects how much of a file is
     * sent. Don't want to have to take into
     * consideration whether write() function has been
     * called or not as just complicates things.
     */

    if (self->output_length != 0)
        return 0;

    /*
     * Work out if file wrapper is associated with a
     * file like object, where that file object is
     * associated with a regular file. If it does then
     * we can optimise how the contents of the file are
     * sent out. If no such associated file descriptor
     * then it needs to be processed like any other
     * iterable value.
     */


    filelike = PyObject_GetAttrString((PyObject *)self->sequence, "filelike");

    if (!filelike) {
        PyErr_SetString(PyExc_KeyError,
                        "file wrapper no filelike attribute");
        return 0;
    }

    fd = PyObject_AsFileDescriptor(filelike);
    if (fd == -1) {
        PyErr_Clear();
        Py_DECREF(filelike);
        return 0;
    }

    Py_DECREF(filelike);

    /*
     * On some platforms, such as Linux, sendfile() system call
     * will not work on UNIX sockets. Thus when using daemon mode
     * cannot enable that feature.
     */

    if (self->config->enable_sendfile)
        apr_os_file_put(&tmpfile, &fd, APR_SENDFILE_ENABLED, self->r->pool);
    else
        apr_os_file_put(&tmpfile, &fd, 0, self->r->pool);

    rv = apr_file_info_get(&finfo, APR_FINFO_SIZE|APR_FINFO_TYPE, tmpfile);
    if (rv != APR_SUCCESS || finfo.filetype != APR_REG)
        return 0;

    /*
     * Because Python file like objects potentially have
     * their own buffering layering, or use an operating
     * system FILE object which also has a buffering
     * layer on top of a normal file descriptor, need to
     * determine from the file like object its position
     * within the file and use that as starting position.
     * Note that it is assumed that user had flushed any
     * modifications to the file as necessary. Also, we
     * need to make sure we remember the original file
     * descriptor position as will need to restore that
     * position so it matches the upper buffering layers
     * when done. This is done to avoid any potential
     * problems if file like object does anything strange
     * in its close() method which relies on file position
     * being what it thought it should be.
     */

    rv = apr_file_seek(tmpfile, APR_CUR, &fd_offset);
    if (rv != APR_SUCCESS)
        return 0;

    method = PyObject_GetAttrString(filelike, "tell");
    if (!method)
        return 0;

    object = PyObject_CallObject(method, NULL);
    Py_DECREF(method);

    if (!object) {
        PyErr_Clear();
        return 0;
    }

    if (PyLong_Check(object)) {
#if defined(HAVE_LONG_LONG)
        fo_offset = PyLong_AsLongLong(object);
#else
        fo_offset = PyLong_AsLong(object);
#endif
    }
#if PY_MAJOR_VERSION < 3
    else if (PyInt_Check(object)) {
        fo_offset = PyInt_AsLong(object);
    }
#endif
    else {
        Py_DECREF(object);
        return 0;
    }

    if (PyErr_Occurred()){
        Py_DECREF(object);
        PyErr_Clear();
        return 0;
    }

    Py_DECREF(object);

    /*
     * For a file wrapper object need to always ensure
     * that response headers are parsed. This is done so
     * that if the content length header has been
     * defined we can get its value and use it to limit
     * how much of a file is being sent. The WSGI 1.0
     * specification says that we are meant to send all
     * available bytes from the file, however this is
     * questionable as sending more than content length
     * would violate HTTP RFC. Note that this doesn't
     * actually flush the headers out when using Apache
     * 2.X. This is good, as we want to still be able to
     * set the content length header if none set and file
     * is seekable. If processing response headers fails,
     * then need to return as if done, with error being
     * logged later.
     */

    if (!Adapter_output(self, "", 0, NULL, 0))
        return 1;

    /*
     * If content length wasn't defined then determine
     * the amount of data which is available to send and
     * set the content length response header. Either
     * way, if can work out length then send data
     * otherwise fall through and treat it as normal
     * iterable.
     */

    if (!self->content_length_set) {
        length = finfo.size - fo_offset;
        self->output_length += length;

        ap_set_content_length(self->r, length);

        self->content_length_set = 1;
        self->content_length = length;

        if (Adapter_output_file(self, tmpfile, fo_offset, length))
            self->result = OK;

        done = 1;
    }
    else {
        length = finfo.size - fo_offset;
        self->output_length += length;

        /* Use user specified content length instead. */

        length = self->content_length;

        if (Adapter_output_file(self, tmpfile, fo_offset, length))
            self->result = OK;

        done = 1;
    }

    /*
     * Restore position of underlying file descriptor.
     * If this fails, then not much we can do about it.
     */

    apr_file_seek(tmpfile, APR_SET, &fd_offset);

#endif

    return done;
}

static int Adapter_run(AdapterObject *self, PyObject *object)
{
    PyObject *vars = NULL;
    PyObject *start = NULL;
    PyObject *args = NULL;
    PyObject *iterator = NULL;
    PyObject *close = NULL;

    PyObject *nrwrapper = NULL;
    PyObject *evwrapper = NULL;

    PyObject *value = NULL;
    PyObject *event = NULL;

    const char *msg = NULL;
    apr_off_t length = 0;

    WSGIThreadInfo *thread_handle = NULL;

    apr_time_t finish_time;

    WSGIThreadCPUUsage start_usage;
    WSGIThreadCPUUsage end_usage;

    int aborted = 0;

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (wsgi_idle_timeout && !self->config->ignore_activity) {
        apr_thread_mutex_lock(wsgi_monitor_lock);

        if (wsgi_idle_timeout) {
            wsgi_idle_shutdown_time = apr_time_now();
            wsgi_idle_shutdown_time += wsgi_idle_timeout;
        }

        apr_thread_mutex_unlock(wsgi_monitor_lock);
    }
#endif

    if (wsgi_newrelic_config_file) {
        PyObject *module = NULL;

        module = PyImport_ImportModule("newrelic.agent");

        if (module) {
            PyObject *dict;
            PyObject *factory;

            dict = PyModule_GetDict(module);
            factory = PyDict_GetItemString(dict, "WSGIApplicationWrapper");

            if (factory) {
                Py_INCREF(factory);

                nrwrapper = PyObject_CallFunctionObjArgs(
                        factory, object, Py_None, NULL);

                if (!nrwrapper) {
                    wsgi_log_python_error(self->r, self->log,
                                          self->r->filename, 0);
                    PyErr_Clear();
                }

                Py_DECREF(factory);
            }

            Py_DECREF(module);
        }
    }

    if (nrwrapper)
        object = nrwrapper;

    self->start_time = apr_time_now();

    apr_table_setn(self->r->subprocess_env, "mod_wsgi.script_start",
                   apr_psprintf(self->r->pool, "%" APR_TIME_T_FMT,
                   self->start_time));

    vars = Adapter_environ(self);

    value = wsgi_PyInt_FromLongLong(wsgi_total_requests);
    PyDict_SetItemString(vars, "mod_wsgi.total_requests", value);
    Py_DECREF(value);

    thread_handle = wsgi_thread_info(1, 1);

    value = wsgi_PyInt_FromLong(thread_handle->thread_id);
    PyDict_SetItemString(vars, "mod_wsgi.thread_id", value);
    Py_DECREF(value);

    value = wsgi_PyInt_FromLongLong(thread_handle->request_count);
    PyDict_SetItemString(vars, "mod_wsgi.thread_requests", value);
    Py_DECREF(value);

    /* Publish event for the start of the request. */

    start_usage.user_time = 0.0;
    start_usage.system_time = 0.0;

    if (wsgi_event_subscribers()) {
        wsgi_thread_cpu_usage(&start_usage);

        event = PyDict_New();

#if AP_MODULE_MAGIC_AT_LEAST(20100923,2)
        if (self->r->log_id) {
#if PY_MAJOR_VERSION >= 3
	    value = PyUnicode_DecodeLatin1(self->r->log_id,
                                           strlen(self->r->log_id), NULL);
#else
	    value = PyString_FromString(self->r->log_id);
#endif
            PyDict_SetItemString(event, "request_id", value);
            Py_DECREF(value);
        }
#endif

        value = wsgi_PyInt_FromLong(thread_handle->thread_id);
        PyDict_SetItemString(event, "thread_id", value);
        Py_DECREF(value);

        value = wsgi_PyInt_FromLong(self->config->daemon_connects);
        PyDict_SetItemString(event, "daemon_connects", value);
        Py_DECREF(value);

        value = wsgi_PyInt_FromLong(self->config->daemon_restarts);
        PyDict_SetItemString(event, "daemon_restarts", value);
        Py_DECREF(value);

        value = PyFloat_FromDouble(apr_time_sec(
                                   (double)self->config->request_start));
        PyDict_SetItemString(event, "request_start", value);
        Py_DECREF(value);

        value = PyFloat_FromDouble(apr_time_sec(
                                   (double)self->config->queue_start));
        PyDict_SetItemString(event, "queue_start", value);
        Py_DECREF(value);

        value = PyFloat_FromDouble(apr_time_sec(
                                   (double)self->config->daemon_start));
        PyDict_SetItemString(event, "daemon_start", value);
        Py_DECREF(value);

        PyDict_SetItemString(event, "application_object", object);

        PyDict_SetItemString(event, "request_environ", vars);

        value = PyFloat_FromDouble(apr_time_sec((double)self->start_time));
        PyDict_SetItemString(event, "application_start", value);
        Py_DECREF(value);

        PyDict_SetItemString(event, "request_data", thread_handle->request_data);

        wsgi_publish_event("request_started", event);

        evwrapper = PyDict_GetItemString(event, "application_object");

        if (evwrapper) {
            if (evwrapper != object) {
                Py_INCREF(evwrapper);
                object = evwrapper;
            }
            else
                evwrapper = NULL;
        }

        Py_DECREF(event);
    }

    /* Pass the request through to the WSGI application. */

    thread_handle->request_count++;

    start = PyObject_GetAttrString((PyObject *)self, "start_response");

    args = Py_BuildValue("(OO)", vars, start);

    self->sequence = PyObject_CallObject(object, args);

    if (self->sequence != NULL) {
        if (!Adapter_process_file_wrapper(self)) {
            iterator = PyObject_GetIter(self->sequence);

            if (iterator != NULL) {
                PyObject *item = NULL;

                while ((item = PyIter_Next(iterator))) {
                    if (!PyString_Check(item)) {
                        PyErr_Format(PyExc_TypeError, "sequence of byte "
                                     "string values expected, value of "
                                     "type %.200s found",
                                     item->ob_type->tp_name);
                        Py_DECREF(item);
                        break;
                    }

                    msg = PyString_AsString(item);
                    length = PyString_Size(item);

                    if (!msg) {
                        Py_DECREF(item);
                        break;
                    }

                    if (length && !Adapter_output(self, msg, length,
                                item, 0)) {
                        if (!PyErr_Occurred())
                            aborted = 1;
                        Py_DECREF(item);
                        break;
                    }

                    Py_DECREF(item);
                }
            }

            if (!PyErr_Occurred()) {
                if (!aborted) {
                    /*
                     * In the case where the response was empty we
                     * need to ensure we explicitly flush out the
                     * headers. This is done by calling the output
                     * routine but with an empty string as content.
                     * This could be gated on whether any content
                     * had already been sent, but easier to just call
                     * it all the time.
                     */

                    if (Adapter_output(self, "", 0, NULL, 0))
                        self->result = OK;
                }
                else {
                    /*
                     * If the client connection was already marked
                     * as aborted, then it indicates the client has
                     * closed the connection. In this case mark the
                     * final result as okay rather than an error so
                     * that the access log still records the original
                     * HTTP response code for the request rather than
                     * overriding it. If don't do this then access
                     * log will show 500 when the WSGI application
                     * itself had run fine.
                     */

                    self->result = OK;
                }
            }

            Py_XDECREF(iterator);
        }

        /*
         * Log warning if more response content generated than was
         * indicated, or less, if there was no errors generated by
         * the application and connection wasn't aborted.
         */

        if (self->content_length_set && ((!PyErr_Occurred() && !aborted &&
            self->output_length != self->content_length) ||
            (self->output_length > self->content_length))) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, self->r,
                          "mod_wsgi (pid=%d): Content length mismatch, "
                          "expected %s, response generated %s: %s", getpid(),
                          apr_off_t_toa(self->r->pool, self->content_length),
                          apr_off_t_toa(self->r->pool, self->output_length),
                          self->r->filename);
        }

        if (PyErr_Occurred()) {
            /*
             * Response content has already been sent, so cannot
             * return an internal server error as Apache will
             * append its own error page. Thus need to return OK
             * and just truncate the response.
             */

            if (self->status_line && !self->headers)
                self->result = OK;

            wsgi_log_python_error(self->r, self->log, self->r->filename, 1);

            /*
             * If response content is being chunked and an error
             * occurred, we need to prevent the sending of the EOS
             * bucket so a client is able to detect that the the
             * response was incomplete.
             */

            if (self->r->chunked)
                self->r->eos_sent = 1;
        }

        if (PyObject_HasAttrString(self->sequence, "close")) {
            PyObject *args = NULL;
            PyObject *data = NULL;

            close = PyObject_GetAttrString(self->sequence, "close");

            args = Py_BuildValue("()");
            data = PyObject_CallObject(close, args);

            Py_DECREF(args);
            Py_XDECREF(data);
            Py_DECREF(close);
        }

        if (PyErr_Occurred())
            wsgi_log_python_error(self->r, self->log, self->r->filename, 1);
    }
    else
        wsgi_log_python_error(self->r, self->log, self->r->filename, 1);

    /* Publish event for the end of the request. */

    finish_time = apr_time_now();

    if (wsgi_event_subscribers()) {
        double application_time = 0.0;
        double output_time = 0.0;

        event = PyDict_New();

#if AP_MODULE_MAGIC_AT_LEAST(20100923,2)
        if (self->r->log_id) {
#if PY_MAJOR_VERSION >= 3
	    value = PyUnicode_DecodeLatin1(self->r->log_id,
                                           strlen(self->r->log_id), NULL);
#else
	    value = PyString_FromString(self->r->log_id);
#endif
            PyDict_SetItemString(event, "request_id", value);
            Py_DECREF(value);
        }
#endif

        value = wsgi_PyInt_FromLongLong(self->input->reads);
        PyDict_SetItemString(event, "input_reads", value);
        Py_DECREF(value);

        value = wsgi_PyInt_FromLongLong(self->input->bytes);
        PyDict_SetItemString(event, "input_length", value);
        Py_DECREF(value);

        value = PyFloat_FromDouble(apr_time_sec((double)self->input->time));
        PyDict_SetItemString(event, "input_time", value);
        Py_DECREF(value);

        value = wsgi_PyInt_FromLongLong(self->output_length);
        PyDict_SetItemString(event, "output_length", value);
        Py_DECREF(value);

        value = wsgi_PyInt_FromLongLong(self->output_writes);
        PyDict_SetItemString(event, "output_writes", value);
        Py_DECREF(value);

        output_time = apr_time_sec((double)self->output_time);

        if (output_time < 0.0)
            output_time = 0.0;

        application_time = apr_time_sec((double)finish_time-self->start_time);

        if (application_time < 0.0)
            application_time = 0.0;

        if (start_usage.user_time != 0.0) {
            if (wsgi_thread_cpu_usage(&end_usage)) {
                double user_seconds;
                double system_seconds;
                double total_seconds;

                user_seconds = end_usage.user_time;
                user_seconds -= start_usage.user_time;

                if (user_seconds < 0.0)
                    user_seconds = 0.0;

                system_seconds = end_usage.system_time;
                system_seconds -= start_usage.system_time;

                if (system_seconds < 0.0)
                    system_seconds = 0.0;

                total_seconds = user_seconds + system_seconds;

                if (total_seconds && total_seconds > application_time) {
                    user_seconds = (user_seconds/total_seconds)*application_time;
                    system_seconds = application_time - user_seconds;
                }

                value = PyFloat_FromDouble(user_seconds);
                PyDict_SetItemString(event, "cpu_user_time", value);
                Py_DECREF(value);

                value = PyFloat_FromDouble(system_seconds);
                PyDict_SetItemString(event, "cpu_system_time", value);
                Py_DECREF(value);
            }
        }

        value = PyFloat_FromDouble(output_time);
        PyDict_SetItemString(event, "output_time", value);
        Py_DECREF(value);

        value = PyFloat_FromDouble(apr_time_sec((double)finish_time));
        PyDict_SetItemString(event, "application_finish", value);
        Py_DECREF(value);

        value = PyFloat_FromDouble(application_time);
        PyDict_SetItemString(event, "application_time", value);
        Py_DECREF(value);

        PyDict_SetItemString(event, "request_data", thread_handle->request_data);

        wsgi_publish_event("request_finished", event);

        Py_DECREF(event);
    }

    /*
     * Record server and application time for metrics. Values
     * are the time request first accepted by child workers,
     * the time that the WSGI application started processing
     * the request, and when the WSGI application finished the
     * request.
     */

    wsgi_record_request_times(self->config->request_start,
            self->config->queue_start, self->config->daemon_start,
            self->start_time, finish_time);

    /*
     * If result indicates an internal server error, then
     * replace the status line in the request object else
     * that provided by the application will be what is used
     * in any error page automatically generated by Apache.
     */

    if (self->result == HTTP_INTERNAL_SERVER_ERROR)
        self->r->status_line = "500 Internal Server Error";

    Py_DECREF(args);
    Py_DECREF(start);
    Py_DECREF(vars);

    Py_XDECREF(nrwrapper);
    Py_XDECREF(evwrapper);

    Py_XDECREF(self->sequence);
    self->sequence = NULL;

    return self->result;
}

static PyObject *Adapter_write(AdapterObject *self, PyObject *args)
{
    PyObject *item = NULL;
    const char *data = NULL;
    long length = 0;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    if (!PyArg_ParseTuple(args, "O:write", &item))
        return NULL;

    if (!PyString_Check(item)) {
        PyErr_Format(PyExc_TypeError, "byte string value expected, value "
                     "of type %.200s found", item->ob_type->tp_name);
        return NULL;
    }

    data = PyString_AsString(item);
    length = PyString_Size(item);

    if (!Adapter_output(self, data, length, item, 1)) {
        return NULL;
    }

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *Adapter_ssl_is_https(AdapterObject *self, PyObject *args)
{
    APR_OPTIONAL_FN_TYPE(ssl_is_https) *ssl_is_https = 0;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    if (!PyArg_ParseTuple(args, ":ssl_is_https"))
        return NULL;

    ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);

    if (ssl_is_https == 0)
      return Py_BuildValue("i", 0);

    return Py_BuildValue("i", ssl_is_https(self->r->connection));
}

static PyObject *Adapter_ssl_var_lookup(AdapterObject *self, PyObject *args)
{
    APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *ssl_var_lookup = 0;

    PyObject *item = NULL;
    PyObject *latin_item = NULL;

    char *name = 0;
    char *value = 0;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    if (!PyArg_ParseTuple(args, "O:ssl_var_lookup", &item))
        return NULL;

#if PY_MAJOR_VERSION >= 3
    if (PyUnicode_Check(item)) {
        latin_item = PyUnicode_AsLatin1String(item);
        if (!latin_item) {
            PyErr_Format(PyExc_TypeError, "byte string value expected, "
                         "value containing non 'latin-1' characters found");

            return NULL;
        }

        item = latin_item;
    }
#endif

    if (!PyString_Check(item)) {
        PyErr_Format(PyExc_TypeError, "byte string value expected, value "
                     "of type %.200s found", item->ob_type->tp_name);

        Py_XDECREF(latin_item);

        return NULL;
    }

    name = PyString_AsString(item);

    ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);

    if (ssl_var_lookup == 0)
    {
        Py_XDECREF(latin_item);

        Py_INCREF(Py_None);

        return Py_None;
    }

    value = ssl_var_lookup(self->r->pool, self->r->server,
                           self->r->connection, self->r, name);

    Py_XDECREF(latin_item);

    if (!value) {
        Py_INCREF(Py_None);

        return Py_None;
    }

#if PY_MAJOR_VERSION >= 3
    return PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    return PyString_FromString(value);
#endif
}

static PyMethodDef Adapter_methods[] = {
    { "start_response", (PyCFunction)Adapter_start_response, METH_VARARGS, 0 },
    { "write",          (PyCFunction)Adapter_write, METH_VARARGS, 0 },
    { "ssl_is_https",   (PyCFunction)Adapter_ssl_is_https, METH_VARARGS, 0 },
    { "ssl_var_lookup", (PyCFunction)Adapter_ssl_var_lookup, METH_VARARGS, 0 },
    { NULL, NULL}
};

static PyTypeObject Adapter_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "mod_wsgi.Adapter",     /*tp_name*/
    sizeof(AdapterObject),  /*tp_basicsize*/
    0,                      /*tp_itemsize*/
    /* methods */
    (destructor)Adapter_dealloc, /*tp_dealloc*/
    0,                      /*tp_print*/
    0,                      /*tp_getattr*/
    0,                      /*tp_setattr*/
    0,                      /*tp_compare*/
    0,                      /*tp_repr*/
    0,                      /*tp_as_number*/
    0,                      /*tp_as_sequence*/
    0,                      /*tp_as_mapping*/
    0,                      /*tp_hash*/
    0,                      /*tp_call*/
    0,                      /*tp_str*/
    0,                      /*tp_getattro*/
    0,                      /*tp_setattro*/
    0,                      /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,     /*tp_flags*/
    0,                      /*tp_doc*/
    0,                      /*tp_traverse*/
    0,                      /*tp_clear*/
    0,                      /*tp_richcompare*/
    0,                      /*tp_weaklistoffset*/
    0,                      /*tp_iter*/
    0,                      /*tp_iternext*/
    Adapter_methods,        /*tp_methods*/
    0,                      /*tp_members*/
    0,                      /*tp_getset*/
    0,                      /*tp_base*/
    0,                      /*tp_dict*/
    0,                      /*tp_descr_get*/
    0,                      /*tp_descr_set*/
    0,                      /*tp_dictoffset*/
    0,                      /*tp_init*/
    0,                      /*tp_alloc*/
    0,                      /*tp_new*/
    0,                      /*tp_free*/
    0,                      /*tp_is_gc*/
};

/*
 * Code for importing a module from source by absolute path.
 */

static PyObject *wsgi_load_source(apr_pool_t *pool, request_rec *r,
                                  const char *name, int exists,
                                  const char* filename,
                                  const char *process_group,
                                  const char *application_group,
                                  int ignore_system_exit)
{
    PyObject *m = NULL;
    PyObject *co = NULL;
    PyObject *io_module = NULL;
    PyObject *fileobject = NULL;
    PyObject *source_bytes_object = NULL;
    PyObject *result = NULL;
    char *source_buf = NULL;

    if (exists) {
        Py_BEGIN_ALLOW_THREADS
        if (r) {
            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
                          "mod_wsgi (pid=%d, process='%s', application='%s'): "
                          "Reloading WSGI script '%s'.", getpid(),
                          process_group, application_group, filename);
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                         "mod_wsgi (pid=%d, process='%s', application='%s'): "
                         "Reloading WSGI script '%s'.", getpid(),
                         process_group, application_group, filename);
        }
        Py_END_ALLOW_THREADS
    }
    else {
        Py_BEGIN_ALLOW_THREADS
        if (r) {
            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
                          "mod_wsgi (pid=%d, process='%s', application='%s'): "
                          "Loading Python script file '%s'.", getpid(),
                          process_group, application_group, filename);
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                         "mod_wsgi (pid=%d, process='%s', application='%s'): "
                         "Loading Python script file '%s'.", getpid(),
                         process_group, application_group, filename);
        }
        Py_END_ALLOW_THREADS
    }

    io_module = PyImport_ImportModule("io");

    if (!io_module)
        goto load_source_finally;

    fileobject = PyObject_CallMethod(io_module, "open", "ss", filename, "rb");

    if (!fileobject)
        goto load_source_finally;

    source_bytes_object = PyObject_CallMethod(fileobject, "read", "");

    if (!source_bytes_object)
        goto load_source_finally;

    result = PyObject_CallMethod(fileobject, "close", "");

    if (!result)
        goto load_source_finally;

    source_buf = PyBytes_AsString(source_bytes_object);

    if (!source_buf)
        goto load_source_finally;

    co = Py_CompileString(source_buf, filename, Py_file_input);

load_source_finally:
    if (!co) {
        Py_BEGIN_ALLOW_THREADS
        if (r) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
                          "mod_wsgi (pid=%d, process='%s', application='%s'): "
                          "Could not read/compile source file '%s'.", getpid(),
                          process_group, application_group, filename);
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_ERR, errno, wsgi_server,
                         "mod_wsgi (pid=%d, process='%s', application='%s'): "
                         "Could not read/compile source file '%s'.", getpid(),
                         process_group, application_group, filename);
        }
        Py_END_ALLOW_THREADS

        wsgi_log_python_error(r, NULL, filename, 0);

        Py_XDECREF(io_module);
        Py_XDECREF(fileobject);
        Py_XDECREF(source_bytes_object);
        Py_XDECREF(result);

        return NULL;
    }

    Py_XDECREF(io_module);
    Py_XDECREF(fileobject);
    Py_XDECREF(source_bytes_object);
    Py_XDECREF(result);

    m = PyImport_ExecCodeModuleEx((char *)name, co, (char *)filename);

    if (m) {
        PyObject *object = NULL;

        if (!r || strcmp(r->filename, filename)) {
            apr_finfo_t finfo;
            apr_status_t status;

            Py_BEGIN_ALLOW_THREADS
            status = apr_stat(&finfo, filename, APR_FINFO_NORM, pool);
            Py_END_ALLOW_THREADS

            if (status != APR_SUCCESS)
                object = PyLong_FromLongLong(0);
            else
                object = PyLong_FromLongLong(finfo.mtime);
        }
        else {
            object = PyLong_FromLongLong(r->finfo.mtime);
        }
        PyModule_AddObject(m, "__mtime__", object);
    }
    else {
        if (PyErr_ExceptionMatches(PyExc_SystemExit)) {
            if (!ignore_system_exit) {
                Py_BEGIN_ALLOW_THREADS
                if (r) {
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                  "mod_wsgi (pid=%d): SystemExit exception "
                                  "raised when doing exec of Python script "
                                  "file '%s'.", getpid(), filename);
                }
                else {
                    ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): SystemExit exception "
                                 "raised when doing exec of Python script "
                                 "file '%s'.", getpid(), filename);
                }
                Py_END_ALLOW_THREADS
            }
        }
        else {
            Py_BEGIN_ALLOW_THREADS
            if (r) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                              "mod_wsgi (pid=%d): Failed to exec Python script "
                              "file '%s'.", getpid(), filename);
            }
            else {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                             "mod_wsgi (pid=%d): Failed to exec Python script "
                             "file '%s'.", getpid(), filename);
            }
            Py_END_ALLOW_THREADS

            wsgi_log_python_error(r, NULL, filename, 0);
        }
    }

    Py_XDECREF(co);

    return m;
}

static int wsgi_reload_required(apr_pool_t *pool, request_rec *r,
                                const char *filename, PyObject *module,
                                const char *resource)
{
    PyObject *dict = NULL;
    PyObject *object = NULL;
    apr_time_t mtime = 0;

    dict = PyModule_GetDict(module);
    object = PyDict_GetItemString(dict, "__mtime__");

    if (object) {
        mtime = PyLong_AsLongLong(object);

        if (!r || strcmp(r->filename, filename)) {
            apr_finfo_t finfo;
            apr_status_t status;

            Py_BEGIN_ALLOW_THREADS
            status = apr_stat(&finfo, filename, APR_FINFO_NORM, pool);
            Py_END_ALLOW_THREADS

            if (status != APR_SUCCESS)
                return 1;
            else if (mtime != finfo.mtime)
                return 1;
        }
        else {
            if (mtime != r->finfo.mtime)
                return 1;
        }
    }
    else
        return 1;

    if (resource) {
        PyObject *dict = NULL;
        PyObject *object = NULL;

        dict = PyModule_GetDict(module);
        object = PyDict_GetItemString(dict, "reload_required");

        if (object) {
            PyObject *args = NULL;
            PyObject *result = NULL;
#if PY_MAJOR_VERSION >= 3
            PyObject *path = NULL;
#endif

            Py_INCREF(object);
#if PY_MAJOR_VERSION >= 3
	    path = PyUnicode_Decode(resource, strlen(resource),
				     Py_FileSystemDefaultEncoding,
				     "surrogateescape");
            args = Py_BuildValue("(O)", path);
            Py_DECREF(path);
#else
            args = Py_BuildValue("(s)", resource);
#endif
            result = PyObject_CallObject(object, args);
            Py_DECREF(args);
            Py_DECREF(object);

            if (result && PyObject_IsTrue(result)) {
                Py_DECREF(result);

                return 1;
            }

            if (PyErr_Occurred())
                wsgi_log_python_error(r, NULL, filename, 0);

            Py_XDECREF(result);
        }
    }

    return 0;
}

static char *wsgi_module_name(apr_pool_t *pool, const char *filename)
{
    char *hash = NULL;
    char *file = NULL;

    /*
     * Calculate a name for the module using the MD5 of its full
     * pathname. This is so that different code files with the
     * same basename are still considered unique. Note that where
     * we believe a case insensitive file system is being used,
     * we always change the file name to lower case so that use
     * of different case in name doesn't result in duplicate
     * modules being loaded for the same file.
     */

    file = (char *)filename;

    if (wsgi_server_config->case_sensitivity) {
        file = apr_pstrdup(pool, file);
        ap_str_tolower(file);
    }

    hash = ap_md5(pool, (const unsigned char *)file);
    return apr_pstrcat(pool, "_mod_wsgi_", hash, NULL);
}

#if APR_HAS_THREADS
static apr_thread_mutex_t* wsgi_module_lock = NULL;
#endif

static int wsgi_execute_script(request_rec *r)
{
    WSGIRequestConfig *config = NULL;

    InterpreterObject *interp = NULL;
    PyObject *modules = NULL;
    PyObject *module = NULL;
    const char *script = NULL;
    const char *name = NULL;
    int exists = 0;

    int status;

    WSGIThreadInfo *thread_info = NULL;

    /* Grab request configuration. */

    config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
                                                       &wsgi_module);

    /*
     * Acquire the desired python interpreter. Once this is done
     * it is safe to start manipulating python objects.
     */

    interp = wsgi_acquire_interpreter(config->application_group);

    if (!interp) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "mod_wsgi (pid=%d): Cannot acquire interpreter '%s'.",
                      getpid(), config->application_group);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* Setup startup timeout if first request and specified. */

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (wsgi_daemon_process) {
        if (wsgi_startup_shutdown_time == 0) {
            if (wsgi_startup_timeout > 0) {
                ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                             "mod_wsgi (pid=%d): Application startup "
                             "timer triggered '%s'.", getpid(),
                             config->process_group);

                apr_thread_mutex_lock(wsgi_monitor_lock);
                wsgi_startup_shutdown_time = apr_time_now();
                wsgi_startup_shutdown_time += wsgi_startup_timeout;
                apr_thread_mutex_unlock(wsgi_monitor_lock);
            }
        }
    }
#endif

    /*
     * Use a lock around the check to see if the module is
     * already loaded and the import of the module to prevent
     * two request handlers trying to import the module at the
     * same time.
     */

#if APR_HAS_THREADS
    Py_BEGIN_ALLOW_THREADS
    apr_thread_mutex_lock(wsgi_module_lock);
    Py_END_ALLOW_THREADS
#endif

    /* Calculate the Python module name to be used for script. */

    if (config->handler_script && *config->handler_script) {
        script = config->handler_script;

#if 0
        /*
         * Check for whether a module reference is provided
         * as opposed to a filesystem path.
         */

        if (strlen(script) > 2 && script[0] == '(' &&
            script[strlen(script)-1] == ')') {
            name = apr_pstrndup(r->pool, script+1, strlen(script)-2);

            module = PyImport_ImportModule(name);

            if (!module) {
                Py_BEGIN_ALLOW_THREADS
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                             "mod_wsgi (pid=%d): Failed to import handler "
                             "via Python module reference %s.", getpid(),
                             script);
                Py_END_ALLOW_THREADS

                wsgi_log_python_error(r, NULL, r->filename, 0);
            }
        }
#endif
    }
    else
        script = r->filename;

    if (!module) {
        name = wsgi_module_name(r->pool, script);

        modules = PyImport_GetModuleDict();
        module = PyDict_GetItemString(modules, name);

        Py_XINCREF(module);

        if (module)
            exists = 1;

        /*
         * If script reloading is enabled and the module for it has
         * previously been loaded, see if it has been modified since
         * the last time it was accessed. For a handler script will
         * also see if it contains a custom function for determining
         * if a reload should be performed.
         */

        if (module && config->script_reloading) {
            if (wsgi_reload_required(r->pool, r, script, module, r->filename)) {
                /*
                 * Script file has changed. Discard reference to
                 * loaded module and work out what action we are
                 * supposed to take. Choices are process reloading
                 * and module reloading. Process reloading cannot be
                 * performed unless a daemon process is being used.
                 */

                Py_DECREF(module);
                module = NULL;

#if defined(MOD_WSGI_WITH_DAEMONS)
                if (*config->process_group) {
                    /*
                     * Need to restart the daemon process. We bail
                     * out on the request process here, sending back
                     * a special response header indicating that
                     * process is being restarted and that remote
                     * end should abandon connection and attempt to
                     * reconnect again. We also need to signal this
                     * process so it will actually shutdown. The
                     * process supervisor code will ensure that it
                     * is restarted.
                     */

                    Py_BEGIN_ALLOW_THREADS
                    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
                                 "mod_wsgi (pid=%d): Force restart of "
                                 "process '%s'.", getpid(),
                                 config->process_group);
                    Py_END_ALLOW_THREADS

#if APR_HAS_THREADS
                    apr_thread_mutex_unlock(wsgi_module_lock);
#endif

                    wsgi_release_interpreter(interp);

                    r->status = HTTP_INTERNAL_SERVER_ERROR;
                    r->status_line = "200 Rejected";

                    wsgi_daemon_shutdown++;
                    kill(getpid(), SIGINT);

                    return OK;
                }
                else {
                    /*
                     * Need to reload just the script module. Remove
                     * the module from the modules dictionary before
                     * reloading it again. If code is executing
                     * within the module at the time, the callers
                     * reference count on the module should ensure
                     * it isn't actually destroyed until it is
                     * finished.
                     */

                    PyDict_DelItemString(modules, name);
                }
#else
                /*
                 * Need to reload just the script module. Remove
                 * the module from the modules dictionary before
                 * reloading it again. If code is executing
                 * within the module at the time, the callers
                 * reference count on the module should ensure
                 * it isn't actually destroyed until it is
                 * finished.
                 */

                PyDict_DelItemString(modules, name);
#endif
            }
        }
    }

    /*
     * When process reloading is in use, or a queue timeout is
     * set, need to indicate that request content should now be
     * sent through. This is done by writing a special response
     * header directly out onto the appropriate network output
     * filter. The special response is picked up by remote end
     * and data will then be sent.
     */

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (*config->process_group && (config->script_reloading ||
                wsgi_daemon_process->group->queue_timeout != 0)) {

        ap_filter_t *filters;
        apr_bucket_brigade *bb;
        apr_bucket *b;

        const char *data = "Status: 200 Continue\r\n\r\n";
        long length = strlen(data);

        Py_BEGIN_ALLOW_THREADS

        filters = r->output_filters;
        while (filters && filters->frec->ftype != AP_FTYPE_NETWORK) {
            filters = filters->next;
        }

        bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);

        b = apr_bucket_transient_create(data, length,
                                        r->connection->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, b);

        b = apr_bucket_flush_create(r->connection->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, b);

        /*
         * This should always work, so ignore any errors
         * from passing the brigade to the network
         * output filter. If there are are problems they
         * will be picked up further down in processing
         * anyway.
         */

        ap_pass_brigade(filters, bb);

        Py_END_ALLOW_THREADS
    }
#endif

    /* Setup metrics for start of request. */

    thread_info = wsgi_start_request(r);

    /* Load module if not already loaded. */

    if (!module) {
        module = wsgi_load_source(r->pool, r, name, exists, script,
                                  config->process_group,
                                  config->application_group, 0);
    }

    /* Safe now to release the module lock. */

#if APR_HAS_THREADS
    apr_thread_mutex_unlock(wsgi_module_lock);
#endif

    /*
     * Clear startup timeout and prevent from running again if the
     * module was successfully loaded.
     */

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (module && wsgi_startup_shutdown_time > 0) {
        wsgi_startup_shutdown_time = -1;

        ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Application startup "
                     "timer cancelled '%s'.", getpid(),
                     config->process_group);
    }
#endif

    /* Assume an internal server error unless everything okay. */

    status = HTTP_INTERNAL_SERVER_ERROR;

    /* Determine if script exists and execute it. */

    if (module) {
        PyObject *module_dict = NULL;
        PyObject *object = NULL;

        module_dict = PyModule_GetDict(module);
        object = PyDict_GetItemString(module_dict, config->callable_object);

        if (object) {
            AdapterObject *adapter = NULL;
            adapter = newAdapterObject(r);

            if (adapter) {
                PyObject *method = NULL;
                PyObject *args = NULL;

                Py_INCREF(adapter->log_buffer);
                thread_info->log_buffer = adapter->log_buffer;

                Py_INCREF(object);
                status = Adapter_run(adapter, object);
                Py_DECREF(object);

                /*
                 * Wipe out references to Apache request object
                 * held by Python objects, so can detect when an
                 * application holds on to the transient Python
                 * objects beyond the life of the request and
                 * thus raise an exception if they are used.
                 */

                adapter->r = NULL;

                Input_finish(adapter->input);

                /* Close the log object so data is flushed. */

                method = PyObject_GetAttrString(adapter->log, "close");

                if (!method) {
                    PyErr_Format(PyExc_AttributeError,
                                 "'%s' object has no attribute 'close'",
                                 adapter->log->ob_type->tp_name);
                }
                else {
                    args = PyTuple_New(0);
                    object = PyObject_CallObject(method, args);
                    Py_DECREF(args);
                }

                Py_XDECREF(object);
                Py_XDECREF(method);

                Py_CLEAR(thread_info->log_buffer);

                adapter->bb = NULL;
            }

            Py_XDECREF((PyObject *)adapter);
        }
        else {
            Py_BEGIN_ALLOW_THREADS
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "mod_wsgi (pid=%d): Target WSGI script '%s' does "
                          "not contain WSGI application '%s'.",
                          getpid(), script, config->callable_object);
            Py_END_ALLOW_THREADS

            status = HTTP_NOT_FOUND;
        }
    }

    /* Log any details of exceptions if execution failed. */

    if (PyErr_Occurred())
        wsgi_log_python_error(r, NULL, r->filename, 0);

    Py_XDECREF(module);

    /* Finalise any metrics at end of the request. */

    wsgi_end_request();

    /* Cleanup and release interpreter, */

    wsgi_release_interpreter(interp);

    return status;
}

/*
 * Apache child process initialisation and cleanup. Initialise
 * global table containing Python interpreter instances and
 * cache reference to main interpreter. Also register cleanup
 * function to delete interpreter on process shutdown.
 */

static apr_status_t wsgi_python_child_cleanup(void *data)
{
    PyObject *interp = NULL;

    /*
     * If not a daemon process need to publish that process
     * is shutting down here. For daemon we did it earlier
     * before trying to wait on request threads.
     */

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (!wsgi_daemon_process)
        wsgi_publish_process_stopping(wsgi_shutdown_reason);
#else
    wsgi_publish_process_stopping(wsgi_shutdown_reason);
#endif

    /* Skip destruction of Python interpreter. */

    if (wsgi_server_config->destroy_interpreter == 0)
        return APR_SUCCESS;

    /* In a multithreaded MPM must protect table. */

#if APR_HAS_THREADS
    apr_thread_mutex_lock(wsgi_interp_lock);
#endif

    /*
     * We should be executing in the main thread again at this
     * point but without the GIL, so simply restore the original
     * thread state for that thread that we remembered when we
     * initialised the interpreter.
     */

    PyEval_AcquireThread(wsgi_main_tstate);

    /*
     * Extract a handle to the main Python interpreter from
     * interpreters dictionary as want to process that one last.
     */

    interp = PyDict_GetItemString(wsgi_interpreters, "");
    Py_INCREF(interp);

    /*
     * Remove all items from interpreters dictionary. This will
     * have side affect of calling any exit functions and
     * destroying interpreters we own.
     */

    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                 "mod_wsgi (pid=%d): Destroying interpreters.", getpid());

    PyDict_Clear(wsgi_interpreters);

#if APR_HAS_THREADS
    apr_thread_mutex_unlock(wsgi_interp_lock);
#endif

    /*
     * Now we decrement reference on handle for main Python
     * interpreter. This only causes exit functions to be called
     * and doesn't result in interpreter being destroyed as we
     * we didn't previously mark ourselves as the owner of the
     * interpreter. Note that when Python as a whole is later
     * being destroyed it will also call exit functions, but by
     * then the exit function registrations have been removed
     * and so they will not actually be run a second time.
     */

    Py_DECREF(interp);

    /*
     * The code which performs actual shutdown of the main
     * interpreter expects to be called without the GIL, so
     * we release it here again.
     */

    PyEval_ReleaseThread(wsgi_main_tstate);

    /*
     * Destroy Python itself including the main interpreter.
     * If mod_python is being loaded it is left to mod_python to
     * destroy Python, although it currently doesn't do so.
     */

    if (wsgi_python_initialized)
        wsgi_python_term();

    return APR_SUCCESS;
}

static void wsgi_python_child_init(apr_pool_t *p)
{
    PyGILState_STATE state;
    PyObject *object = NULL;

    int ignore_system_exit = 0;

    /* Working with Python, so must acquire GIL. */

    state = PyGILState_Ensure();

    /*
     * Trigger any special Python stuff required after a fork.
     * Only do this though if we were responsible for the
     * initialisation of the Python interpreter in the first
     * place to avoid it being done multiple times. Also only
     * do it if Python was initialised in parent process.
     */

#ifdef HAVE_FORK
    if (wsgi_python_initialized && !wsgi_python_after_fork) {
#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7)
        PyOS_AfterFork_Child();
#else
        PyOS_AfterFork();
#endif
    }
#endif

    /* Finalise any Python objects required by child process. */

    PyType_Ready(&Log_Type);
    PyType_Ready(&Stream_Type);
    PyType_Ready(&Input_Type);
    PyType_Ready(&Adapter_Type);
    PyType_Ready(&Restricted_Type);
    PyType_Ready(&Interpreter_Type);
    PyType_Ready(&Dispatch_Type);
    PyType_Ready(&Auth_Type);

    PyType_Ready(&SignalIntercept_Type);

#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 4)
    PyType_Ready(&ShutdownInterpreter_Type);
#endif

    /* Initialise Python interpreter instance table and lock. */

    wsgi_interpreters = PyDict_New();

#if APR_HAS_THREADS
    apr_thread_mutex_create(&wsgi_interp_lock, APR_THREAD_MUTEX_UNNESTED, p);
    apr_thread_mutex_create(&wsgi_module_lock, APR_THREAD_MUTEX_UNNESTED, p);
    apr_thread_mutex_create(&wsgi_shutdown_lock, APR_THREAD_MUTEX_UNNESTED, p);
#endif

    /*
     * Create an interpreters index using Apache data structure so
     * can iterate over interpreter names without needing Python GIL.
     */

    wsgi_interpreters_index = apr_hash_make(p);

    /*
     * Initialise the key for data related to a thread and force
     * creation of thread info.
     */

    apr_threadkey_private_create(&wsgi_thread_key, NULL, p);

    wsgi_thread_info(1, 0);

    /*
     * Cache a reference to the first Python interpreter
     * instance. This interpreter is special as some third party
     * Python modules will only work when used from within this
     * interpreter. This is generally when they use the Python
     * simplified GIL API or otherwise don't use threading API
     * properly. An empty string for name is used to identify
     * the first Python interpreter instance.
     */

    object = (PyObject *)newInterpreterObject(NULL);
    PyDict_SetItemString(wsgi_interpreters, "", object);
    Py_DECREF(object);

    apr_hash_set(wsgi_interpreters_index, "", APR_HASH_KEY_STRING, "");

    /* Restore the prior thread state and release the GIL. */

    PyGILState_Release(state);

    /* Register cleanups to performed on process shutdown. */

    apr_pool_cleanup_register(p, NULL, wsgi_python_child_cleanup,
                              apr_pool_cleanup_null);

    /* Loop through import scripts for this process and load them. */

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (wsgi_daemon_process && wsgi_daemon_process->group->threads == 0)
        ignore_system_exit = 1;
#endif

    if (wsgi_import_list) {
        apr_array_header_t *scripts = NULL;

        WSGIScriptFile *entries;
        WSGIScriptFile *entry;

        int i;

        scripts = wsgi_import_list;
        entries = (WSGIScriptFile *)scripts->elts;

        for (i = 0; i < scripts->nelts; ++i) {
            entry = &entries[i];

            /*
             * Stop loading scripts if this is a daemon process and
             * we have already been flagged to be shutdown.
             */

            if (wsgi_daemon_shutdown)
                break;

            if (!strcmp(wsgi_daemon_group, entry->process_group)) {
                InterpreterObject *interp = NULL;
                PyObject *modules = NULL;
                PyObject *module = NULL;
                char *name = NULL;
                int exists = 0;

                interp = wsgi_acquire_interpreter(entry->application_group);

                if (!interp) {
                    ap_log_error(APLOG_MARK, APLOG_CRIT, 0, wsgi_server,
                                  "mod_wsgi (pid=%d): Cannot acquire "
                                  "interpreter '%s'.", getpid(),
                                  entry->application_group);
                }

                /* Calculate the Python module name to be used for script. */

                name = wsgi_module_name(p, entry->handler_script);

                /*
                 * Use a lock around the check to see if the module is
                 * already loaded and the import of the module. Strictly
                 * speaking shouldn't be required at this point.
                 */

#if APR_HAS_THREADS
                Py_BEGIN_ALLOW_THREADS
                apr_thread_mutex_lock(wsgi_module_lock);
                Py_END_ALLOW_THREADS
#endif

                modules = PyImport_GetModuleDict();
                module = PyDict_GetItemString(modules, name);

                Py_XINCREF(module);

                if (module)
                    exists = 1;

                /*
                 * If script reloading is enabled and the module for it has
                 * previously been loaded, see if it has been modified since
                 * the last time it was accessed.
                 */

                if (module && wsgi_server_config->script_reloading) {
                    if (wsgi_reload_required(p, NULL, entry->handler_script,
                                             module, NULL)) {
                        /*
                         * Script file has changed. Only support module
                         * reloading for dispatch scripts. Remove the
                         * module from the modules dictionary before
                         * reloading it again. If code is executing within
                         * the module at the time, the callers reference
                         * count on the module should ensure it isn't
                         * actually destroyed until it is finished.
                         */

                        Py_DECREF(module);
                        module = NULL;

                        PyDict_DelItemString(modules, name);
                    }
                }

                if (!module) {
                    module = wsgi_load_source(p, NULL, name, exists,
                                              entry->handler_script,
                                              entry->process_group,
                                              entry->application_group,
                                              ignore_system_exit);

                    if (PyErr_Occurred()) 
                        PyErr_Clear(); 
                }

                /* Safe now to release the module lock. */

#if APR_HAS_THREADS
                apr_thread_mutex_unlock(wsgi_module_lock);
#endif

                /* Cleanup and release interpreter, */

                Py_XDECREF(module);

                wsgi_release_interpreter(interp);
            }
        }
    }
}

/* The processors for directives. */

static int wsgi_parse_option(apr_pool_t *p, const char **line,
                             const char **name, const char **value)
{
    const char *str = *line, *strend;

    while (*str && apr_isspace(*str))
        ++str;

    if (!*str || *str == '=') {
        *line = str;
        return !APR_SUCCESS;
    }

    /* Option must be of form name=value. Extract the name. */

    strend = str;
    while (*strend && *strend != '=' && !apr_isspace(*strend))
        ++strend;

    if (*strend != '=') {
        *line = str;
        return !APR_SUCCESS;
    }

    *name = apr_pstrndup(p, str, strend-str);

    *line = strend+1;

    /* Now extract the value. Note that value can be quoted. */

    *value = ap_getword_conf(p, line);

    return APR_SUCCESS;
}

static const char *wsgi_add_script_alias(cmd_parms *cmd, void *mconfig,
                                         const char *args)
{
    const char *l = NULL;
    const char *a = NULL;

    WSGIServerConfig *sconfig = NULL;
    WSGIAliasEntry *entry = NULL;

    const char *option = NULL;
    const char *value = NULL;

#if defined(MOD_WSGI_WITH_DAEMONS)
    const char *process_group = NULL;
#else
    const char *process_group = "";
#endif

    const char *application_group = NULL;
    const char *callable_object = NULL;

    int pass_authorization = -1;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (!sconfig->alias_list) {
        sconfig->alias_list = apr_array_make(sconfig->pool, 20,
                                            sizeof(WSGIAliasEntry));
    }

    l = ap_getword_conf(cmd->pool, &args);

    if (*l == '\0' || *args == 0) {
        return apr_pstrcat(cmd->pool, cmd->cmd->name,
                           " requires at least two arguments",
                           cmd->cmd->errmsg ? ", " : NULL,
                           cmd->cmd->errmsg, NULL);
    }

    a = ap_getword_conf(cmd->pool, &args);

    if (*a == '\0') {
        return apr_pstrcat(cmd->pool, cmd->cmd->name,
                           " requires at least two arguments",
                           cmd->cmd->errmsg ? ", " : NULL,
                           cmd->cmd->errmsg, NULL);
    }

    while (*args) {
        if (wsgi_parse_option(cmd->pool, &args, &option,
                              &value) != APR_SUCCESS) {
            return "Invalid option to WSGI script alias definition.";
        }

        if (!strcmp(option, "application-group")) {
            if (!*value)
                return "Invalid name for WSGI application group.";

            if (!strcmp(value, "%{GLOBAL}"))
                value = "";

            application_group = value;
        }
#if defined(MOD_WSGI_WITH_DAEMONS)
        else if (!strcmp(option, "process-group")) {
            if (!*value)
                return "Invalid name for WSGI process group.";

            if (!strcmp(value, "%{GLOBAL}"))
                value = "";

            process_group = value;
        }
#endif
        else if (!strcmp(option, "callable-object")) {
            if (!*value)
                return "Invalid name for WSGI callable object.";

            callable_object = value;
        }
        else if (!strcmp(option, "pass-authorization")) {
            if (!*value)
                return "Invalid value for authorization flag.";

            if (strcasecmp(value, "Off") == 0)
                pass_authorization = 0;
            else if (strcasecmp(value, "On") == 0)
                pass_authorization = 1;
            else
                return "Invalid value for authorization flag.";
        }
        else
            return "Invalid option to WSGI script alias definition.";
    }

    entry = (WSGIAliasEntry *)apr_array_push(sconfig->alias_list);

    if (cmd->info) {
        entry->regexp = ap_pregcomp(cmd->pool, l, AP_REG_EXTENDED);
        if (!entry->regexp)
            return "Regular expression could not be compiled.";
    }

    entry->location = l;
    entry->application = a;

    entry->process_group = process_group;
    entry->application_group = application_group;
    entry->callable_object = callable_object;
    entry->pass_authorization = pass_authorization;

    /*
     * Only add to import list if both process group and application
     * group are specified, that they don't include substitution values,
     * and in the case of WSGIScriptAliasMatch, that the WSGI script
     * target path doesn't include substitutions from URL pattern.
     */

    if (process_group && application_group &&
        !strstr(process_group, "%{") &&
        !strstr(application_group, "%{") &&
        (!cmd->info || !strstr(a, "$"))) {

        WSGIScriptFile *object = NULL;

        if (!wsgi_import_list) {
            wsgi_import_list = apr_array_make(cmd->pool, 20,
                                              sizeof(WSGIScriptFile));
            apr_pool_cleanup_register(cmd->pool, &wsgi_import_list,
                              ap_pool_cleanup_set_null,
                              apr_pool_cleanup_null);
        }

        object = (WSGIScriptFile *)apr_array_push(wsgi_import_list);

        object->handler_script = a;
        object->process_group = process_group;
        object->application_group = application_group;

#if defined(MOD_WSGI_WITH_DAEMONS)
        if (*object->process_group &&
            strcmp(object->process_group, "%{RESOURCE}") != 0 &&
            strcmp(object->process_group, "%{SERVER}") != 0 &&
            strcmp(object->process_group, "%{HOST}") != 0) {

            WSGIProcessGroup *group = NULL;
            WSGIProcessGroup *entries = NULL;
            WSGIProcessGroup *entry = NULL;
            int i;

            if (!wsgi_daemon_list)
                return "WSGI process group not yet configured.";

            entries = (WSGIProcessGroup *)wsgi_daemon_list->elts;

            for (i = 0; i < wsgi_daemon_list->nelts; ++i) {
                entry = &entries[i];

                if (!strcmp(entry->name, object->process_group)) {
                    group = entry;
                    break;
                }
            }

            if (!group)
                return "WSGI process group not yet configured.";

            if (cmd->server->server_hostname &&
                group->server->server_hostname &&
                strcmp(cmd->server->server_hostname,
                group->server->server_hostname) &&
                group->server->is_virtual) {

                return "WSGI process group not accessible.";
            }

            if (!cmd->server->server_hostname &&
                group->server->server_hostname &&
                group->server->is_virtual) {

                return "WSGI process group not matchable.";
            }

            if (cmd->server->server_hostname &&
                !group->server->server_hostname &&
                group->server->is_virtual) {

                return "WSGI process group not matchable.";
            }
        }
#endif
    }

    return NULL;
}

static const char *wsgi_set_verbose_debugging(cmd_parms *cmd, void *mconfig,
                                              const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->verbose_debugging = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->verbose_debugging = 1;
    else
        return "WSGIVerboseDebugging must be one of: Off | On";

    return NULL;
}

static const char *wsgi_set_lazy_initialization(cmd_parms *cmd, void *mconfig,
                                                const char *f)
{
    const char *error = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    if (strcasecmp(f, "Off") == 0)
        wsgi_python_after_fork = 0;
    else if (strcasecmp(f, "On") == 0)
        wsgi_python_after_fork = 1;
    else
        return "WSGILazyInitialization must be one of: Off | On";

    return NULL;
}

static const char *wsgi_add_python_warnings(cmd_parms *cmd, void *mconfig,
                                            const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    char **entry = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (!sconfig->python_warnings) {
        sconfig->python_warnings = apr_array_make(sconfig->pool, 5,
                                                  sizeof(char*));
    }

    entry = (char **)apr_array_push(sconfig->python_warnings);
    *entry = apr_pstrdup(sconfig->pool, f);

    return NULL;
}

#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6
static const char *wsgi_set_py3k_warning_flag(cmd_parms *cmd, void *mconfig,
                                              const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->py3k_warning_flag = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->py3k_warning_flag = 1;
    else
        return "WSGIPy3kWarningFlag must be one of: Off | On";

    return NULL;
}
#endif

#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 3) || \
    (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6)
static const char *wsgi_set_dont_write_bytecode(cmd_parms *cmd, void *mconfig,
                                                const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->dont_write_bytecode = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->dont_write_bytecode = 1;
    else
        return "WSGIDontWriteBytecode must be one of: Off | On";

    return NULL;
}
#endif

static const char *wsgi_set_python_optimize(cmd_parms *cmd, void *mconfig,
                                            const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);
    sconfig->python_optimize = atoi(f);

    return NULL;
}

static const char *wsgi_set_python_home(cmd_parms *cmd, void *mconfig,
                                        const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);
    sconfig->python_home = f;

    return NULL;
}

static const char *wsgi_set_python_path(cmd_parms *cmd, void *mconfig,
                                        const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);
    sconfig->python_path = f;

    return NULL;
}

static const char *wsgi_set_python_eggs(cmd_parms *cmd, void *mconfig,
                                        const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);
    sconfig->python_eggs = f;

    return NULL;
}

static const char *wsgi_set_python_hash_seed(cmd_parms *cmd, void *mconfig,
                                             const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    /*
     * Must check this here because if we don't and is wrong, then
     * Python interpreter will check later and may kill the process.
     */

    if (f && *f != '\0' && strcmp(f, "random") != 0) {
        const char *endptr = f;
        unsigned long seed;

        seed = PyOS_strtoul((char *)f, (char **)&endptr, 10);

        if (*endptr != '\0' || seed > 4294967295UL
                || (errno == ERANGE && seed == ULONG_MAX))
        {
            return "WSGIPythonHashSeed must be \"random\" or an integer "
                              "in range [0; 4294967295]";
        }
    }

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);
    sconfig->python_hash_seed = f;

    return NULL;
}

static const char *wsgi_set_destroy_interpreter(cmd_parms *cmd, void *mconfig,
                                                const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->destroy_interpreter = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->destroy_interpreter = 1;
    else
        return "WSGIDestroyInterpreter must be one of: Off | On";

    return NULL;
}

static const char *wsgi_set_restrict_embedded(cmd_parms *cmd, void *mconfig,
                                              const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->restrict_embedded = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->restrict_embedded = 1;
    else
        return "WSGIRestrictEmbedded must be one of: Off | On";

    if (sconfig->restrict_embedded) {
        if (wsgi_python_required == -1)
            wsgi_python_required = 0;
    }

    return NULL;
}

static const char *wsgi_set_restrict_stdin(cmd_parms *cmd, void *mconfig,
                                           const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->restrict_stdin = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->restrict_stdin = 1;
    else
        return "WSGIRestrictStdin must be one of: Off | On";

    return NULL;
}

static const char *wsgi_set_restrict_stdout(cmd_parms *cmd, void *mconfig,
                                            const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->restrict_stdout = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->restrict_stdout = 1;
    else
        return "WSGIRestrictStdout must be one of: Off | On";

    return NULL;
}

static const char *wsgi_set_restrict_signal(cmd_parms *cmd, void *mconfig,
                                            const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->restrict_signal = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->restrict_signal = 1;
    else
        return "WSGIRestrictSignal must be one of: Off | On";

    return NULL;
}

static const char *wsgi_set_case_sensitivity(cmd_parms *cmd, void *mconfig,
                                           const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->case_sensitivity = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->case_sensitivity = 1;
    else
        return "WSGICaseSensitivity must be one of: Off | On";

    return NULL;
}

static const char *wsgi_set_restrict_process(cmd_parms *cmd, void *mconfig,
                                             const char *args)
{
    apr_table_t *index = apr_table_make(cmd->pool, 5);

    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        dconfig->restrict_process = index;
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        sconfig->restrict_process = index;
    }

    while (*args) {
        const char *option;

        option = ap_getword_conf(cmd->pool, &args);

        if (!strcmp(option, "%{GLOBAL}"))
            option = "";

        apr_table_setn(index, option, option);
    }

    return NULL;
}

static const char *wsgi_set_process_group(cmd_parms *cmd, void *mconfig,
                                          const char *n)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;
        dconfig->process_group = n;
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);
        sconfig->process_group = n;
    }

    return NULL;
}

static const char *wsgi_set_application_group(cmd_parms *cmd, void *mconfig,
                                              const char *n)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;
        dconfig->application_group = n;
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);
        sconfig->application_group = n;
    }

    return NULL;
}

static const char *wsgi_set_callable_object(cmd_parms *cmd, void *mconfig,
                                            const char *n)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;
        dconfig->callable_object = n;
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);
        sconfig->callable_object = n;
    }

    return NULL;
}

static const char *wsgi_add_import_script(cmd_parms *cmd, void *mconfig,
                                          const char *args)
{
    WSGIScriptFile *object = NULL;

    const char *option = NULL;
    const char *value = NULL;

    if (!wsgi_import_list) {
        wsgi_import_list = apr_array_make(cmd->pool, 20,
                                          sizeof(WSGIScriptFile));
        apr_pool_cleanup_register(cmd->pool, &wsgi_import_list,
                              ap_pool_cleanup_set_null,
                              apr_pool_cleanup_null);
    }

    object = (WSGIScriptFile *)apr_array_push(wsgi_import_list);

    object->handler_script = ap_getword_conf(cmd->pool, &args);
    object->process_group = NULL;
    object->application_group = NULL;

    if (!object->handler_script || !*object->handler_script)
        return "Location of import script not supplied.";

    while (*args) {
        if (wsgi_parse_option(cmd->pool, &args, &option,
                              &value) != APR_SUCCESS) {
            return "Invalid option to WSGI import script definition.";
        }

        if (!strcmp(option, "application-group")) {
            if (!*value)
                return "Invalid name for WSGI application group.";

            object->application_group = value;
        }
#if defined(MOD_WSGI_WITH_DAEMONS)
        else if (!strcmp(option, "process-group")) {
            if (!*value)
                return "Invalid name for WSGI process group.";

            object->process_group = value;
        }
#endif
        else
            return "Invalid option to WSGI import script definition.";
    }

    if (!object->application_group)
        return "Name of WSGI application group required.";

    if (!strcmp(object->application_group, "%{GLOBAL}"))
        object->application_group = "";

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (!object->process_group)
        return "Name of WSGI process group required.";

    if (!strcmp(object->process_group, "%{GLOBAL}"))
        object->process_group = "";

    if (*object->process_group) {
        WSGIProcessGroup *group = NULL;
        WSGIProcessGroup *entries = NULL;
        WSGIProcessGroup *entry = NULL;
        int i;

        if (!wsgi_daemon_list)
            return "WSGI process group not yet configured.";

        entries = (WSGIProcessGroup *)wsgi_daemon_list->elts;

        for (i = 0; i < wsgi_daemon_list->nelts; ++i) {
            entry = &entries[i];

            if (!strcmp(entry->name, object->process_group)) {
                group = entry;
                break;
            }
        }

        if (!group)
            return "WSGI process group not yet configured.";

        if (cmd->server->server_hostname &&
            group->server->server_hostname &&
            strcmp(cmd->server->server_hostname,
            group->server->server_hostname) &&
            group->server->is_virtual) {

            return "WSGI process group not accessible.";
        }

        if (!cmd->server->server_hostname &&
            group->server->server_hostname &&
            group->server->is_virtual) {

            return "WSGI process group not matchable.";
        }

        if (cmd->server->server_hostname &&
            !group->server->server_hostname &&
            group->server->is_virtual) {

            return "WSGI process group not matchable.";
        }
    }
#else
    object->process_group = "";
#endif

    if (!*object->process_group)
        wsgi_python_required = 1;

    return NULL;
}

static const char *wsgi_set_dispatch_script(cmd_parms *cmd, void *mconfig,
                                            const char *args)
{
    WSGIScriptFile *object = NULL;

    const char *option = NULL;
    const char *value = NULL;

    object = newWSGIScriptFile(cmd->pool);

    object->handler_script = ap_getword_conf(cmd->pool, &args);

    if (!object->handler_script || !*object->handler_script)
        return "Location of dispatch script not supplied.";

    while (*args) {
        if (wsgi_parse_option(cmd->pool, &args, &option,
                              &value) != APR_SUCCESS) {
            return "Invalid option to WSGI dispatch script definition.";
        }

        if (!strcmp(option, "application-group")) {
            if (!*value)
                return "Invalid name for WSGI application group.";

            object->application_group = value;
        }
        else
            return "Invalid option to WSGI dispatch script definition.";
    }

    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;
        dconfig->dispatch_script = object;
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);
        sconfig->dispatch_script = object;
    }

    wsgi_python_required = 1;

    return NULL;
}

static const char *wsgi_set_pass_apache_request(cmd_parms *cmd, void *mconfig,
                                                const char *f)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (strcasecmp(f, "Off") == 0)
            dconfig->pass_apache_request = 0;
        else if (strcasecmp(f, "On") == 0)
            dconfig->pass_apache_request = 1;
        else
            return "WSGIPassApacheRequest must be one of: Off | On";
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (strcasecmp(f, "Off") == 0)
            sconfig->pass_apache_request = 0;
        else if (strcasecmp(f, "On") == 0)
            sconfig->pass_apache_request = 1;
        else
            return "WSGIPassApacheRequest must be one of: Off | On";
    }

    return NULL;
}

static const char *wsgi_set_pass_authorization(cmd_parms *cmd, void *mconfig,
                                               const char *f)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (strcasecmp(f, "Off") == 0)
            dconfig->pass_authorization = 0;
        else if (strcasecmp(f, "On") == 0)
            dconfig->pass_authorization = 1;
        else
            return "WSGIPassAuthorization must be one of: Off | On";
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (strcasecmp(f, "Off") == 0)
            sconfig->pass_authorization = 0;
        else if (strcasecmp(f, "On") == 0)
            sconfig->pass_authorization = 1;
        else
            return "WSGIPassAuthorization must be one of: Off | On";
    }

    return NULL;
}

static const char *wsgi_set_script_reloading(cmd_parms *cmd, void *mconfig,
                                             const char *f)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (strcasecmp(f, "Off") == 0)
            dconfig->script_reloading = 0;
        else if (strcasecmp(f, "On") == 0)
            dconfig->script_reloading = 1;
        else
            return "WSGIScriptReloading must be one of: Off | On";
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (strcasecmp(f, "Off") == 0)
            sconfig->script_reloading = 0;
        else if (strcasecmp(f, "On") == 0)
            sconfig->script_reloading = 1;
        else
            return "WSGIScriptReloading must be one of: Off | On";
    }

    return NULL;
}

static const char *wsgi_set_error_override(cmd_parms *cmd, void *mconfig,
                                           const char *f)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (strcasecmp(f, "Off") == 0)
            dconfig->error_override = 0;
        else if (strcasecmp(f, "On") == 0)
            dconfig->error_override = 1;
        else
            return "WSGIErrorOverride must be one of: Off | On";
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (strcasecmp(f, "Off") == 0)
            sconfig->error_override = 0;
        else if (strcasecmp(f, "On") == 0)
            sconfig->error_override = 1;
        else
            return "WSGIErrorOverride must be one of: Off | On";
    }

    return NULL;
}

static const char *wsgi_set_chunked_request(cmd_parms *cmd, void *mconfig,
                                            const char *f)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (strcasecmp(f, "Off") == 0)
            dconfig->chunked_request = 0;
        else if (strcasecmp(f, "On") == 0)
            dconfig->chunked_request = 1;
        else
            return "WSGIChunkedRequest must be one of: Off | On";
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (strcasecmp(f, "Off") == 0)
            sconfig->chunked_request = 0;
        else if (strcasecmp(f, "On") == 0)
            sconfig->chunked_request = 1;
        else
            return "WSGIChunkedRequest must be one of: Off | On";
    }

    return NULL;
}

static const char *wsgi_set_map_head_to_get(cmd_parms *cmd, void *mconfig,
                                            const char *f)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (strcasecmp(f, "Off") == 0)
            dconfig->map_head_to_get = 0;
        else if (strcasecmp(f, "On") == 0)
            dconfig->map_head_to_get = 1;
        else if (strcasecmp(f, "Auto") == 0)
            dconfig->map_head_to_get = 2;
        else
            return "WSGIMapHEADToGET must be one of: Off | On | Auto";
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (strcasecmp(f, "Off") == 0)
            sconfig->map_head_to_get = 0;
        else if (strcasecmp(f, "On") == 0)
            sconfig->map_head_to_get = 1;
        else if (strcasecmp(f, "Auto") == 0)
            sconfig->map_head_to_get = 2;
        else
            return "WSGIMapHEADToGET must be one of: Off | On | Auto";
    }

    return NULL;
}

static const char *wsgi_set_ignore_activity(cmd_parms *cmd, void *mconfig,
                                            const char *f)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (strcasecmp(f, "Off") == 0)
            dconfig->ignore_activity = 0;
        else if (strcasecmp(f, "On") == 0)
            dconfig->ignore_activity = 1;
        else
            return "WSGIIgnoreActivity must be one of: Off | On";
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (strcasecmp(f, "Off") == 0)
            sconfig->ignore_activity = 0;
        else if (strcasecmp(f, "On") == 0)
            sconfig->ignore_activity = 1;
        else
            return "WSGIIgnoreActivity must be one of: Off | On";
    }

    return NULL;
}

static char *wsgi_http2env(apr_pool_t *a, const char *w);

static const char *wsgi_set_trusted_proxy_headers(cmd_parms *cmd,
                                                  void *mconfig,
                                                  const char *args)
{
    apr_array_header_t *headers = NULL;

    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (!dconfig->trusted_proxy_headers) {
            headers = apr_array_make(cmd->pool, 3, sizeof(char*));
            dconfig->trusted_proxy_headers = headers;
        }
        else
            headers = dconfig->trusted_proxy_headers;
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (!sconfig->trusted_proxy_headers) {
            headers = apr_array_make(cmd->pool, 3, sizeof(char*));
            sconfig->trusted_proxy_headers = headers;
        }
        else
            headers = sconfig->trusted_proxy_headers;
    }

    while (*args) {
        const char **entry = NULL;

        entry = (const char **)apr_array_push(headers);
        *entry = wsgi_http2env(cmd->pool, ap_getword_conf(cmd->pool, &args));
    }

    return NULL;
}

static int wsgi_looks_like_ip(const char *ip) {
    static const char ipv4_set[] = "0123456789./";
    static const char ipv6_set[] = "0123456789abcdef:/";

    const char *ptr;

    /* Zero length value is not valid. */

    if (!*ip)
      return 0;

    /* Determine if this could be a IPv6 or IPv4 address. */

    ptr = ip;

    if (strchr(ip, ':')) {
        while(*ptr && strchr(ipv6_set, *ptr) != NULL)
            ++ptr;
    }
    else {
        while(*ptr && strchr(ipv4_set, *ptr) != NULL)
            ++ptr;
    }

    return (*ptr == '\0');
}

static const char *wsgi_set_trusted_proxies(cmd_parms *cmd,
                                              void *mconfig, const char *args)
{
    apr_array_header_t *proxy_ips = NULL;

    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (!dconfig->trusted_proxies) {
            proxy_ips = apr_array_make(cmd->pool, 3, sizeof(char*));
            dconfig->trusted_proxies = proxy_ips;
        }
        else
            proxy_ips = dconfig->trusted_proxies;
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (!sconfig->trusted_proxies) {
            proxy_ips = apr_array_make(cmd->pool, 3, sizeof(char*));
            sconfig->trusted_proxies = proxy_ips;
        }
        else
            proxy_ips = sconfig->trusted_proxies;
    }

    while (*args) {
        const char *proxy_ip;

        proxy_ip = ap_getword_conf(cmd->pool, &args);

        if (wsgi_looks_like_ip(proxy_ip)) {
            char *ip;
            char *mask;
            apr_ipsubnet_t **sub;
            apr_status_t rv;

            ip = apr_pstrdup(cmd->temp_pool, proxy_ip);

            if ((mask = ap_strchr(ip, '/')))
                *mask++ = '\0';

            sub = (apr_ipsubnet_t **)apr_array_push(proxy_ips);

            rv = apr_ipsubnet_create(sub, ip, mask, cmd->pool);

            if (rv != APR_SUCCESS) {
                char msgbuf[128];
                apr_strerror(rv, msgbuf, sizeof(msgbuf));

                return apr_pstrcat(cmd->pool, "Unable to parse trusted "
                                   "proxy IP address/subnet of \"", proxy_ip,
                                   "\". ", msgbuf, NULL);
            }
        }
        else {
            return apr_pstrcat(cmd->pool, "Unable to parse trusted proxy "
                               "IP address/subnet of \"", proxy_ip, "\".",
                               NULL);
        }
    }

    return NULL;
}

static const char *wsgi_set_enable_sendfile(cmd_parms *cmd, void *mconfig,
                                            const char *f)
{
    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (strcasecmp(f, "Off") == 0)
            dconfig->enable_sendfile = 0;
        else if (strcasecmp(f, "On") == 0)
            dconfig->enable_sendfile = 1;
        else
            return "WSGIEnableSendfile must be one of: Off | On";
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (strcasecmp(f, "Off") == 0)
            sconfig->enable_sendfile = 0;
        else if (strcasecmp(f, "On") == 0)
            sconfig->enable_sendfile = 1;
        else
            return "WSGIEnableSendfile must be one of: Off | On";
    }

    return NULL;
}

static const char *wsgi_set_access_script(cmd_parms *cmd, void *mconfig,
                                          const char *args)
{
    WSGIDirectoryConfig *dconfig = NULL;
    WSGIScriptFile *object = NULL;

    const char *option = NULL;
    const char *value = NULL;

    object = newWSGIScriptFile(cmd->pool);

    object->handler_script = ap_getword_conf(cmd->pool, &args);

    if (!object->handler_script || !*object->handler_script)
        return "Location of access script not supplied.";

    while (*args) {
        if (wsgi_parse_option(cmd->pool, &args, &option,
                              &value) != APR_SUCCESS) {
            return "Invalid option to WSGI access script definition.";
        }

        if (!strcmp(option, "application-group")) {
            if (!*value)
                return "Invalid name for WSGI application group.";

            object->application_group = value;
        }
        else
            return "Invalid option to WSGI access script definition.";
    }

    dconfig = (WSGIDirectoryConfig *)mconfig;
    dconfig->access_script = object;

    wsgi_python_required = 1;

    return NULL;
}

static const char *wsgi_set_auth_user_script(cmd_parms *cmd, void *mconfig,
                                             const char *args)
{
    WSGIDirectoryConfig *dconfig = NULL;
    WSGIScriptFile *object = NULL;

    const char *option = NULL;
    const char *value = NULL;

    object = newWSGIScriptFile(cmd->pool);

    object->handler_script = ap_getword_conf(cmd->pool, &args);

    if (!object->handler_script || !*object->handler_script)
        return "Location of auth user script not supplied.";

    while (*args) {
        if (wsgi_parse_option(cmd->pool, &args, &option,
                              &value) != APR_SUCCESS) {
            return "Invalid option to WSGI auth user script definition.";
        }

        if (!strcmp(option, "application-group")) {
            if (!*value)
                return "Invalid name for WSGI application group.";

            object->application_group = value;
        }
        else
            return "Invalid option to WSGI auth user script definition.";
    }

    dconfig = (WSGIDirectoryConfig *)mconfig;
    dconfig->auth_user_script = object;

    wsgi_python_required = 1;

    return NULL;
}

static const char *wsgi_set_auth_group_script(cmd_parms *cmd, void *mconfig,
                                               const char *args)
{
    WSGIDirectoryConfig *dconfig = NULL;
    WSGIScriptFile *object = NULL;

    const char *option = NULL;
    const char *value = NULL;

    object = newWSGIScriptFile(cmd->pool);

    object->handler_script = ap_getword_conf(cmd->pool, &args);

    if (!object->handler_script || !*object->handler_script)
        return "Location of auth group script not supplied.";

    while (*args) {
        if (wsgi_parse_option(cmd->pool, &args, &option,
                              &value) != APR_SUCCESS) {
            return "Invalid option to WSGI auth group script definition.";
        }

        if (!strcmp(option, "application-group")) {
            if (!*value)
                return "Invalid name for WSGI application group.";

            object->application_group = value;
        }
        else
            return "Invalid option to WSGI auth group script definition.";
    }

    dconfig = (WSGIDirectoryConfig *)mconfig;
    dconfig->auth_group_script = object;

    wsgi_python_required = 1;

    return NULL;
}

#if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER)
static const char *wsgi_set_user_authoritative(cmd_parms *cmd, void *mconfig,
                                               const char *f)
{
    WSGIDirectoryConfig *dconfig = NULL;
    dconfig = (WSGIDirectoryConfig *)mconfig;

    if (strcasecmp(f, "Off") == 0)
        dconfig->user_authoritative = 0;
    else if (strcasecmp(f, "On") == 0)
        dconfig->user_authoritative = 1;
    else
        return "WSGIUserAuthoritative must be one of: Off | On";

    return NULL;
}
#endif

static const char *wsgi_set_group_authoritative(cmd_parms *cmd, void *mconfig,
                                                const char *f)
{
    WSGIDirectoryConfig *dconfig = NULL;
    dconfig = (WSGIDirectoryConfig *)mconfig;

    if (strcasecmp(f, "Off") == 0)
        dconfig->group_authoritative = 0;
    else if (strcasecmp(f, "On") == 0)
        dconfig->group_authoritative = 1;
    else
        return "WSGIGroupAuthoritative must be one of: Off | On";

    return NULL;
}

static const char *wsgi_add_handler_script(cmd_parms *cmd, void *mconfig,
                                           const char *args)
{
    WSGIScriptFile *object = NULL;

    const char *name = NULL;
    const char *option = NULL;
    const char *value = NULL;

    name = ap_getword_conf(cmd->pool, &args);

    if (!name || !*name)
        return "Name for handler script not supplied.";

    object = newWSGIScriptFile(cmd->pool);

    object->handler_script = ap_getword_conf(cmd->pool, &args);

    if (!object->handler_script || !*object->handler_script)
        return "Location of handler script not supplied.";

    while (*args) {
        if (wsgi_parse_option(cmd->pool, &args, &option,
                              &value) != APR_SUCCESS) {
            return "Invalid option to WSGI handler script definition.";
        }

        if (!strcmp(option, "process-group")) {
            if (!*value)
                return "Invalid name for WSGI process group.";

            object->process_group = value;
        }
        else if (!strcmp(option, "application-group")) {
            if (!*value)
                return "Invalid name for WSGI application group.";

            object->application_group = value;
        }
        else if (!strcmp(option, "pass-authorization")) {
            if (!*value)
                return "Invalid value for authorization flag.";

            if (strcasecmp(value, "Off") == 0)
                object->pass_authorization = "0";
            else if (strcasecmp(value, "On") == 0)
                object->pass_authorization = "1";
            else
                return "Invalid value for authorization flag.";
        }
        else
            return "Invalid option to WSGI handler script definition.";
    }

    if (cmd->path) {
        WSGIDirectoryConfig *dconfig = NULL;
        dconfig = (WSGIDirectoryConfig *)mconfig;

        if (!dconfig->handler_scripts)
            dconfig->handler_scripts = apr_hash_make(cmd->pool);

        apr_hash_set(dconfig->handler_scripts, name, APR_HASH_KEY_STRING,
                     object);
    }
    else {
        WSGIServerConfig *sconfig = NULL;
        sconfig = ap_get_module_config(cmd->server->module_config,
                                       &wsgi_module);

        if (!sconfig->handler_scripts)
            sconfig->handler_scripts = apr_hash_make(cmd->pool);

        apr_hash_set(sconfig->handler_scripts, name, APR_HASH_KEY_STRING,
                     object);
    }

    return NULL;
}

static const char *wsgi_set_server_metrics(cmd_parms *cmd, void *mconfig,
                                           const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->server_metrics = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->server_metrics = 1;
    else
        return "WSGIServerMetrics must be one of: Off | On";

    return NULL;
}

static const char *wsgi_set_newrelic_config_file(
        cmd_parms *cmd, void *mconfig, const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);
    sconfig->newrelic_config_file = f;

    return NULL;
}

static const char *wsgi_set_newrelic_environment(
        cmd_parms *cmd, void *mconfig, const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);
    sconfig->newrelic_environment = f;

    return NULL;
}

/* Handler for the translate name phase. */

static long wsgi_alias_matches(const char *uri, const char *alias_fakename)
{
    /* Code for this function from Apache mod_alias module. */

    const char *aliasp = alias_fakename, *urip = uri;

    while (*aliasp) {
        if (*aliasp == '/') {
            /* any number of '/' in the alias matches any number in
             * the supplied URI, but there must be at least one...
             */
            if (*urip != '/')
                return 0;

            do {
                ++aliasp;
            } while (*aliasp == '/');
            do {
                ++urip;
            } while (*urip == '/');
        }
        else {
            /* Other characters are compared literally */
            if (*urip++ != *aliasp++)
                return 0;
        }
    }

    /* Check last alias path component matched all the way */

    if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/')
        return 0;

    /* Return number of characters from URI which matched (may be
     * greater than length of alias, since we may have matched
     * doubled slashes)
     */

    return urip - uri;
}

static int wsgi_hook_intercept(request_rec *r)
{
    WSGIServerConfig *config = NULL;

    apr_array_header_t *aliases = NULL;

    WSGIAliasEntry *entries = NULL;
    WSGIAliasEntry *entry = NULL;

    ap_regmatch_t matches[AP_MAX_REG_MATCH];

    const char *location = NULL;
    const char *application = NULL;

    int i = 0;

    config = ap_get_module_config(r->server->module_config, &wsgi_module);

    if (!config->alias_list)
        return DECLINED;

    if (r->uri[0] != '/' && r->uri[0])
        return DECLINED;

    aliases = config->alias_list;
    entries = (WSGIAliasEntry *)aliases->elts;

    for (i = 0; i < aliases->nelts; ++i) {
        long l = 0;

        entry = &entries[i];

        if (entry->regexp) {
            if (!ap_regexec(entry->regexp, r->uri, AP_MAX_REG_MATCH,
                matches, 0)) {
                if (entry->application) {
                    l = matches[0].rm_eo;

                    location = apr_pstrndup(r->pool, r->uri, l);
                    application = ap_pregsub(r->pool, entry->application,
                                             r->uri, AP_MAX_REG_MATCH,
                                             matches);
                }
            }
        }
        else if (entry->location) {
            l = wsgi_alias_matches(r->uri, entry->location);

            location = entry->location;
            application = entry->application;
        }

        if (l > 0) {
            if (!strcmp(location, "/")) {
                r->filename = apr_pstrcat(r->pool, application,
                                          r->uri, NULL);
            }
            else {
                r->filename = apr_pstrcat(r->pool, application,
                                          r->uri + l, NULL);
            }

            r->handler = "wsgi-script";
            apr_table_setn(r->notes, "alias-forced-type", r->handler);

            if (entry->process_group) {
                apr_table_setn(r->notes, "mod_wsgi.process_group",
                               entry->process_group);
            }
            if (entry->application_group) {
                apr_table_setn(r->notes, "mod_wsgi.application_group",
                               entry->application_group);
            }
            if (entry->callable_object) {
                apr_table_setn(r->notes, "mod_wsgi.callable_object",
                               entry->callable_object);
            }

            if (entry->pass_authorization == 0)
                apr_table_setn(r->notes, "mod_wsgi.pass_authorization", "0");
            else if (entry->pass_authorization == 1)
                apr_table_setn(r->notes, "mod_wsgi.pass_authorization", "1");

            return OK;
        }
    }

    return DECLINED;
}

/* Handler for the response handler phase. */

static void wsgi_drop_invalid_headers(request_rec *r);
static void wsgi_process_proxy_headers(request_rec *r);

static void wsgi_build_environment(request_rec *r)
{
    WSGIRequestConfig *config = NULL;

    const char *value = NULL;
    const char *script_name = NULL;
    const char *path_info = NULL;

    conn_rec *c = r->connection;

    /* Grab request configuration. */

    config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
                                                       &wsgi_module);

    /*
     * Remove any invalid headers which use invalid characters.
     * This is necessary to ensure that someone doesn't try and
     * take advantage of header spoofing. This can come about
     * where characters other than alphanumerics or '-' are used
     * as the conversion of non alphanumerics to '_' means one
     * can get collisions. This is technically only an issue
     * with Apache 2.2 as Apache 2.4 addresses the problem and
     * drops them anyway. Still go through and drop them even
     * for Apache 2.4 as not sure which version of Apache 2.4
     * introduces the change.
     */

    wsgi_drop_invalid_headers(r);

    /* Populate environment with standard CGI variables. */

    ap_add_cgi_vars(r);
    ap_add_common_vars(r);

    /*
     * Mutate a HEAD request into a GET request. This is
     * required because WSGI specification doesn't lay out
     * clearly how WSGI applications should treat a HEAD
     * request. Generally authors of WSGI applications or
     * frameworks take it that they do not need to return any
     * content, but this screws up any Apache output filters
     * which need to see all the response content in order to
     * correctly set up response headers for a HEAD request such
     * that they are the same as a GET request. Thus change a
     * HEAD request into a GET request to ensure that request
     * content is generated. If using Apache 2.X we can skip
     * doing this if we know there is no output filter that
     * might change the content and/or headers.
     *
     * The default behaviour here of changing it if an output
     * filter is detected can be overridden using the directive
     * WSGIMapHEADToGet. The default value is 'Auto'. If set to
     * 'On' then it remapped regardless of whether an output
     * filter is present. If 'Off' then it will be left alone
     * and the original value used.
     */

    if (config->map_head_to_get == 2) {
        if (r->method_number == M_GET && r->header_only &&
            r->output_filters->frec->ftype < AP_FTYPE_PROTOCOL)
            apr_table_setn(r->subprocess_env, "REQUEST_METHOD", "GET");
    }
    else if (config->map_head_to_get == 1) {
        if (r->method_number == M_GET)
            apr_table_setn(r->subprocess_env, "REQUEST_METHOD", "GET");
    }

    /*
     * If enabled, pass along authorisation headers which Apache
     * leaves out of CGI environment. WSGI still needs to see
     * these if it needs to implement any of the standard
     * authentication schemes such as Basic and Digest. We do
     * not pass these through by default though as it can result
     * in passwords being leaked though to a WSGI application
     * when it shouldn't. This would be a problem where there is
     * some sort of site wide authorisation scheme in place
     * which has got nothing to do with specific applications.
     */

    if (config->pass_authorization) {
        value = apr_table_get(r->headers_in, "Authorization");
        if (value)
            apr_table_setn(r->subprocess_env, "HTTP_AUTHORIZATION", value);
    }

    /* If PATH_INFO not set, set it to an empty string. */

    value = apr_table_get(r->subprocess_env, "PATH_INFO");
    if (!value)
        apr_table_setn(r->subprocess_env, "PATH_INFO", "");

    /*
     * Multiple slashes are not always collapsed into a single
     * slash in SCRIPT_NAME and PATH_INFO with Apache 1.3 and
     * Apache 2.X behaving a bit differently. Because some WSGI
     * applications don't deal with multiple slashes properly we
     * collapse any duplicate slashes to a single slash so
     * Apache behaviour is consistent across all versions. We
     * don't care that PATH_TRANSLATED can on Apache 1.3 still
     * contain multiple slashes as that should not be getting
     * used from a WSGI application anyway.
     */

    script_name = apr_table_get(r->subprocess_env, "SCRIPT_NAME");

    if (*script_name == '/') {
        while (*script_name && (*(script_name+1) == '/'))
            script_name++;
        script_name = apr_pstrdup(r->pool, script_name);
        ap_no2slash((char*)script_name);
        apr_table_setn(r->subprocess_env, "SCRIPT_NAME", script_name);
    }

    path_info = apr_table_get(r->subprocess_env, "PATH_INFO");

    if (*path_info == '/') {
        while (*path_info && (*(path_info+1) == '/'))
            path_info++;
        path_info = apr_pstrdup(r->pool, path_info);
        ap_no2slash((char*)path_info);
        apr_table_setn(r->subprocess_env, "PATH_INFO", path_info);
    }

    /*
     * Save away the SCRIPT_NAME and PATH_INFO values at this point
     * so we have a way of determining if they are rewritten somehow.
     * This can be important when dealing with rewrite rules and
     * a trusted header was being handled for SCRIPT_NAME.
     */

    apr_table_setn(r->subprocess_env, "mod_wsgi.script_name", script_name);
    apr_table_setn(r->subprocess_env, "mod_wsgi.path_info", path_info);

    /*
     * Perform fixups on environment based on trusted proxy headers
     * sent through from a front end proxy.
     */

    wsgi_process_proxy_headers(r);

    /*
     * Determine whether connection uses HTTPS protocol. This has
     * to be done after and fixups due to trusted proxy headers.
     */

    if (!wsgi_is_https)
        wsgi_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);

    if (wsgi_is_https && wsgi_is_https(r->connection))
        apr_table_set(r->subprocess_env, "HTTPS", "1");

    /*
     * Set values specific to mod_wsgi configuration. These control
     * aspects of how a request is managed but don't strictly need
     * to be passed through to the application itself. It is though
     * easier to set them here as then they are carried across to
     * the daemon process as part of the environment where they can
     * be extracted and used.
     */

    apr_table_setn(r->subprocess_env, "mod_wsgi.process_group",
                   config->process_group);
    apr_table_setn(r->subprocess_env, "mod_wsgi.application_group",
                   config->application_group);
    apr_table_setn(r->subprocess_env, "mod_wsgi.callable_object",
                   config->callable_object);

    apr_table_setn(r->subprocess_env, "mod_wsgi.request_handler", r->handler);
    apr_table_setn(r->subprocess_env, "mod_wsgi.handler_script",
                   config->handler_script);

    apr_table_setn(r->subprocess_env, "mod_wsgi.script_reloading",
                   apr_psprintf(r->pool, "%d", config->script_reloading));

#if defined(MOD_WSGI_WITH_DAEMONS)
    apr_table_setn(r->subprocess_env, "mod_wsgi.listener_host",
                   c->local_addr->hostname ? c->local_addr->hostname : "");
    apr_table_setn(r->subprocess_env, "mod_wsgi.listener_port",
                   apr_psprintf(r->pool, "%d", c->local_addr->port));
#endif

    apr_table_setn(r->subprocess_env, "mod_wsgi.enable_sendfile",
                   apr_psprintf(r->pool, "%d", config->enable_sendfile));
    apr_table_setn(r->subprocess_env, "mod_wsgi.ignore_activity",
                   apr_psprintf(r->pool, "%d", config->ignore_activity));

    apr_table_setn(r->subprocess_env, "mod_wsgi.request_start",
                   apr_psprintf(r->pool, "%" APR_TIME_T_FMT, r->request_time));

#if AP_MODULE_MAGIC_AT_LEAST(20100923,2)
    if (!r->log_id) {
        const char **id;

        /* Need to cast const away. */

        id = &((request_rec *)r)->log_id;

        ap_run_generate_log_id(c, r, id);
    }

    if (r->log_id)
        apr_table_setn(r->subprocess_env, "mod_wsgi.request_id", r->log_id);
    if (r->connection->log_id)
        apr_table_setn(r->subprocess_env, "mod_wsgi.connection_id",
                       r->connection->log_id);
#endif
}

typedef struct {
        PyObject_HEAD
        request_rec *r;
        WSGIRequestConfig *config;
        PyObject *log;
} DispatchObject;

static DispatchObject *newDispatchObject(request_rec *r,
                                         WSGIRequestConfig *config)
{
    DispatchObject *self;

    self = PyObject_New(DispatchObject, &Dispatch_Type);
    if (self == NULL)
        return NULL;

    self->config = config;

    self->r = r;

    self->log = newLogObject(r, APLOG_ERR, NULL, 0);

    return self;
}

static void Dispatch_dealloc(DispatchObject *self)
{
    Py_DECREF(self->log);

    PyObject_Del(self);
}

static PyObject *Dispatch_environ(DispatchObject *self, const char *group)
{
    request_rec *r = NULL;

    PyObject *vars = NULL;
    PyObject *object = NULL;

    const apr_array_header_t *head = NULL;
    const apr_table_entry_t *elts = NULL;

    int i = 0;

    /* Create the WSGI environment dictionary. */

    vars = PyDict_New();

    /* Merge the CGI environment into the WSGI environment. */

    r = self->r;

    head = apr_table_elts(r->subprocess_env);
    elts = (apr_table_entry_t *)head->elts;

    for (i = 0; i < head->nelts; ++i) {
        if (elts[i].key) {
            if (elts[i].val) {
#if PY_MAJOR_VERSION >= 3
                object = PyUnicode_DecodeLatin1(elts[i].val,
                                                strlen(elts[i].val), NULL);
#else
                object = PyString_FromString(elts[i].val);
#endif
                PyDict_SetItemString(vars, elts[i].key, object);
                Py_DECREF(object);
            }
            else
                PyDict_SetItemString(vars, elts[i].key, Py_None);
        }
    }

    /*
     * Need to override process and application group as they
     * are set to the default target, where as we need to set
     * them to context dispatch script is run in. Also need
     * to remove callable object reference.
     */

#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_FromString("");
#else
    object = PyString_FromString("");
#endif
    PyDict_SetItemString(vars, "mod_wsgi.process_group", object);
    Py_DECREF(object);

#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(group, strlen(group), NULL);
#else
    object = PyString_FromString(group);
#endif
    PyDict_SetItemString(vars, "mod_wsgi.application_group", object);
    Py_DECREF(object);

    PyDict_DelItemString(vars, "mod_wsgi.callable_object");

    /*
     * Setup log object for WSGI errors. Don't decrement
     * reference to log object as keep reference to it.
     */

    object = (PyObject *)self->log;
    PyDict_SetItemString(vars, "wsgi.errors", object);

    /*
     * If Apache extensions are enabled add a CObject reference
     * to the Apache request_rec structure instance.
     */

    if (!wsgi_daemon_pool && self->config->pass_apache_request) {
#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2) || \
    (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7)
        object = PyCapsule_New(self->r, 0, 0);
#else
        object = PyCObject_FromVoidPtr(self->r, 0);
#endif
        PyDict_SetItemString(vars, "apache.request_rec", object);
        Py_DECREF(object);
    }

    /*
     * Extensions for accessing SSL certificate information from
     * mod_ssl when in use.
     */

#if 0
    object = PyObject_GetAttrString((PyObject *)self, "ssl_is_https");
    PyDict_SetItemString(vars, "mod_ssl.is_https", object);
    Py_DECREF(object);

    object = PyObject_GetAttrString((PyObject *)self, "ssl_var_lookup");
    PyDict_SetItemString(vars, "mod_ssl.var_lookup", object);
    Py_DECREF(object);
#endif

    return vars;
}

static PyObject *Dispatch_ssl_is_https(DispatchObject *self, PyObject *args)
{
    APR_OPTIONAL_FN_TYPE(ssl_is_https) *ssl_is_https = 0;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    if (!PyArg_ParseTuple(args, ":ssl_is_https"))
        return NULL;

    ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);

    if (ssl_is_https == 0)
      return Py_BuildValue("i", 0);

    return Py_BuildValue("i", ssl_is_https(self->r->connection));
}

static PyObject *Dispatch_ssl_var_lookup(DispatchObject *self, PyObject *args)
{
    APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *ssl_var_lookup = 0;

    PyObject *item = NULL;

    char *name = 0;
    char *value = 0;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    if (!PyArg_ParseTuple(args, "O:ssl_var_lookup", &item))
        return NULL;

#if PY_MAJOR_VERSION >= 3
    if (PyUnicode_Check(item)) {
        PyObject *latin_item;
        latin_item = PyUnicode_AsLatin1String(item);
        if (!latin_item) {
            PyErr_Format(PyExc_TypeError, "byte string value expected, "
                         "value containing non 'latin-1' characters found");
            Py_DECREF(item);
            return NULL;
        }

        Py_DECREF(item);
        item = latin_item;
    }
#endif

    if (!PyString_Check(item)) {
        PyErr_Format(PyExc_TypeError, "byte string value expected, value "
                     "of type %.200s found", item->ob_type->tp_name);
        Py_DECREF(item);
        return NULL;
    }

    name = PyString_AsString(item);

    ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);

    if (ssl_var_lookup == 0)
    {
        Py_INCREF(Py_None);

        return Py_None;
    }

    value = ssl_var_lookup(self->r->pool, self->r->server,
                           self->r->connection, self->r, name);

    if (!value) {
        Py_INCREF(Py_None);

        return Py_None;
    }

#if PY_MAJOR_VERSION >= 3
    return PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    return PyString_FromString(value);
#endif
}

static PyMethodDef Dispatch_methods[] = {
    { "ssl_is_https",   (PyCFunction)Dispatch_ssl_is_https, METH_VARARGS, 0 },
    { "ssl_var_lookup", (PyCFunction)Dispatch_ssl_var_lookup, METH_VARARGS, 0 },
    { NULL, NULL}
};

static PyTypeObject Dispatch_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "mod_wsgi.Dispatch",    /*tp_name*/
    sizeof(DispatchObject), /*tp_basicsize*/
    0,                      /*tp_itemsize*/
    /* methods */
    (destructor)Dispatch_dealloc, /*tp_dealloc*/
    0,                      /*tp_print*/
    0,                      /*tp_getattr*/
    0,                      /*tp_setattr*/
    0,                      /*tp_compare*/
    0,                      /*tp_repr*/
    0,                      /*tp_as_number*/
    0,                      /*tp_as_sequence*/
    0,                      /*tp_as_mapping*/
    0,                      /*tp_hash*/
    0,                      /*tp_call*/
    0,                      /*tp_str*/
    0,                      /*tp_getattro*/
    0,                      /*tp_setattro*/
    0,                      /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,     /*tp_flags*/
    0,                      /*tp_doc*/
    0,                      /*tp_traverse*/
    0,                      /*tp_clear*/
    0,                      /*tp_richcompare*/
    0,                      /*tp_weaklistoffset*/
    0,                      /*tp_iter*/
    0,                      /*tp_iternext*/
    Dispatch_methods,       /*tp_methods*/
    0,                      /*tp_members*/
    0,                      /*tp_getset*/
    0,                      /*tp_base*/
    0,                      /*tp_dict*/
    0,                      /*tp_descr_get*/
    0,                      /*tp_descr_set*/
    0,                      /*tp_dictoffset*/
    0,                      /*tp_init*/
    0,                      /*tp_alloc*/
    0,                      /*tp_new*/
    0,                      /*tp_free*/
    0,                      /*tp_is_gc*/
};

static int wsgi_execute_dispatch(request_rec *r)
{
    WSGIRequestConfig *config;

    InterpreterObject *interp = NULL;
    PyObject *modules = NULL;
    PyObject *module = NULL;
    char *name = NULL;
    int exists = 0;

    const char *script = NULL;
    const char *group = NULL;

    int status;

    /* Grab request configuration. */

    config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
                                                       &wsgi_module);

    if (!config->dispatch_script) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Location of WSGI dispatch "
                     "script not provided.", getpid());

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /*
     * Acquire the desired python interpreter. Once this is done
     * it is safe to start manipulating python objects.
     */

    script = config->dispatch_script->handler_script;
    group = wsgi_server_group(r, config->dispatch_script->application_group);

    interp = wsgi_acquire_interpreter(group);

    if (!interp) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "mod_wsgi (pid=%d): Cannot acquire interpreter '%s'.",
                      getpid(), group);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* Calculate the Python module name to be used for script. */

    name = wsgi_module_name(r->pool, script);

    /*
     * Use a lock around the check to see if the module is
     * already loaded and the import of the module to prevent
     * two request handlers trying to import the module at the
     * same time.
     */

#if APR_HAS_THREADS
    Py_BEGIN_ALLOW_THREADS
    apr_thread_mutex_lock(wsgi_module_lock);
    Py_END_ALLOW_THREADS
#endif

    modules = PyImport_GetModuleDict();
    module = PyDict_GetItemString(modules, name);

    Py_XINCREF(module);

    if (module)
        exists = 1;

    /*
     * If script reloading is enabled and the module for it has
     * previously been loaded, see if it has been modified since
     * the last time it was accessed.
     */

    if (module && config->script_reloading) {
        if (wsgi_reload_required(r->pool, r, script, module, NULL)) {
            /*
             * Script file has changed. Only support module
             * reloading for dispatch scripts. Remove the
             * module from the modules dictionary before
             * reloading it again. If code is executing within
             * the module at the time, the callers reference
             * count on the module should ensure it isn't
             * actually destroyed until it is finished.
             */

            Py_DECREF(module);
            module = NULL;

            PyDict_DelItemString(modules, name);
        }
    }

    if (!module) {
        module = wsgi_load_source(r->pool, r, name, exists, script,
                                  "", group, 0);
    }

    /* Safe now to release the module lock. */

#if APR_HAS_THREADS
    apr_thread_mutex_unlock(wsgi_module_lock);
#endif

    /* Log any details of exceptions if import failed. */

    if (PyErr_Occurred())
        wsgi_log_python_error(r, NULL, script, 0);

    /* Assume everything will be okay for now. */

    status = OK;

    /* Determine if script exists and execute it. */

    if (module) {
        PyObject *module_dict = NULL;
        PyObject *object = NULL;
        DispatchObject *adapter = NULL;

        module_dict = PyModule_GetDict(module);

        adapter = newDispatchObject(r, config);

        if (adapter) {
            PyObject *vars = NULL;
            PyObject *args = NULL;
            PyObject *method = NULL;

            vars = Dispatch_environ(adapter, group);

            /* First check process_group(). */

#if defined(MOD_WSGI_WITH_DAEMONS)
            object = PyDict_GetItemString(module_dict, "process_group");

            if (object) {
                PyObject *result = NULL;

                if (adapter) {
                    Py_INCREF(object);
                    args = Py_BuildValue("(O)", vars);
                    result = PyObject_CallObject(object, args);
                    Py_DECREF(args);
                    Py_DECREF(object);

                    if (result) {
                        if (result != Py_None) {
                            if (PyString_Check(result)) {
                                const char *s;

                                s = PyString_AsString(result);
                                s = apr_pstrdup(r->pool, s);
                                s = wsgi_process_group(r, s);
                                config->process_group = s;

                                apr_table_setn(r->subprocess_env,
                                               "mod_wsgi.process_group",
                                               config->process_group);
                            }
#if PY_MAJOR_VERSION >= 3
                            else if (PyUnicode_Check(result)) {
                                PyObject *latin_item;
                                latin_item = PyUnicode_AsLatin1String(result);
                                if (!latin_item) {
                                    PyErr_SetString(PyExc_TypeError,
                                                    "Process group must be "
                                                    "a byte string, value "
                                                    "containing non 'latin-1' "
                                                    "characters found");

                                    status = HTTP_INTERNAL_SERVER_ERROR;
                                }
                                else {
                                    const char *s;

                                    Py_DECREF(result);
                                    result = latin_item;

                                    s = PyString_AsString(result);
                                    s = apr_pstrdup(r->pool, s);
                                    s = wsgi_process_group(r, s);
                                    config->process_group = s;

                                    apr_table_setn(r->subprocess_env,
                                                   "mod_wsgi.process_group",
                                                   config->process_group);
                                }
                            }
#endif
                            else {
                                PyErr_SetString(PyExc_TypeError, "Process "
                                                "group must be a byte string");

                                status = HTTP_INTERNAL_SERVER_ERROR;
                            }
                        }

                        Py_DECREF(result);
                    }
                    else
                        status = HTTP_INTERNAL_SERVER_ERROR;

                    /* Log any details of exceptions if execution failed. */

                    if (PyErr_Occurred())
                        wsgi_log_python_error(r, NULL, script, 0);
                }

                object = NULL;
            }
#endif

            /* Now check application_group(). */

            if (status == OK)
                object = PyDict_GetItemString(module_dict, "application_group");

            if (object) {
                PyObject *result = NULL;

                if (adapter) {
                    Py_INCREF(object);
                    args = Py_BuildValue("(O)", vars);
                    result = PyObject_CallObject(object, args);
                    Py_DECREF(args);
                    Py_DECREF(object);

                    if (result) {
                        if (result != Py_None) {
                            if (PyString_Check(result)) {
                                const char *s;

                                s = PyString_AsString(result);
                                s = apr_pstrdup(r->pool, s);
                                s = wsgi_application_group(r, s);
                                config->application_group = s;

                                apr_table_setn(r->subprocess_env,
                                               "mod_wsgi.application_group",
                                               config->application_group);
                            }
#if PY_MAJOR_VERSION >= 3
                            else if (PyUnicode_Check(result)) {
                                PyObject *latin_item;
                                latin_item = PyUnicode_AsLatin1String(result);
                                if (!latin_item) {
                                    PyErr_SetString(PyExc_TypeError,
                                                    "Application group must "
                                                    "be a byte string, value "
                                                    "containing non 'latin-1' "
                                                    "characters found");

                                    status = HTTP_INTERNAL_SERVER_ERROR;
                                }
                                else {
                                    const char *s;

                                    Py_DECREF(result);
                                    result = latin_item;

                                    s = PyString_AsString(result);
                                    s = apr_pstrdup(r->pool, s);
                                    s = wsgi_application_group(r, s);
                                    config->application_group = s;

                                    apr_table_setn(r->subprocess_env,
                                                   "mod_wsgi.application_group",
                                                   config->application_group);
                                }
                            }
#endif
                            else {
                                PyErr_SetString(PyExc_TypeError, "Application "
                                                "group must be a string "
                                                "object");

                                status = HTTP_INTERNAL_SERVER_ERROR;
                            }
                        }

                        Py_DECREF(result);
                    }
                    else
                        status = HTTP_INTERNAL_SERVER_ERROR;

                    /* Log any details of exceptions if execution failed. */

                    if (PyErr_Occurred())
                        wsgi_log_python_error(r, NULL, script, 0);
                }

                object = NULL;
            }

            /* Now check callable_object(). */

            if (status == OK)
                object = PyDict_GetItemString(module_dict, "callable_object");

            if (object) {
                PyObject *result = NULL;

                if (adapter) {
                    Py_INCREF(object);
                    args = Py_BuildValue("(O)", vars);
                    result = PyObject_CallObject(object, args);
                    Py_DECREF(args);
                    Py_DECREF(object);

                    if (result) {
                        if (result != Py_None) {
                            if (PyString_Check(result)) {
                                const char *s;

                                s = PyString_AsString(result);
                                s = apr_pstrdup(r->pool, s);
                                s = wsgi_callable_object(r, s);
                                config->callable_object = s;

                                apr_table_setn(r->subprocess_env,
                                               "mod_wsgi.callable_object",
                                               config->callable_object);
                            }
#if PY_MAJOR_VERSION >= 3
                            else if (PyUnicode_Check(result)) {
                                PyObject *latin_item;
                                latin_item = PyUnicode_AsLatin1String(result);
                                if (!latin_item) {
                                    PyErr_SetString(PyExc_TypeError,
                                                    "Callable object must "
                                                    "be a byte string, value "
                                                    "containing non 'latin-1' "
                                                    "characters found");

                                    status = HTTP_INTERNAL_SERVER_ERROR;
                                }
                                else {
                                    const char *s;

                                    Py_DECREF(result);
                                    result = latin_item;

                                    s = PyString_AsString(result);
                                    s = apr_pstrdup(r->pool, s);
                                    s = wsgi_callable_object(r, s);
                                    config->callable_object = s;

                                    apr_table_setn(r->subprocess_env,
                                                   "mod_wsgi.callable_object",
                                                   config->callable_object);
                                }
                            }
#endif
                            else {
                                PyErr_SetString(PyExc_TypeError, "Callable "
                                                "object must be a string "
                                                "object");

                                status = HTTP_INTERNAL_SERVER_ERROR;
                            }
                        }

                        Py_DECREF(result);
                    }
                    else
                        status = HTTP_INTERNAL_SERVER_ERROR;

                    /* Log any details of exceptions if execution failed. */

                    if (PyErr_Occurred())
                        wsgi_log_python_error(r, NULL, script, 0);
                }

                object = NULL;
            }

            /*
             * Wipe out references to Apache request object
             * held by Python objects, so can detect when an
             * application holds on to the transient Python
             * objects beyond the life of the request and
             * thus raise an exception if they are used.
             */

            adapter->r = NULL;

            /* Close the log object so data is flushed. */

            method = PyObject_GetAttrString(adapter->log, "close");

            if (!method) {
                PyErr_Format(PyExc_AttributeError,
                             "'%s' object has no attribute 'close'",
                             adapter->log->ob_type->tp_name);
            }
            else {
                object = PyObject_CallObject(method, NULL);
            }

            Py_XDECREF(object);
            Py_XDECREF(method);

            /* No longer need adapter object. */

            Py_DECREF((PyObject *)adapter);

            /* Log any details of exceptions if execution failed. */

            if (PyErr_Occurred())
                wsgi_log_python_error(r, NULL, script, 0);

            Py_DECREF(vars);
        }
    }

    /* Cleanup and release interpreter, */

    Py_XDECREF(module);

    wsgi_release_interpreter(interp);

    return status;
}

static int wsgi_is_script_aliased(request_rec *r)
{
    const char *t = NULL;

    t = apr_table_get(r->notes, "alias-forced-type");
    return t && (!strcasecmp(t, "wsgi-script"));
}

#if defined(MOD_WSGI_WITH_DAEMONS)
static int wsgi_execute_remote(request_rec *r);
#endif

static int wsgi_hook_handler(request_rec *r)
{
    int status;
    apr_off_t limit = 0;

    WSGIRequestConfig *config = NULL;

    const char *value = NULL;

    const char *tenc = NULL;
    const char *lenp = NULL;

    /* Filter out the obvious case of no handler defined. */

    if (!r->handler)
        return DECLINED;

    /*
     * Construct request configuration and cache it in the
     * request object against this module so can access it later
     * from handler code.
     */

    config = wsgi_create_req_config(r->pool, r);

    ap_set_module_config(r->request_config, &wsgi_module, config);

    /*
     * Only process requests for this module. First check for
     * where target is the actual WSGI script. Then need to
     * check for the case where handler name mapped to a handler
     * script definition.
     */

    if (!strcmp(r->handler, "wsgi-script") ||
        !strcmp(r->handler, "application/x-httpd-wsgi")) {

        /*
         * Ensure that have adequate privileges to run the WSGI
         * script. Require ExecCGI to be specified in Options for
         * this. In doing this, using the wider interpretation that
         * ExecCGI refers to any executable like script even though
         * not a separate process execution.
         */

        if (!(ap_allow_options(r) & OPT_EXECCGI) &&
            !wsgi_is_script_aliased(r)) {
            wsgi_log_script_error(r, "Options ExecCGI is off in this "
                                  "directory", r->filename);
            return HTTP_FORBIDDEN;
        }

        /* Ensure target script exists and is a file. */

        if (r->finfo.filetype == 0) {
            wsgi_log_script_error(r, "Target WSGI script not found or unable "
                                  "to stat", r->filename);
            return HTTP_NOT_FOUND;
        }

        if (r->finfo.filetype == APR_DIR) {
            wsgi_log_script_error(r, "Attempt to invoke directory as WSGI "
                                  "application", r->filename);
            return HTTP_FORBIDDEN;
        }

        if (wsgi_is_script_aliased(r)) {
            /*
             * Allow any configuration supplied through request notes to
             * override respective values. Request notes are used when
             * configuration supplied with WSGIScriptAlias directives.
             */

            if ((value = apr_table_get(r->notes, "mod_wsgi.process_group")))
                config->process_group = wsgi_process_group(r, value);
            if ((value = apr_table_get(r->notes, "mod_wsgi.application_group")))
                config->application_group = wsgi_application_group(r, value);
            if ((value = apr_table_get(r->notes, "mod_wsgi.callable_object")))
                config->callable_object = value;

            if ((value = apr_table_get(r->notes,
                                      "mod_wsgi.pass_authorization"))) {
                if (!strcmp(value, "1"))
                    config->pass_authorization = 1;
                else
                    config->pass_authorization = 0;
            }
        }
    }
#if 0
    else if (strstr(r->handler, "wsgi-handler=") == r->handler) {
        config->handler_script = apr_pstrcat(r->pool, r->handler+13, NULL);
        config->callable_object = "handle_request";
    }
#endif
    else if (config->handler_scripts) {
        WSGIScriptFile *entry;

        entry = (WSGIScriptFile *)apr_hash_get(config->handler_scripts,
                                               r->handler,
                                               APR_HASH_KEY_STRING);

        if (entry) {
            config->handler_script = entry->handler_script;
            config->callable_object = "handle_request";

            if ((value = entry->process_group))
                config->process_group = wsgi_process_group(r, value);
            if ((value = entry->application_group))
                config->application_group = wsgi_application_group(r, value);

            if ((value = entry->pass_authorization)) {
                if (!strcmp(value, "1"))
                    config->pass_authorization = 1;
                else
                    config->pass_authorization = 0;
            }
        }
        else
            return DECLINED;
    }
    else
        return DECLINED;

    /*
     * Honour AcceptPathInfo directive. Default behaviour is
     * accept additional path information.
     */

#if AP_MODULE_MAGIC_AT_LEAST(20011212,0)
    if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
        r->path_info && *r->path_info) {
        wsgi_log_script_error(r, "AcceptPathInfo off disallows user's path",
                              r->filename);
        return HTTP_NOT_FOUND;
    }
#endif

    /*
     * Setup policy to apply if request contains a body. Note that the
     * WSGI specification doesn't strictly allow for chunked request
     * content as CONTENT_LENGTH is required when reading input and
     * an application isn't meant to read more than what is defined by
     * CONTENT_LENGTH. We still optionally allow chunked request content.
     * For an application to use the content, it has to ignore the WSGI
     * specification and use read() with no arguments to read all
     * available input, or call read() with specific block size until
     * read() returns an empty string.
     */

    tenc = apr_table_get(r->headers_in, "Transfer-Encoding");

    if (tenc) {
        /* Only chunked transfer encoding is supported. */

        if (strcasecmp(tenc, "chunked")) {
            wsgi_log_script_error(r, apr_psprintf(r->pool,
                    "Unexpected value for Transfer-Encoding of '%s' "
                    "supplied. Only 'chunked' supported.", tenc),
                    r->filename);
            return HTTP_NOT_IMPLEMENTED;
        }

        /* Only allow chunked requests when explicitly enabled. */

        if (!config->chunked_request) {
            wsgi_log_script_error(r, "Received request requiring chunked "
                    "transfer encoding, but optional support for chunked "
                    "transfer encoding has not been enabled.", r->filename);
            return HTTP_LENGTH_REQUIRED;
        }

        /*
         * When chunked transfer encoding is specified, there should
         * not be any content length specified.
         */

        if (lenp) {
            wsgi_log_script_error(r, "Unexpected Content-Length header "
                    "supplied where Transfer-Encoding was specified "
                    "as 'chunked'.", r->filename);
            return HTTP_BAD_REQUEST;
        }
    }

    /*
     * Check to see if the request content is too large if the
     * Content-Length header is defined then end the request here. We do
     * this as otherwise it will not be done until first time input data
     * is read in by the application. Problem is that underlying HTTP
     * output filter will also generate a 413 response and the error
     * raised from the application will be appended to that. The call to
     * ap_discard_request_body() is hopefully enough to trigger sending
     * of the 413 response by the HTTP filter.
     */

    lenp = apr_table_get(r->headers_in, "Content-Length");

    if (lenp) {
        char *endstr;
        apr_off_t length;

        if (wsgi_strtoff(&length, lenp, &endstr, 10)
            || *endstr || length < 0) {

            wsgi_log_script_error(r, apr_psprintf(r->pool,
                    "Invalid Content-Length header value of '%s' was "
                    "supplied.", lenp), r->filename);

            return HTTP_BAD_REQUEST;
        }

        limit = ap_get_limit_req_body(r);

        if (limit && limit < length) {
            ap_discard_request_body(r);
            return OK;
        }
    }

    /* Build the sub process environment. */

    config->request_start = r->request_time;

    wsgi_build_environment(r);

    /*
     * If a dispatch script has been provided, as appropriate
     * allow it to override any of the configuration related
     * to what context the script will be executed in and what
     * the target callable object for the application is.
     */

    if (config->dispatch_script) {
        status = wsgi_execute_dispatch(r);

        if (status != OK)
            return status;
    }

    /*
     * Execute the target WSGI application script or proxy
     * request to one of the daemon processes as appropriate.
     */

#if defined(MOD_WSGI_WITH_DAEMONS)
    status = wsgi_execute_remote(r);

    if (status != DECLINED)
        return status;
#endif

#if defined(MOD_WSGI_DISABLE_EMBEDDED)
    wsgi_log_script_error(r, "Embedded mode of mod_wsgi disabled at compile "
                          "time", r->filename);
    return HTTP_INTERNAL_SERVER_ERROR;
#endif

    if (wsgi_server_config->restrict_embedded == 1) {
        wsgi_log_script_error(r, "Embedded mode of mod_wsgi disabled by "
                              "runtime configuration", r->filename);
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    return wsgi_execute_script(r);
}

/*
 * Apache 2.X and UNIX specific code for creation and management
 * of distinct daemon processes.
 */

#if defined(MOD_WSGI_WITH_DAEMONS)

static const char *wsgi_add_daemon_process(cmd_parms *cmd, void *mconfig,
                                           const char *args)
{
    const char *name = NULL;
    const char *user = NULL;
    const char *group = NULL;

    int processes = 1;
    int multiprocess = 0;
    int threads = 15;
    long umask = -1;

    const char *root = NULL;
    const char *home = NULL;

    const char *lang = NULL;
    const char *locale = NULL;

    const char *python_home = NULL;
    const char *python_path = NULL;
    const char *python_eggs = NULL;

    int stack_size = 0;
    int maximum_requests = 0;
    int startup_timeout = 0;
    int shutdown_timeout = 5;
    int deadlock_timeout = 300;
    int inactivity_timeout = 0;
    int request_timeout = 0;
    int graceful_timeout = 15;
    int eviction_timeout = 0;
    int restart_interval = 0;
    int connect_timeout = 15;
    int socket_timeout = 0;
    int queue_timeout = 0;

    const char *socket_user = NULL;

    int listen_backlog = WSGI_LISTEN_BACKLOG;

    const char *display_name = NULL;

    int send_buffer_size = 0;
    int recv_buffer_size = 0;
    int header_buffer_size = 0;
    int response_buffer_size = 0;

    int response_socket_timeout = 0;

    const char *script_user = NULL;
    const char *script_group = NULL;

    int cpu_time_limit = 0;
    int cpu_priority = 0;

    apr_int64_t memory_limit = 0;
    apr_int64_t virtual_memory_limit = 0;

    uid_t uid;
    uid_t gid;

    const char *groups_list = NULL;
    int groups_count = 0;
    gid_t *groups = NULL;

    int server_metrics = 0;

    const char *newrelic_config_file = NULL;
    const char *newrelic_environment = NULL;

    const char *option = NULL;
    const char *value = NULL;

    WSGIProcessGroup *entries = NULL;
    WSGIProcessGroup *entry = NULL;

    int i;

    /*
     * Set the defaults for user/group from values
     * defined for the User/Group directives in main
     * Apache configuration.
     */

    uid = ap_unixd_config.user_id;
    user = ap_unixd_config.user_name;

    gid = ap_unixd_config.group_id;

    /* Now parse options for directive. */

    name = ap_getword_conf(cmd->pool, &args);

    if (!name || !*name)
        return "Name of WSGI daemon process not supplied.";

    while (*args) {
        if (wsgi_parse_option(cmd->pool, &args, &option,
                              &value) != APR_SUCCESS) {
            return "Invalid option to WSGI daemon process definition.";
        }

        if (!strcmp(option, "user")) {
            if (!*value)
                return "Invalid user for WSGI daemon process.";

            user = value;
            uid = ap_uname2id(user);
            if (uid == 0)
                return "WSGI process blocked from running as root.";

            if (*user == '#') {
                struct passwd *entry = NULL;

                if ((entry = getpwuid(uid)) == NULL)
                    return "Couldn't determine user name from uid.";

                user = entry->pw_name;
            }
        }
        else if (!strcmp(option, "group")) {
            if (!*value)
                return "Invalid group for WSGI daemon process.";

            group = value;
            gid = ap_gname2id(group);
        }
        else if (!strcmp(option, "supplementary-groups")) {
            groups_list = value;
        }
        else if (!strcmp(option, "processes")) {
            if (!*value)
                return "Invalid process count for WSGI daemon process.";

            processes = atoi(value);
            if (processes < 1)
                return "Invalid process count for WSGI daemon process.";

            multiprocess = 1;
        }
        else if (!strcmp(option, "threads")) {
            if (!*value)
                return "Invalid thread count for WSGI daemon process.";

            threads = atoi(value);
            if (threads < 0 || threads >= WSGI_STACK_LAST-1)
                return "Invalid thread count for WSGI daemon process.";
        }
        else if (!strcmp(option, "umask")) {
            if (!*value)
                return "Invalid umask for WSGI daemon process.";

            errno = 0;
            umask = strtol(value, (char **)&value, 8);

            if (*value || errno == ERANGE || umask < 0)
                return "Invalid umask for WSGI daemon process.";
        }
        else if (!strcmp(option, "chroot")) {
            if (geteuid())
                return "Cannot chroot WSGI daemon process when not root.";

            if (*value != '/')
                return "Invalid chroot directory for WSGI daemon process.";

            root = value;
        }
        else if (!strcmp(option, "home")) {
            if (*value != '/')
                return "Invalid home directory for WSGI daemon process.";

            home = value;
        }
        else if (!strcmp(option, "lang")) {
            lang = value;
        }
        else if (!strcmp(option, "locale")) {
            locale = value;
        }
        else if (!strcmp(option, "python-home")) {
            python_home = value;
        }
        else if (!strcmp(option, "python-path")) {
            python_path = value;
        }
        else if (!strcmp(option, "python-eggs")) {
            python_eggs = value;
        }
#if (APR_MAJOR_VERSION >= 1)
        else if (!strcmp(option, "stack-size")) {
            if (!*value)
                return "Invalid stack size for WSGI daemon process.";

            stack_size = atoi(value);
            if (stack_size <= 0)
                return "Invalid stack size for WSGI daemon process.";
        }
#endif
        else if (!strcmp(option, "maximum-requests")) {
            if (!*value)
                return "Invalid request count for WSGI daemon process.";

            maximum_requests = atoi(value);
            if (maximum_requests < 0)
                return "Invalid request count for WSGI daemon process.";
        }
        else if (!strcmp(option, "startup-timeout")) {
            if (!*value)
                return "Invalid startup timeout for WSGI daemon process.";

            startup_timeout = atoi(value);
            if (startup_timeout < 0)
                return "Invalid startup timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "shutdown-timeout")) {
            if (!*value)
                return "Invalid shutdown timeout for WSGI daemon process.";

            shutdown_timeout = atoi(value);
            if (shutdown_timeout < 0)
                return "Invalid shutdown timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "deadlock-timeout")) {
            if (!*value)
                return "Invalid deadlock timeout for WSGI daemon process.";

            deadlock_timeout = atoi(value);
            if (deadlock_timeout < 0)
                return "Invalid deadlock timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "inactivity-timeout")) {
            if (!*value)
                return "Invalid inactivity timeout for WSGI daemon process.";

            inactivity_timeout = atoi(value);
            if (inactivity_timeout < 0)
                return "Invalid inactivity timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "request-timeout")) {
            if (!*value)
                return "Invalid request timeout for WSGI daemon process.";

            request_timeout = atoi(value);
            if (request_timeout < 0)
                return "Invalid request timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "graceful-timeout")) {
            if (!*value)
                return "Invalid graceful timeout for WSGI daemon process.";

            graceful_timeout = atoi(value);
            if (graceful_timeout < 0)
                return "Invalid graceful timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "eviction-timeout")) {
            if (!*value)
                return "Invalid eviction timeout for WSGI daemon process.";

            eviction_timeout = atoi(value);
            if (eviction_timeout < 0)
                return "Invalid eviction timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "restart-interval")) {
            if (!*value)
                return "Invalid restart interval for WSGI daemon process.";

            restart_interval = atoi(value);
            if (restart_interval < 0)
                return "Invalid restart interval for WSGI daemon process.";
        }
        else if (!strcmp(option, "connect-timeout")) {
            if (!*value)
                return "Invalid connect timeout for WSGI daemon process.";

            connect_timeout = atoi(value);
            if (connect_timeout < 0)
                return "Invalid connect timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "socket-timeout")) {
            if (!*value)
                return "Invalid socket timeout for WSGI daemon process.";

            socket_timeout = atoi(value);
            if (socket_timeout < 0)
                return "Invalid socket timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "queue-timeout")) {
            if (!*value)
                return "Invalid queue timeout for WSGI daemon process.";

            queue_timeout = atoi(value);
            if (queue_timeout < 0)
                return "Invalid queue timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "listen-backlog")) {
            if (!*value)
                return "Invalid listen backlog for WSGI daemon process.";

            listen_backlog = atoi(value);
            if (listen_backlog < 0)
                return "Invalid listen backlog for WSGI daemon process.";
        }
        else if (!strcmp(option, "display-name")) {
            display_name = value;
        }
        else if (!strcmp(option, "send-buffer-size")) {
            if (!*value)
                return "Invalid send buffer size for WSGI daemon process.";

            send_buffer_size = atoi(value);
            if (send_buffer_size < 512 && send_buffer_size != 0) {
                return "Send buffer size must be >= 512 bytes, "
                       "or 0 for system default.";
            }
        }
        else if (!strcmp(option, "receive-buffer-size")) {
            if (!*value)
                return "Invalid receive buffer size for WSGI daemon process.";

            recv_buffer_size = atoi(value);
            if (recv_buffer_size < 512 && recv_buffer_size != 0) {
                return "Receive buffer size must be >= 512 bytes, "
                       "or 0 for system default.";
            }
        }
        else if (!strcmp(option, "header-buffer-size")) {
            if (!*value)
                return "Invalid header buffer size for WSGI daemon process.";

            header_buffer_size = atoi(value);
            if (header_buffer_size < 8192 && header_buffer_size != 0) {
                return "Header buffer size must be >= 8192 bytes, "
                       "or 0 for default.";
            }
        }
        else if (!strcmp(option, "response-buffer-size")) {
            if (!*value)
                return "Invalid response buffer size for WSGI daemon process.";

            response_buffer_size = atoi(value);
            if (response_buffer_size < 65536 && response_buffer_size != 0) {
                return "Response buffer size must be >= 65536 bytes, "
                       "or 0 for default.";
            }
        }
        else if (!strcmp(option, "response-socket-timeout")) {
            if (!*value)
                return "Invalid response socket timeout for WSGI daemon process.";

            response_socket_timeout = atoi(value);
            if (response_socket_timeout < 0)
                return "Invalid response socket timeout for WSGI daemon process.";
        }
        else if (!strcmp(option, "socket-user")) {
            uid_t socket_uid;

            if (!*value)
                return "Invalid socket user for WSGI daemon process.";

            socket_uid = ap_uname2id(value);

            if (*value == '#') {
                struct passwd *entry = NULL;

                if ((entry = getpwuid(socket_uid)) == NULL)
                    return "Couldn't determine user name from socket user.";

                value = entry->pw_name;
            }

            socket_user = value;
        }
        else if (!strcmp(option, "script-user")) {
            uid_t script_uid;

            if (!*value)
                return "Invalid script user for WSGI daemon process.";

            script_uid = ap_uname2id(value);

            if (*value == '#') {
                struct passwd *entry = NULL;

                if ((entry = getpwuid(script_uid)) == NULL)
                    return "Couldn't determine uid from script user.";

                value = entry->pw_name;
            }

            script_user = value;
        }
        else if (!strcmp(option, "script-group")) {
            gid_t script_gid;

            if (!*value)
                return "Invalid script group for WSGI daemon process.";

            script_gid = ap_gname2id(value);

            if (*value == '#') {
                struct group *entry = NULL;

                if ((entry = getgrgid(script_gid)) == NULL)
                    return "Couldn't determine gid from script group.";

                value = entry->gr_name;
            }

            script_group = value;
        }
        else if (!strcmp(option, "cpu-time-limit")) {
            if (!*value)
                return "Invalid CPU time limit for WSGI daemon process.";

            cpu_time_limit = atoi(value);
            if (cpu_time_limit < 0)
                return "Invalid CPU time limit for WSGI daemon process.";
        }
        else if (!strcmp(option, "cpu-priority")) {
            if (!*value)
                return "Invalid CPU priority for WSGI daemon process.";

            cpu_priority = atoi(value);
        }
        else if (!strcmp(option, "memory-limit")) {
            if (!*value)
                return "Invalid memory limit for WSGI daemon process.";

            memory_limit = apr_atoi64(value);
            if (memory_limit < 0)
                return "Invalid memory limit for WSGI daemon process.";
        }
        else if (!strcmp(option, "virtual-memory-limit")) {
            if (!*value)
                return "Invalid virtual memory limit for WSGI daemon process.";

            virtual_memory_limit = apr_atoi64(value);
            if (virtual_memory_limit < 0)
                return "Invalid virtual memory limit for WSGI daemon process.";
        }
        else if (!strcmp(option, "server-metrics")) {
            if (!*value)
                return "Invalid server metrics flag for WSGI daemon process.";

            if (strcasecmp(value, "Off") == 0)
                server_metrics = 0;
            else if (strcasecmp(value, "On") == 0)
                server_metrics = 1;
            else
                return "Invalid server metrics flag for WSGI daemon process.";
        }
        else if (!strcmp(option, "newrelic-config-file")) {
            newrelic_config_file = value;
        }
        else if (!strcmp(option, "newrelic-environment")) {
            newrelic_environment = value;
        }
        else
            return "Invalid option to WSGI daemon process definition.";
    }

    if (script_user && script_group)
        return "Only one of script-user and script-group allowed.";

    if (groups_list) {
        const char *group_name = NULL;
        long groups_maximum = NGROUPS_MAX;
        const char *items = NULL;

#ifdef _SC_NGROUPS_MAX
        groups_maximum = sysconf(_SC_NGROUPS_MAX);
        if (groups_maximum < 0)
            groups_maximum = NGROUPS_MAX;
#endif
        groups = (gid_t *)apr_pcalloc(cmd->pool,
                                      groups_maximum*sizeof(groups[0]));

        groups[groups_count++] = gid;

        items = groups_list;
        group_name = ap_getword(cmd->pool, &items, ',');

        while (group_name && *group_name) {
            if (groups_count >= groups_maximum)
                return "Too many supplementary groups WSGI daemon process";

            groups[groups_count++] = ap_gname2id(group_name);
            group_name = ap_getword(cmd->pool, &items, ',');
        }
    }

    if (!wsgi_daemon_list) {
        wsgi_daemon_list = apr_array_make(cmd->pool, 20,
                                          sizeof(WSGIProcessGroup));
        apr_pool_cleanup_register(cmd->pool, &wsgi_daemon_list,
                              ap_pool_cleanup_set_null,
                              apr_pool_cleanup_null);
    }

    entries = (WSGIProcessGroup *)wsgi_daemon_list->elts;

    for (i = 0; i < wsgi_daemon_list->nelts; ++i) {
        entry = &entries[i];

        if (!strcmp(entry->name, name))
            return "Name duplicates previous WSGI daemon definition.";
    }

    wsgi_daemon_count++;

    entry = (WSGIProcessGroup *)apr_array_push(wsgi_daemon_list);

    entry->server = cmd->server;

    entry->random = random();
    entry->id = wsgi_daemon_count;

    entry->name = apr_pstrdup(cmd->pool, name);
    entry->user = apr_pstrdup(cmd->pool, user);
    entry->group = apr_pstrdup(cmd->pool, group);

    entry->uid = uid;
    entry->gid = gid;

    entry->groups_list = groups_list;
    entry->groups_count = groups_count;
    entry->groups = groups;

    entry->processes = processes;
    entry->multiprocess = multiprocess;
    entry->threads = threads;

    entry->umask = umask;
    entry->root = root;
    entry->home = home;

    entry->lang = lang;
    entry->locale = locale;

    entry->python_home = python_home;
    entry->python_path = python_path;
    entry->python_eggs = python_eggs;

    entry->stack_size = stack_size;
    entry->maximum_requests = maximum_requests;
    entry->shutdown_timeout = shutdown_timeout;
    entry->startup_timeout = apr_time_from_sec(startup_timeout);
    entry->deadlock_timeout = apr_time_from_sec(deadlock_timeout);
    entry->inactivity_timeout = apr_time_from_sec(inactivity_timeout);
    entry->request_timeout = apr_time_from_sec(request_timeout);
    entry->graceful_timeout = apr_time_from_sec(graceful_timeout);
    entry->eviction_timeout = apr_time_from_sec(eviction_timeout);
    entry->restart_interval = apr_time_from_sec(restart_interval);
    entry->connect_timeout = apr_time_from_sec(connect_timeout);
    entry->socket_timeout = apr_time_from_sec(socket_timeout);
    entry->queue_timeout = apr_time_from_sec(queue_timeout);

    entry->socket_user = apr_pstrdup(cmd->pool, socket_user);

    entry->listen_backlog = listen_backlog;

    entry->display_name = display_name;

    entry->send_buffer_size = send_buffer_size;
    entry->recv_buffer_size = recv_buffer_size;
    entry->header_buffer_size = header_buffer_size;
    entry->response_buffer_size = response_buffer_size;

    if (response_socket_timeout == 0)
        response_socket_timeout = socket_timeout;

    entry->response_socket_timeout = apr_time_from_sec(response_socket_timeout);

    entry->script_user = script_user;
    entry->script_group = script_group;

    entry->cpu_time_limit = cpu_time_limit;
    entry->cpu_priority = cpu_priority;

    entry->memory_limit = memory_limit;
    entry->virtual_memory_limit = virtual_memory_limit;

    entry->server_metrics = server_metrics;

    entry->newrelic_config_file = newrelic_config_file;
    entry->newrelic_environment = newrelic_environment;

    entry->listener_fd = -1;

    return NULL;
}

static const char *wsgi_set_socket_prefix(cmd_parms *cmd, void *mconfig,
                                         const char *arg)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    sconfig->socket_prefix = ap_server_root_relative(cmd->pool, arg);

    if (!sconfig->socket_prefix) {
        return apr_pstrcat(cmd->pool, "Invalid WSGISocketPrefix '",
                           arg, "'.", NULL);
    }

    return NULL;
}

static const char *wsgi_set_socket_rotation(cmd_parms *cmd, void *mconfig,
                                            const char *f)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

    if (strcasecmp(f, "Off") == 0)
        sconfig->socket_rotation = 0;
    else if (strcasecmp(f, "On") == 0)
        sconfig->socket_rotation = 1;
    else
        return "WSGISocketRotation must be one of: Off | On";

    return NULL;
}

static const char wsgi_valid_accept_mutex_string[] =
    "Valid accept mutex mechanisms for this platform are: default"
#if APR_HAS_FLOCK_SERIALIZE
    ", flock"
#endif
#if APR_HAS_FCNTL_SERIALIZE
    ", fcntl"
#endif
#if APR_HAS_SYSVSEM_SERIALIZE
    ", sysvsem"
#endif
#if APR_HAS_POSIXSEM_SERIALIZE
    ", posixsem"
#endif
#if APR_HAS_PROC_PTHREAD_SERIALIZE
    ", pthread"
#endif
    ".";

static const char *wsgi_set_accept_mutex(cmd_parms *cmd, void *mconfig,
                                         const char *arg)
{
    const char *error = NULL;
    WSGIServerConfig *sconfig = NULL;

    error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (error != NULL)
        return error;

    sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);

#if !defined(AP_ACCEPT_MUTEX_TYPE)
    sconfig->lock_mechanism = ap_accept_lock_mech;
#else
    sconfig->lock_mechanism = APR_LOCK_DEFAULT;
#endif

    if (!strcasecmp(arg, "default")) {
        sconfig->lock_mechanism = APR_LOCK_DEFAULT;
    }
#if APR_HAS_FLOCK_SERIALIZE
    else if (!strcasecmp(arg, "flock")) {
        sconfig->lock_mechanism = APR_LOCK_FLOCK;
    }
#endif
#if APR_HAS_FCNTL_SERIALIZE
    else if (!strcasecmp(arg, "fcntl")) {
        sconfig->lock_mechanism = APR_LOCK_FCNTL;
    }
#endif
#if APR_HAS_SYSVSEM_SERIALIZE
    else if (!strcasecmp(arg, "sysvsem")) {
        sconfig->lock_mechanism = APR_LOCK_SYSVSEM;
    }
#endif
#if APR_HAS_POSIXSEM_SERIALIZE
    else if (!strcasecmp(arg, "posixsem")) {
        sconfig->lock_mechanism = APR_LOCK_POSIXSEM;
    }
#endif
#if APR_HAS_PROC_PTHREAD_SERIALIZE
    else if (!strcasecmp(arg, "pthread")) {
        sconfig->lock_mechanism = APR_LOCK_PROC_PTHREAD;
    }
#endif
    else {
        return apr_pstrcat(cmd->pool, "Accept mutex lock mechanism '", arg,
                           "' is invalid. ", wsgi_valid_accept_mutex_string,
                           NULL);
    }

    return NULL;
}

static apr_file_t *wsgi_signal_pipe_in = NULL;
static apr_file_t *wsgi_signal_pipe_out = NULL;

static void wsgi_signal_handler(int signum)
{
    apr_size_t nbytes = 1;

    if (wsgi_daemon_pid != 0 && wsgi_daemon_pid != getpid())
        exit(-1);

    if (signum == AP_SIG_GRACEFUL) {
        apr_file_write(wsgi_signal_pipe_out, "G", &nbytes);
        apr_file_flush(wsgi_signal_pipe_out);
    }
    else if (signum == SIGXCPU) {
        if (!wsgi_graceful_timeout)
            wsgi_daemon_shutdown++;

        apr_file_write(wsgi_signal_pipe_out, "C", &nbytes);
        apr_file_flush(wsgi_signal_pipe_out);
    }
    else {
        wsgi_daemon_shutdown++;

        apr_file_write(wsgi_signal_pipe_out, "S", &nbytes);
        apr_file_flush(wsgi_signal_pipe_out);
    }
}

static void wsgi_exit_daemon_process(int status)
{
    if (wsgi_server && wsgi_daemon_group) {
        ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Exiting process '%s'.", getpid(),
                     wsgi_daemon_group);
    }

    exit(status);
}

static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon);

static void wsgi_manage_process(int reason, void *data, apr_wait_t status)
{
    WSGIDaemonProcess *daemon = data;

    switch (reason) {

        /* Child daemon process has died. */

        case APR_OC_REASON_DEATH: {
            int mpm_state;
            int stopping;

            /*
             * Determine if Apache is being shutdown or not and
             * if it is not being shutdown, we will need to
             * restart the child daemon process that has died.
             * If MPM doesn't support query assume that child
             * daemon process shouldn't be restarted. Both
             * prefork and worker MPMs support this query so
             * should always be okay.
             */

            stopping = 1;

            if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state) == APR_SUCCESS
                && mpm_state != AP_MPMQ_STOPPING) {
                stopping = 0;
            }

            if (!stopping) {
                ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                             wsgi_server, "mod_wsgi (pid=%d): "
                             "Process '%s' has died, deregister and "
                             "restart it.", daemon->process.pid,
                             daemon->group->name);

                if (WIFEXITED(status)) {
                    ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                             wsgi_server, "mod_wsgi (pid=%d): "
                             "Process '%s' terminated normally, exit code %d",
                             daemon->process.pid, daemon->group->name,
                             WEXITSTATUS(status));
                }
                else if (WIFSIGNALED(status)) {
                    ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                             wsgi_server, "mod_wsgi (pid=%d): "
                             "Process '%s' terminated by signal %d",
                             daemon->process.pid, daemon->group->name,
                             WTERMSIG(status));
                }
            }
            else {
                ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                             wsgi_server, "mod_wsgi (pid=%d): "
                             "Process '%s' has died but server is "
                             "being stopped, deregister it.",
                             daemon->process.pid, daemon->group->name);
            }

            /* Deregister existing process so we stop watching it. */

            apr_proc_other_child_unregister(daemon);

            /* Now restart process if not shutting down. */

            if (!stopping)
                wsgi_start_process(wsgi_parent_pool, daemon);

            break;
        }

        /* Apache is being restarted or shutdown. */

        case APR_OC_REASON_RESTART: {

            ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                         wsgi_server, "mod_wsgi (pid=%d): "
                         "Process '%s' to be deregistered, as server is "
                         "restarting or being shutdown.",
                         daemon->process.pid, daemon->group->name);

            /* Deregister existing process so we stop watching it. */

            apr_proc_other_child_unregister(daemon);

            break;
        }

        /* Child daemon process vanished. */

        case APR_OC_REASON_LOST: {

            ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                         wsgi_server, "mod_wsgi (pid=%d): "
                         "Process '%s' appears to have been lost, "
                         "deregister and restart it.",
                         daemon->process.pid, daemon->group->name);

            /* Deregister existing process so we stop watching it. */

            apr_proc_other_child_unregister(daemon);

            /* Restart the child daemon process that has died. */

            wsgi_start_process(wsgi_parent_pool, daemon);

            break;
        }

        /* Call to unregister the process. */

        case APR_OC_REASON_UNREGISTER: {

            /* Nothing to do at present. */

            ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                         wsgi_server, "mod_wsgi (pid=%d): "
                         "Process '%s' has been deregistered and will "
                         "no longer be monitored.", daemon->process.pid,
                         daemon->group->name);

            break;
        }

        default: {
            ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                         wsgi_server, "mod_wsgi (pid=%d): "
                         "Process '%s' targeted by unexpected event %d.",
                         daemon->process.pid, daemon->group->name, reason);
        }
    }
}

static void wsgi_setup_daemon_name(WSGIDaemonProcess *daemon, apr_pool_t *p)
{
    const char *display_name = NULL;

#if !(defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
    long slen = 0;
    long dlen = 0;

    char *argv0 = NULL;
#endif

    display_name = daemon->group->display_name;

    if (!display_name)
        return;

    if (!strcmp(display_name, "%{GROUP}")) {
        display_name = apr_pstrcat(p, "(wsgi:", daemon->group->name,
                                   ")", NULL);
    }

    /*
     * Only argv[0] is guaranteed to be the real things as MPM
     * modules may make modifications to subsequent arguments.
     * Thus can only replace the argv[0] value. Because length
     * is restricted, need to truncate display name if too long.
     */

#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
    setproctitle("%s", display_name);
#else
    argv0 = (char*)wsgi_server->process->argv[0];

    dlen = strlen(argv0);
    slen = strlen(display_name);

    memset(argv0, ' ', dlen);

    if (slen < dlen)
        memcpy(argv0, display_name, slen);
    else
        memcpy(argv0, display_name, dlen);
#endif
}

static int wsgi_setup_access(WSGIDaemonProcess *daemon)
{
    /* Change to chroot environment. */

    if (daemon->group->root) {
        if (chroot(daemon->group->root) == -1) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                         "mod_wsgi (pid=%d): Unable to change root "
                         "directory to '%s'.", getpid(), daemon->group->root);

            return -1;
        }
    }

    /* We don't need to switch user/group if not root. */

    if (geteuid() == 0) {
        /* Setup the daemon process real and effective group. */

        if (setgid(daemon->group->gid) == -1) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                         "mod_wsgi (pid=%d): Unable to set group id "
                         "to gid=%u.", getpid(),
                         (unsigned)daemon->group->gid);

            return -1;
        }
        else {
            if (daemon->group->groups) {
                if (setgroups(daemon->group->groups_count,
                              daemon->group->groups) == -1) {
                    ap_log_error(APLOG_MARK, APLOG_ALERT, errno,
                                 wsgi_server, "mod_wsgi (pid=%d): Unable "
                                 "to set supplementary groups for uname=%s "
                                 "of '%s'.", getpid(), daemon->group->user,
                                 daemon->group->groups_list);

                    return -1;
                }
            }
            else if (initgroups(daemon->group->user,
                     daemon->group->gid) == -1) {
                ap_log_error(APLOG_MARK, APLOG_ALERT, errno,
                             wsgi_server, "mod_wsgi (pid=%d): Unable "
                             "to set groups for uname=%s and gid=%u.",
                             getpid(), daemon->group->user,
                             (unsigned)daemon->group->gid);

                return -1;
            }
        }

        /* Setup the daemon process real and effective user. */

        if (setuid(daemon->group->uid) == -1) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                         "mod_wsgi (pid=%d): Unable to change to uid=%ld.",
                         getpid(), (long)daemon->group->uid);

            /*
             * On true UNIX systems this should always succeed at
             * this point. With certain Linux kernel versions though
             * we can get back EAGAIN where the target user had
             * reached their process limit. In that case will be left
             * running as wrong user. Just exit on all failures to be
             * safe. Don't die immediately to avoid a fork bomb.
             *
             * We could just return -1 here and let the caller do the
             * sleep() and exit() but this failure is critical enough
             * that we still do it here so it is obvious that the issue
             * is being addressed.
             */

            ap_log_error(APLOG_MARK, APLOG_ALERT, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Failure to configure the "
                         "daemon process correctly and process left in "
                         "unspecified state. Restarting daemon process "
                         "after delay.", getpid());

            sleep(20);

            wsgi_exit_daemon_process(-1);

            return -1;
        }
    }

    /*
     * Setup the working directory for the process. It is either set to
     * what the 'home' option explicitly provides, or the home home
     * directory of the user, where it has been set to be different to
     * the user that Apache's own processes run as.
     */

    if (daemon->group->home) {
        if (chdir(daemon->group->home) == -1) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                         "mod_wsgi (pid=%d): Unable to change working "
                         "directory to '%s'.", getpid(), daemon->group->home);

            return -1;
        }
    }
    else if (geteuid() != ap_unixd_config.user_id) {
        struct passwd *pwent;

        pwent = getpwuid(geteuid());

        if (pwent) {
            if (chdir(pwent->pw_dir) == -1) {
                ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                             "mod_wsgi (pid=%d): Unable to change working "
                             "directory to home directory '%s' for uid=%ld.",
                             getpid(), pwent->pw_dir, (long)geteuid());

            return -1;
            }
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                         "mod_wsgi (pid=%d): Unable to determine home "
                         "directory for uid=%ld.", getpid(), (long)geteuid());

            return -1;
        }
    }

    /* Setup the umask for the effective user. */

    if (daemon->group->umask != -1)
        umask(daemon->group->umask);

    /*
     * Linux prevents generation of core dumps after setuid()
     * has been used. Attempt to reenable ability to dump core
     * so that the CoreDumpDirectory directive still works.
     */

#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
    /* This applies to Linux 2.4 and later. */

    if (ap_coredumpdir_configured) {
        if (prctl(PR_SET_DUMPABLE, 1)) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                    "mod_wsgi (pid=%d): Set dumpable failed. This child "
                    "will not coredump after software errors.", getpid());
        }
    }
#endif

    return 0;
}

static int wsgi_setup_socket(WSGIProcessGroup *process)
{
    int sockfd = -1;
    struct sockaddr_un addr;
    mode_t omask;
    int rc;

    int sendsz = process->send_buffer_size;
    int recvsz = process->recv_buffer_size;

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                 "mod_wsgi (pid=%d): Socket for '%s' is '%s'.",
                 getpid(), process->name, process->socket_path);

    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                     "mod_wsgi (pid=%d): Couldn't create unix domain "
                     "socket.", getpid());
        return -1;
    }

#ifdef SO_SNDBUF
    if (sendsz) {
        if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF,
                       (void *)&sendsz, sizeof(sendsz)) == -1) {
            ap_log_error(APLOG_MARK, APLOG_WARNING, errno, wsgi_server,
                         "mod_wsgi (pid=%d): Failed to set send buffer "
                         "size on daemon process socket.", getpid());
        }
    }
#endif
#ifdef SO_RCVBUF
    if (recvsz) {
        if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF,
                       (void *)&recvsz, sizeof(recvsz)) == -1) {
            ap_log_error(APLOG_MARK, APLOG_WARNING, errno, wsgi_server,
                         "mod_wsgi (pid=%d): Failed to set receive buffer "
                         "size on daemon process socket.", getpid());
        }
    }
#endif

    if (strlen(process->socket_path) > sizeof(addr.sun_path)) {
        ap_log_error(APLOG_MARK, APLOG_ALERT, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Length of path for daemon process "
                     "socket exceeds maxmimum allowed value and will be "
                     "truncated, resulting in likely failure to bind the "
                     "socket, or other later related failure.", getpid());
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    apr_cpystrn(addr.sun_path, process->socket_path, sizeof(addr.sun_path));

    omask = umask(0077);
    rc = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));

    if (rc < 0 && errno == EADDRINUSE) {
        ap_log_error(APLOG_MARK, APLOG_WARNING, errno, wsgi_server,
                     "mod_wsgi (pid=%d): Removing stale unix domain "
                     "socket '%s'.", getpid(), process->socket_path);

        unlink(process->socket_path);

        rc = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    }

    umask(omask);

    if (rc < 0) {
        ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                     "mod_wsgi (pid=%d): Couldn't bind unix domain "
                     "socket '%s'.", getpid(), process->socket_path);

        close(sockfd);

        return -1;
    }

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                 "mod_wsgi (pid=%d): Listen backlog for socket '%s' is '%d'.",
                 getpid(), process->socket_path, process->listen_backlog);

    if (listen(sockfd, process->listen_backlog) < 0) {
        ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                     "mod_wsgi (pid=%d): Couldn't listen on unix domain "
                     "socket.", getpid());

        close(sockfd);

        return -1;
    }

    /*
     * Set the ownership of the UNIX listener socket. This would
     * normally be the Apache user that the Apache server child
     * processes run as, as they are the only processes that
     * would connect to the sockets. In the case of ITK MPM,
     * having them owned by Apache user is useless as at the
     * time the request is to be proxied, the Apache server
     * child process will have uid corresponding to the user
     * whose request they are handling. For ITK, thus set the
     * ownership to be the same as the daemon processes. This is
     * still restrictive, in that can only connect to daemon
     * process group running under same user, but most of the
     * time that is what you would want anyway when using ITK
     * MPM.
     */

    if (!geteuid()) {
#if defined(MPM_ITK) || defined(ITK_MPM)
        uid_t socket_uid = process->uid;
#else
        uid_t socket_uid = ap_unixd_config.user_id;
#endif

        if (process->socket_user)
            socket_uid = ap_uname2id(process->socket_user);

        if (chown(process->socket_path, socket_uid, -1) < 0) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                         "mod_wsgi (pid=%d): Couldn't change owner of unix "
                         "domain socket '%s' to uid=%ld.", getpid(),
                         process->socket_path, (long)socket_uid);

            close(sockfd);

            return -1;
        }
    }

    return sockfd;
}

static int wsgi_hook_daemon_handler(conn_rec *c);

static void wsgi_process_socket(apr_pool_t *p, apr_socket_t *sock,
                                apr_bucket_alloc_t *bucket_alloc,
                                WSGIDaemonProcess *daemon)
{
    apr_status_t rv;

    conn_rec *c;
    ap_sb_handle_t *sbh;
    core_net_rec *net;

    /*
     * This duplicates Apache connection setup. This is done
     * here rather than letting Apache do it so that avoid the
     * possibility that any Apache modules, such as mod_ssl
     * will add their own input/output filters to the chain.
     */

#if AP_MODULE_MAGIC_AT_LEAST(20110619,0)
    /* For 2.4 a NULL sbh pointer should work. */
    sbh = NULL;
#else
    /* For 2.2 a dummy sbh pointer is needed. */
    ap_create_sb_handle(&sbh, p, -1, 0);
#endif

    c = (conn_rec *)apr_pcalloc(p, sizeof(conn_rec));

    c->sbh = sbh;

    c->conn_config = ap_create_conn_config(p);
    c->notes = apr_table_make(p, 5);
    c->pool = p;

    if ((rv = apr_socket_addr_get(&c->local_addr, APR_LOCAL, sock))
        != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_INFO, rv, wsgi_server,
                     "mod_wsgi (pid=%d): Failed call "
                     "apr_socket_addr_get(APR_LOCAL).", getpid());
        apr_socket_close(sock);
        return;
    }
    apr_sockaddr_ip_get(&c->local_ip, c->local_addr);

#if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
    if ((rv = apr_socket_addr_get(&c->client_addr, APR_REMOTE, sock))
        != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_INFO, rv, wsgi_server,
                     "mod_wsgi (pid=%d): Failed call "
                     "apr_socket_addr_get(APR_REMOTE).", getpid());
        apr_socket_close(sock);
        return;
    }
    c->client_ip = "unknown";
#else
    if ((rv = apr_socket_addr_get(&c->remote_addr, APR_REMOTE, sock))
        != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_INFO, rv, wsgi_server,
                     "mod_wsgi (pid=%d): Failed call "
                     "apr_socket_addr_get(APR_REMOTE).", getpid());
        apr_socket_close(sock);
        return;
    }
    c->remote_ip = "unknown";
#endif

    c->base_server = daemon->group->server;

    c->bucket_alloc = bucket_alloc;
    c->id = 1;

    net = apr_palloc(c->pool, sizeof(core_net_rec));

    if (daemon->group->socket_timeout)
        rv = apr_socket_timeout_set(sock, daemon->group->socket_timeout);
    else
        rv = apr_socket_timeout_set(sock, c->base_server->timeout);

    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, wsgi_server,
                      "mod_wsgi (pid=%d): Failed call "
                      "apr_socket_timeout_set().", getpid());
    }

    net->c = c;
    net->in_ctx = NULL;
    net->out_ctx = NULL;
    net->client_socket = sock;

    ap_set_module_config(net->c->conn_config, &core_module, sock);
    ap_add_input_filter_handle(ap_core_input_filter_handle,
                               net, NULL, net->c);
    ap_add_output_filter_handle(ap_core_output_filter_handle,
                                net, NULL, net->c);

    wsgi_hook_daemon_handler(c);

    ap_lingering_close(c);
}

static apr_status_t wsgi_worker_acquire(int id)
{
    WSGIThreadStack *stack = wsgi_worker_stack;
    WSGIDaemonThread *thread = &wsgi_worker_threads[id];

    while (1) {
        apr_uint32_t state = stack->state;
        if (state & (WSGI_STACK_TERMINATED | WSGI_STACK_NO_LISTENER)) {
            if (state & WSGI_STACK_TERMINATED) {
                return APR_EINVAL;
            }
            if (apr_atomic_cas32(&(stack->state), WSGI_STACK_LAST, state) !=
                state) {
                continue;
            }
            else {
                return APR_SUCCESS;
            }
        }
        thread->next = state;
        if (apr_atomic_cas32(&(stack->state), (unsigned)id, state) != state) {
            continue;
        }
        else {
            apr_status_t rv;

            if (thread->wakeup) {
                thread->wakeup = 0;

                return APR_SUCCESS;
            }

            rv = apr_thread_cond_wait(thread->condition, thread->mutex);

            while (rv == APR_SUCCESS && !thread->wakeup)
                rv = apr_thread_cond_wait(thread->condition, thread->mutex);

            if (rv != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, rv,
                             wsgi_server, "mod_wsgi (pid=%d): "
                             "Wait on thread %d wakeup condition variable "
                             "failed.", getpid(), id);
            }

            thread->wakeup = 0;

            return rv;
        }
    }
}

static apr_status_t wsgi_worker_release(void)
{
    WSGIThreadStack *stack = wsgi_worker_stack;

    while (1) {
        apr_uint32_t state = stack->state;
        unsigned int first = state & WSGI_STACK_HEAD;
        if (first == WSGI_STACK_LAST) {
            if (apr_atomic_cas32(&(stack->state),
                                 state | WSGI_STACK_NO_LISTENER,
                                 state) != state) {
                continue;
            }
            else {
                return APR_SUCCESS;
            }
        }
        else {
            WSGIDaemonThread *thread = &wsgi_worker_threads[first];
            if (apr_atomic_cas32(&(stack->state),
                                 (state ^ first) | thread->next,
                                 state) != state) {
                continue;
            }
            else {
                /*
                 * Flag that thread should be woken up and then
                 * signal it via the condition variable.
                 */

                apr_status_t rv;
                if ((rv = apr_thread_mutex_lock(thread->mutex)) !=
                    APR_SUCCESS) {
                    return rv;
                }

                thread->wakeup = 1;

                if ((rv = apr_thread_mutex_unlock(thread->mutex)) !=
                    APR_SUCCESS) {
                    return rv;
                }

                return apr_thread_cond_signal(thread->condition);
            }
        }
    }
}

static apr_status_t wsgi_worker_shutdown(void)
{
    int i;
    apr_status_t rv;
    WSGIThreadStack *stack = wsgi_worker_stack;

    while (1) {
        apr_uint32_t state = stack->state;
        if (apr_atomic_cas32(&(stack->state), state | WSGI_STACK_TERMINATED,
                           state) == state) {
            break;
        }
    }
    for (i = 0; i < wsgi_daemon_process->group->threads; i++) {
        if ((rv = wsgi_worker_release()) != APR_SUCCESS) {
            return rv;
        }
    }
    return APR_SUCCESS;
}

static void wsgi_daemon_worker(apr_pool_t *p, WSGIDaemonThread *thread)
{
    apr_status_t status;
    apr_socket_t *socket;

    apr_pool_t *ptrans;

    apr_pollset_t *pollset;
    apr_pollfd_t pfd = { 0 };
    apr_int32_t numdesc;
    const apr_pollfd_t *pdesc;

    apr_bucket_alloc_t *bucket_alloc;

    WSGIDaemonProcess *daemon = thread->process;
    WSGIProcessGroup *group = daemon->group;

    /* Loop until signal received to shutdown daemon process. */

    while (!wsgi_daemon_shutdown) {
        apr_status_t rv;

        /*
         * Only allow one thread in this process to attempt to
         * acquire the global process lock as the global process
         * lock will actually allow all threads in this process
         * through once one in this process acquires lock. Only
         * allowing one means better chance of another process
         * subsequently getting it thereby distributing requests
         * across processes better and reducing chance of Python
         * GIL contention.
         */

        wsgi_worker_acquire(thread->id);

        if (wsgi_daemon_shutdown)
            break;

        if (group->mutex) {
            /*
             * Grab the accept mutex across all daemon processes
             * in this process group.
             */

            rv = apr_proc_mutex_lock(group->mutex);

            if (rv != APR_SUCCESS) {
#if 0
#if defined(EIDRM)
                /*
                 * When using multiple threads locking the
                 * process accept mutex fails with an EIDRM when
                 * process being shutdown but signal check
                 * hasn't triggered quick enough to set shutdown
                 * flag. This causes lots of error messages to
                 * be logged which make it look like something
                 * nasty has happened even when it hasn't. For
                 * now assume that if multiple threads and EIDRM
                 * occurs that it is okay and the process is
                 * being shutdown. The condition should by
                 * rights only occur when the Apache parent
                 * process is being shutdown or has died for
                 * some reason so daemon process would logically
                 * therefore also be in process of being
                 * shutdown or killed.
                 */
                if (!strcmp(apr_proc_mutex_name(group->mutex), "sysvsem")) {
                    if (errno == EIDRM && group->threads > 1)
                        wsgi_daemon_shutdown = 1;
                }
#endif
#endif

                if (!wsgi_daemon_shutdown) {
                    ap_log_error(APLOG_MARK, APLOG_CRIT, rv,
                                 wsgi_server, "mod_wsgi (pid=%d): "
                                 "Couldn't acquire accept mutex '%s'. "
                                 "Shutting down daemon process.",
                                 getpid(), group->socket_path);

                    wsgi_daemon_shutdown++;
                    kill(getpid(), SIGTERM);
                    sleep(5);
                }

                break;
            }

            /*
             * Daemon process being shutdown so don't accept the
             * connection after all.
             */

            if (wsgi_daemon_shutdown) {
                apr_proc_mutex_unlock(group->mutex);

                wsgi_worker_release();

                break;
            }
        }

        apr_pool_create(&ptrans, p);

        /*
         * Accept socket connection from the child process. We
         * test the socket for whether it is ready before actually
         * performing the accept() so that can know for sure that
         * we will be processing a request and flag thread as
         * running. Only bother to do join with thread which is
         * actually running when process is being shutdown.
         */

        apr_pollset_create(&pollset, 1, ptrans, 0);

        memset(&pfd, '\0', sizeof(pfd));
        pfd.desc_type = APR_POLL_SOCKET;
        pfd.desc.s = daemon->listener;
        pfd.reqevents = APR_POLLIN;
        pfd.client_data = daemon;

        apr_pollset_add(pollset, &pfd);

        rv = apr_pollset_poll(pollset, -1, &numdesc, &pdesc);

        if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
            ap_log_error(APLOG_MARK, APLOG_CRIT, rv,
                         wsgi_server, "mod_wsgi (pid=%d): "
                         "Unable to poll daemon socket for '%s'. "
                         "Shutting down daemon process.",
                         getpid(), group->socket_path);

            wsgi_daemon_shutdown++;
            kill(getpid(), SIGTERM);
            sleep(5);

            break;
        }

        if (wsgi_daemon_shutdown) {
            if (group->mutex)
                apr_proc_mutex_unlock(group->mutex);

            wsgi_worker_release();

            apr_pool_destroy(ptrans);

            break;
        }

        if (rv != APR_SUCCESS && APR_STATUS_IS_EINTR(rv)) {
            if (group->mutex)
                apr_proc_mutex_unlock(group->mutex);

            wsgi_worker_release();

            apr_pool_destroy(ptrans);

            continue;
        }

        thread->running = 1;

        status = apr_socket_accept(&socket, daemon->listener, ptrans);

        if (group->mutex) {
            apr_status_t rv;
            rv = apr_proc_mutex_unlock(group->mutex);

            if (rv != APR_SUCCESS) {
                if (!wsgi_daemon_shutdown) {
                    wsgi_worker_release();

                    ap_log_error(APLOG_MARK, APLOG_CRIT, rv,
                                 wsgi_server, "mod_wsgi (pid=%d): "
                                 "Couldn't release accept mutex '%s'.",
                                 getpid(), group->socket_path);

                    apr_pool_destroy(ptrans);
                    thread->running = 0;

                    break;
                }
            }
        }

        wsgi_worker_release();

        if (status != APR_SUCCESS && APR_STATUS_IS_EINTR(status)) {
            apr_pool_destroy(ptrans);
            thread->running = 0;

            continue;
        }

        /* Process the request proxied from the child process. */

        apr_thread_mutex_lock(wsgi_monitor_lock);
        thread->request = apr_time_now();
        apr_thread_mutex_unlock(wsgi_monitor_lock);

        bucket_alloc = apr_bucket_alloc_create(ptrans);
        wsgi_process_socket(ptrans, socket, bucket_alloc, daemon);

        apr_thread_mutex_lock(wsgi_monitor_lock);
        thread->request = 0;
        apr_thread_mutex_unlock(wsgi_monitor_lock);

        /* Cleanup ready for next request. */

        apr_pool_destroy(ptrans);

        thread->running = 0;

        /* Check to see if maximum number of requests reached. */

        if (daemon->group->maximum_requests) {
            if (--wsgi_request_count <= 0) {
                if (wsgi_graceful_timeout && wsgi_active_requests) {
                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): Maximum requests "
                                 "reached, attempt a graceful shutdown "
                                 "'%s'.", getpid(), daemon->group->name);

                    apr_thread_mutex_lock(wsgi_monitor_lock);
                    wsgi_graceful_shutdown_time = apr_time_now();
                    wsgi_graceful_shutdown_time += wsgi_graceful_timeout;
                    apr_thread_mutex_unlock(wsgi_monitor_lock);
                }
                else {
                    if (!wsgi_daemon_shutdown) {
                        ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                     "mod_wsgi (pid=%d): Maximum requests "
                                     "reached, triggering immediate shutdown "
                                     "'%s'.", getpid(), daemon->group->name);
                    }

                    wsgi_daemon_shutdown++;
                    kill(getpid(), SIGINT);
                }
            }
        }

        /* Check if graceful shutdown and no active requests. */

        if (wsgi_daemon_graceful && !wsgi_daemon_shutdown) {
            if (wsgi_active_requests == 0) {
                ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                             "mod_wsgi (pid=%d): Requests have completed, "
                             "triggering immediate shutdown '%s'.",
                             getpid(), daemon->group->name);

                wsgi_daemon_shutdown++;
                kill(getpid(), SIGINT);
            }
        }
    }

    if (wsgi_server_config->verbose_debugging) {
        ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Exiting thread %d in daemon "
                     "process '%s'.", getpid(), thread->id,
                     thread->process->group->name);
    }
    else {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Exiting thread %d in daemon "
                     "process '%s'.", getpid(), thread->id,
                     thread->process->group->name);
    }
}

static void *wsgi_daemon_thread(apr_thread_t *thd, void *data)
{
    WSGIDaemonThread *thread = data;
    apr_pool_t *p = apr_thread_pool_get(thd);

    if (wsgi_server_config->verbose_debugging) {
      ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                   "mod_wsgi (pid=%d): Started thread %d in daemon "
                   "process '%s'.", getpid(), thread->id,
                   thread->process->group->name);
    }
    else {
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                   "mod_wsgi (pid=%d): Started thread %d in daemon "
                   "process '%s'.", getpid(), thread->id,
                   thread->process->group->name);
    }

    apr_thread_mutex_lock(thread->mutex);

    wsgi_daemon_worker(p, thread);

    apr_thread_exit(thd, APR_SUCCESS);

    return NULL;
}

static void *wsgi_reaper_thread(apr_thread_t *thd, void *data)
{
    WSGIDaemonProcess *daemon = data;

    sleep(daemon->group->shutdown_timeout);

    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                 "mod_wsgi (pid=%d): Aborting process '%s'.",
                 getpid(), daemon->group->name);

    wsgi_exit_daemon_process(-1);

    return NULL;
}

static void *wsgi_deadlock_thread(apr_thread_t *thd, void *data)
{
    WSGIDaemonProcess *daemon = data;

    PyGILState_STATE gilstate;

    if (wsgi_server_config->verbose_debugging) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Enable deadlock thread in "
                     "process '%s'.", getpid(), daemon->group->name);
    }

    apr_thread_mutex_lock(wsgi_monitor_lock);
    wsgi_deadlock_shutdown_time = apr_time_now();
    wsgi_deadlock_shutdown_time += wsgi_deadlock_timeout;
    apr_thread_mutex_unlock(wsgi_monitor_lock);

    while (1) {
        apr_sleep(apr_time_from_sec(1));

        apr_thread_mutex_lock(wsgi_shutdown_lock);

        if (!wsgi_daemon_shutdown) {
            gilstate = PyGILState_Ensure();
            PyGILState_Release(gilstate);
        }

        apr_thread_mutex_unlock(wsgi_shutdown_lock);

        apr_thread_mutex_lock(wsgi_monitor_lock);
        wsgi_deadlock_shutdown_time = apr_time_now();
        wsgi_deadlock_shutdown_time += wsgi_deadlock_timeout;
        apr_thread_mutex_unlock(wsgi_monitor_lock);
    }

    return NULL;
}

static void *wsgi_monitor_thread(apr_thread_t *thd, void *data)
{
    WSGIDaemonProcess *daemon = data;
    WSGIProcessGroup *group = daemon->group;

    int restart = 0;

    if (wsgi_server_config->verbose_debugging) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Enable monitor thread in "
                     "process '%s'.", getpid(), group->name);

        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Startup timeout is %d.",
                     getpid(), (int)(apr_time_sec(wsgi_startup_timeout)));
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Deadlock timeout is %d.",
                     getpid(), (int)(apr_time_sec(wsgi_deadlock_timeout)));
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Idle inactivity timeout is %d.",
                     getpid(), (int)(apr_time_sec(wsgi_idle_timeout)));
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Request time limit is %d.",
                     getpid(), (int)(apr_time_sec(wsgi_request_timeout)));
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Graceful timeout is %d.",
                     getpid(), (int)(apr_time_sec(wsgi_graceful_timeout)));
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Eviction timeout is %d.",
                     getpid(), (int)(apr_time_sec(wsgi_eviction_timeout)));
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Restart interval is %d.",
                     getpid(), (int)(apr_time_sec(wsgi_restart_interval)));
    }

    /*
     * If a restart interval was specified then set up the time for
     * when the restart should occur.
     */

    if (wsgi_restart_interval) {
        wsgi_restart_shutdown_time = apr_time_now();
        wsgi_restart_shutdown_time += wsgi_restart_interval;
    }

    while (1) {
        apr_time_t now;

        apr_time_t startup_time;
        apr_time_t deadlock_time;
        apr_time_t idle_time;
        apr_time_t graceful_time;
        apr_time_t restart_time;

        apr_time_t request_time = 0;

        apr_interval_time_t period = 0;

        int i = 0;

        now = apr_time_now();

        apr_thread_mutex_lock(wsgi_monitor_lock);

        startup_time = wsgi_startup_shutdown_time;
        deadlock_time = wsgi_deadlock_shutdown_time;
        idle_time = wsgi_idle_shutdown_time;
        graceful_time = wsgi_graceful_shutdown_time;
        restart_time = wsgi_restart_shutdown_time;

        if (wsgi_request_timeout && wsgi_worker_threads) {
            for (i = 0; i<wsgi_daemon_process->group->threads; i++) {
                if (wsgi_worker_threads[i].request)
                    request_time += (now - wsgi_worker_threads[i].request);
            }
        }

        request_time /= wsgi_daemon_process->group->threads;

        apr_thread_mutex_unlock(wsgi_monitor_lock);

        if (!restart && wsgi_request_timeout) {
            if (request_time > wsgi_request_timeout) {
                ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                             "mod_wsgi (pid=%d): Daemon process request "
                             "time limit exceeded, stopping process "
                             "'%s'.", getpid(), group->name);

                wsgi_shutdown_reason = "request_timeout";

                wsgi_dump_stack_traces = 1;

                restart = 1;
            }
        }

        if (!restart && wsgi_startup_timeout) {
            if (startup_time > 0) {
                if (startup_time <= now) {
                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): Application startup "
                                 "timer expired, stopping process '%s'.",
                                 getpid(), group->name);

                    wsgi_shutdown_reason = "startup_timeout";

                    restart = 1;
                }
                else {
                    period = startup_time - now;
                }
            }
        }

        if (!restart && wsgi_restart_interval) {
            if (restart_time > 0) {
                if (restart_time <= now) {
                    if (!wsgi_daemon_graceful) {
                        if (wsgi_active_requests) {
                            wsgi_daemon_graceful++;

                            apr_thread_mutex_lock(wsgi_monitor_lock);
                            wsgi_graceful_shutdown_time = apr_time_now();
                            wsgi_graceful_shutdown_time += wsgi_graceful_timeout;
                            apr_thread_mutex_unlock(wsgi_monitor_lock);

                            ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                                         wsgi_server, "mod_wsgi (pid=%d): "
                                         "Application restart timer expired, "
                                         "waiting for requests to complete "
                                         "'%s'.", getpid(),
                                         daemon->group->name);
                        }
                        else {
                            ap_log_error(APLOG_MARK, APLOG_INFO, 0,
                                         wsgi_server, "mod_wsgi (pid=%d): "
                                         "Application restart timer expired, "
                                         "stopping process '%s'.", getpid(),
                                         daemon->group->name);

                            wsgi_shutdown_reason = "restart_interval";

                            restart = 1;
                        }
                    }
                }
                else {
                    if (!period || ((restart_time - now) < period))
                        period = restart_time - now;
                }
            }
        }

        if (!restart && wsgi_deadlock_timeout) {
            if (deadlock_time) {
                if (deadlock_time <= now) {
                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): Daemon process deadlock "
                                 "timer expired, stopping process '%s'.",
                                 getpid(), group->name);

                    restart = 1;
                }
                else {
                    if (!period || ((deadlock_time - now) < period))
                        period = deadlock_time - now;
                }
            }
            else {
                if (!period || (wsgi_deadlock_timeout < period))
                    period = wsgi_deadlock_timeout;
            }
        }

        if (!restart && wsgi_idle_timeout) {
            if (idle_time) {
                if (idle_time <= now) {
                    if (wsgi_active_requests == 0) {
                        ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                     "mod_wsgi (pid=%d): Daemon process "
                                     "idle inactivity timer expired, "
                                     "stopping process '%s'.", getpid(),
                                     group->name);

                        wsgi_shutdown_reason = "inactivity_timeout";

                        restart = 1;
                    }
                    else {
                        /* Ignore for now as still have requests. */

                        if (!period || (wsgi_idle_timeout < period))
                            period = wsgi_idle_timeout;
                    }
                }
                else {
                    if (!period || ((idle_time - now) < period))
                        period = idle_time - now;
                }
            }
            else {
                if (!period || (wsgi_idle_timeout < period))
                    period = wsgi_idle_timeout;
            }
        }

        if (!restart && wsgi_graceful_timeout) {
            if (graceful_time) {
                if (graceful_time <= now) {
                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): Daemon process "
                                 "graceful timer expired '%s'.", getpid(),
                                 group->name);

                    restart = 1;
                }
                else {
                    if (!period || ((graceful_time - now) < period))
                        period = graceful_time - now;
                    else if (wsgi_graceful_timeout < period)
                        period = wsgi_graceful_timeout;
                }
            }
            else {
                if (!period || (wsgi_graceful_timeout < period))
                    period = wsgi_graceful_timeout;
            }
        }

        if (!restart && wsgi_eviction_timeout) {
            if (graceful_time) {
                if (graceful_time <= now) {
                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): Daemon process "
                                 "graceful timer expired '%s'.", getpid(),
                                 group->name);

                    restart = 1;
                }
                else {
                    if (!period || ((graceful_time - now) < period))
                        period = graceful_time - now;
                    else if (wsgi_eviction_timeout < period)
                        period = wsgi_eviction_timeout;
                }
            }
            else {
                if (!period || (wsgi_eviction_timeout < period))
                    period = wsgi_eviction_timeout;
            }
        }

        if (restart) {
            wsgi_daemon_shutdown++;
            kill(getpid(), SIGINT);
        }

        if (restart || wsgi_request_timeout || period <= 0 ||
            (wsgi_startup_timeout && !wsgi_startup_shutdown_time)) {
            period = apr_time_from_sec(1);
        }

        apr_sleep(period);
    }

    return NULL;
}

#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 5)
static void wsgi_log_stack_traces(void)
{
    PyGILState_STATE state;

    PyObject *threads = NULL;

    /*
     * This should only be called on shutdown so don't try and log
     * any errors, just dump them straight out.
     */

    state = PyGILState_Ensure();

    threads = _PyThread_CurrentFrames();

    if (threads && PyDict_Size(threads) != 0) {
        PyObject *seq = NULL;

        seq = PyObject_GetIter(threads);

        if (seq) {
            PyObject *id = NULL;
            PyObject *frame = NULL;

            Py_ssize_t i = 0;

            ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                          "mod_wsgi (pid=%d): Dumping stack trace for "
                          "active Python threads.", getpid());

            while (PyDict_Next(threads, &i, &id, &frame)) {
                apr_int64_t thread_id = 0;

                PyFrameObject *current = NULL;

                thread_id = PyLong_AsLong(id);

                current = (PyFrameObject *)frame;

                while (current) {
                    int lineno;

                    const char *filename = NULL;
                    const char *name = NULL;

#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 9)
                    lineno = PyFrame_GetLineNumber(current);
#else
                    if (current->f_trace) {
                        lineno = current->f_lineno;
                    }
                    else {
                        lineno = PyCode_Addr2Line(current->f_code,
                                                  current->f_lasti);
                    }
#endif

#if PY_MAJOR_VERSION >= 3
#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 9)
                    filename = PyUnicode_AsUTF8(PyFrame_GetCode(current)->co_filename);
                    name = PyUnicode_AsUTF8(PyFrame_GetCode(current)->co_name);
#else
                    filename = PyUnicode_AsUTF8(current->f_code->co_filename);
                    name = PyUnicode_AsUTF8(current->f_code->co_name);
#endif
#else
                    filename = PyString_AsString(current->f_code->co_filename);
                    name = PyString_AsString(current->f_code->co_name);
#endif

                    if (current == (PyFrameObject *)frame) {
                        ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                "mod_wsgi (pid=%d): Thread %" APR_INT64_T_FMT
                                " executing file \"%s\", line %d, in %s",
                                getpid(), thread_id, filename, lineno, name);
                    }
                    else {
#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 9)
                        if (PyFrame_GetBack(current)) {
#else
                        if (current->f_back) {
#endif
                            ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                    "mod_wsgi (pid=%d): called from file "
                                    "\"%s\", line %d, in %s,", getpid(),
                                    filename, lineno, name);
                        }
                        else {
                            ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                    "mod_wsgi (pid=%d): called from file "
                                    "\"%s\", line %d, in %s.", getpid(),
                                    filename, lineno, name);
                        }
                    }

#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 9)
                    current = PyFrame_GetBack(current);
#else
                    current = current->f_back;
#endif
                }
            }
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                          "mod_wsgi (pid=%d): Failed to iterate over "
                          "current frames for active threads.", getpid());

            PyErr_Print();
            PyErr_Clear();
        }
    }
    else {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                      "mod_wsgi (pid=%d): Failed to get current frames "
                      "for active threads.", getpid());

        PyErr_Print();
        PyErr_Clear();
    }

    Py_XDECREF(threads);

    PyGILState_Release(state);
}
#endif

static void wsgi_daemon_main(apr_pool_t *p, WSGIDaemonProcess *daemon)
{
    apr_threadattr_t *thread_attr;
    apr_thread_t *reaper = NULL;

    int i;
    apr_status_t rv;
    apr_status_t thread_rv;

    apr_pollfd_t poll_fd;
    apr_int32_t poll_count = 0;

    /*
     * Setup poll object for listening for shutdown notice from
     * signal handler.
     */

    poll_fd.desc_type = APR_POLL_FILE;
    poll_fd.reqevents = APR_POLLIN;
    poll_fd.desc.f = wsgi_signal_pipe_in;

    /* Initialise maximum request count for daemon. */

    if (daemon->group->maximum_requests)
        wsgi_request_count = daemon->group->maximum_requests;

    /* Ensure that threads are joinable. */

    apr_threadattr_create(&thread_attr, p);
    apr_threadattr_detach_set(thread_attr, 0);

#if (APR_MAJOR_VERSION >= 1)
    if (daemon->group->stack_size) {
        apr_threadattr_stacksize_set(thread_attr, daemon->group->stack_size);
    }
#endif

    /* Start monitoring thread if required. */

    wsgi_startup_timeout = daemon->group->startup_timeout;
    wsgi_deadlock_timeout = daemon->group->deadlock_timeout;
    wsgi_idle_timeout = daemon->group->inactivity_timeout;
    wsgi_request_timeout = daemon->group->request_timeout;
    wsgi_graceful_timeout = daemon->group->graceful_timeout;
    wsgi_eviction_timeout = daemon->group->eviction_timeout;
    wsgi_restart_interval = daemon->group->restart_interval;

    if (wsgi_deadlock_timeout || wsgi_idle_timeout) {
        rv = apr_thread_create(&reaper, thread_attr, wsgi_monitor_thread,
                               daemon, p);

        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server,
                         "mod_wsgi (pid=%d): Couldn't create monitor "
                         "thread in daemon process '%s'.", getpid(),
                         daemon->group->name);
        }
    }

    if (wsgi_deadlock_timeout) {
        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server,
                         "mod_wsgi (pid=%d): Couldn't create deadlock "
                         "thread in daemon process '%s'.", getpid(),
                         daemon->group->name);
        }

        rv = apr_thread_create(&reaper, thread_attr, wsgi_deadlock_thread,
                               daemon, p);
    }

    /* Initialise worker stack. */

    wsgi_worker_stack = (WSGIThreadStack *)apr_palloc(p,
            sizeof(WSGIThreadStack));
    wsgi_worker_stack->state = WSGI_STACK_NO_LISTENER | WSGI_STACK_LAST;

    /* Start the required number of threads. */

    wsgi_worker_threads = (WSGIDaemonThread *)apr_pcalloc(p,
                           daemon->group->threads * sizeof(WSGIDaemonThread));

    if (wsgi_server_config->verbose_debugging) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Starting %d threads in daemon "
                     "process '%s'.", getpid(), daemon->group->threads,
                     daemon->group->name);
    }

    for (i=0; i<daemon->group->threads; i++) {
        WSGIDaemonThread *thread = &wsgi_worker_threads[i];

        if (wsgi_server_config->verbose_debugging) {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Starting thread %d in daemon "
                         "process '%s'.", getpid(), i+1, daemon->group->name);
        }

        /* Create the mutex and condition variable for this thread. */

        rv = apr_thread_cond_create(&thread->condition, p);

        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server,
                         "mod_wsgi (pid=%d): Couldn't create worker "
                         "thread %d state condition variable in daemon "
                         "process '%s'.", getpid(), i, daemon->group->name);

            /*
             * Try to force an exit of the process if fail
             * to create the worker threads.
             */

            kill(getpid(), SIGTERM);
            sleep(5);
        }

        rv = apr_thread_mutex_create(&thread->mutex,
                                     APR_THREAD_MUTEX_DEFAULT, p);

        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server,
                         "mod_wsgi (pid=%d): Couldn't create worker "
                         "thread %d state mutex variable in daemon "
                         "process '%s'.", getpid(), i, daemon->group->name);

            /*
             * Try to force an exit of the process if fail
             * to create the worker threads.
             */

            kill(getpid(), SIGTERM);
            sleep(5);
        }

        /* Now create the actual thread. */

        thread->id = i;
        thread->process = daemon;
        thread->running = 0;
        thread->request = 0;

        rv = apr_thread_create(&thread->thread, thread_attr,
                               wsgi_daemon_thread, thread, p);

        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server,
                         "mod_wsgi (pid=%d): Couldn't create worker "
                         "thread %d in daemon process '%s'.", getpid(),
                         i, daemon->group->name);

            /*
             * Try to force an exit of the process if fail
             * to create the worker threads.
             */

            kill(getpid(), SIGTERM);
            sleep(5);
        }
    }

    /* Block until we get a process shutdown signal. */

    while (1) {
        char buf[1];
        apr_size_t nbytes = 1;

        rv = apr_poll(&poll_fd, 1, &poll_count, -1);
        if (APR_STATUS_IS_EINTR(rv))
            continue;

        rv = apr_file_read(wsgi_signal_pipe_in, buf, &nbytes);

        if (rv != APR_SUCCESS || nbytes != 1) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Failed read on signal pipe '%s'.",
                         getpid(), daemon->group->name);

            break;
        }

        if (buf[0] == 'C') {
            if (!wsgi_daemon_graceful) {
                wsgi_shutdown_reason = "cpu_time_limit";

                if (wsgi_active_requests) {
                    wsgi_daemon_graceful++;

                    apr_thread_mutex_lock(wsgi_monitor_lock);
                    wsgi_graceful_shutdown_time = apr_time_now();
                    wsgi_graceful_shutdown_time += wsgi_graceful_timeout;
                    apr_thread_mutex_unlock(wsgi_monitor_lock);

                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): Exceeded CPU time "
                                 "limit, waiting for requests to complete "
                                 "'%s'.", getpid(), daemon->group->name);
                }
                else {
                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): Exceeded CPU time "
                                 "limit, triggering immediate shutdown "
                                 "'%s'.", getpid(), daemon->group->name);

                    wsgi_daemon_shutdown++;
                    kill(getpid(), SIGINT);
                }
            }
        }
        else if (buf[0] == 'G') {
            if (!wsgi_daemon_graceful) {
                wsgi_shutdown_reason = "graceful_signal";

                if (wsgi_active_requests) {
                    wsgi_daemon_graceful++;

                    apr_thread_mutex_lock(wsgi_monitor_lock);
                    wsgi_graceful_shutdown_time = apr_time_now();
                    if (wsgi_eviction_timeout)
                        wsgi_graceful_shutdown_time += wsgi_eviction_timeout;
                    else
                        wsgi_graceful_shutdown_time += wsgi_graceful_timeout;
                    apr_thread_mutex_unlock(wsgi_monitor_lock);

                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): Process eviction "
                                 "requested, waiting for requests to complete "
                                 "'%s'.", getpid(), daemon->group->name);
                }
                else {
                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                                 "mod_wsgi (pid=%d): Process eviction "
                                 "requested, triggering immediate shutdown "
                                 "'%s'.", getpid(), daemon->group->name);

                    wsgi_daemon_shutdown++;
                    kill(getpid(), SIGINT);
                }
            }
        }
        else
            break;
    }

    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                 "mod_wsgi (pid=%d): Shutdown requested '%s'.",
                 getpid(), daemon->group->name);

    /*
     * Create a reaper thread to abort process if graceful
     * shutdown takes too long. Not recommended to disable
     * this unless external process is controlling shutdown.
     */

    if (daemon->group->shutdown_timeout) {
        rv = apr_thread_create(&reaper, thread_attr, wsgi_reaper_thread,
                               daemon, p);

        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server,
                         "mod_wsgi (pid=%d): Couldn't create reaper "
                         "thread in daemon process '%s'.", getpid(),
                         daemon->group->name);
        }
    }

    /*
     * If shutting down process due to reaching request time
     * limit, then try and dump out stack traces of any threads
     * which are running as a debugging aid.
     */

    wsgi_publish_process_stopping(wsgi_shutdown_reason);

#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 5)
    if (wsgi_dump_stack_traces)
        wsgi_log_stack_traces();
#endif

    /*
     * Attempt a graceful shutdown by waiting for any
     * threads which were processing a request at the time
     * of shutdown. In some respects this is a bit pointless
     * as even though we allow the requests to be completed,
     * the Apache child process which proxied the request
     * through to this daemon process could get killed off
     * before the daemon process and so the response gets
     * cut off or lost.
     */

    wsgi_worker_shutdown();

    for (i=0; i<daemon->group->threads; i++) {
        if (wsgi_worker_threads[i].thread && wsgi_worker_threads[i].running) {
            rv = apr_thread_join(&thread_rv, wsgi_worker_threads[i].thread);
            if (rv != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server,
                             "mod_wsgi (pid=%d): Couldn't join with "
                             "worker thread %d in daemon process '%s'.",
                             getpid(), i, daemon->group->name);
            }
        }
    }
}

static apr_status_t wsgi_cleanup_process(void *data)
{
    WSGIProcessGroup *group = (WSGIProcessGroup *)data;

    /* Only do cleanup if in Apache parent process. */

    if (wsgi_parent_pid != getpid())
        return APR_SUCCESS;

    if (group->listener_fd != -1) {
        if (close(group->listener_fd) < 0) {
            ap_log_error(APLOG_MARK, APLOG_ERR, errno,
                         wsgi_server, "mod_wsgi (pid=%d): "
                         "Couldn't close unix domain socket '%s'.",
                         getpid(), group->socket_path);
        }

        if (unlink(group->socket_path) < 0 && errno != ENOENT) {
            ap_log_error(APLOG_MARK, APLOG_ERR, errno,
                         wsgi_server, "mod_wsgi (pid=%d): "
                         "Couldn't unlink unix domain socket '%s'.",
                         getpid(), group->socket_path);
        }
    }

    return APR_SUCCESS;
}

static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
{
    apr_status_t status;

    ap_listen_rec *lr;

    WSGIProcessGroup *entries = NULL;
    WSGIProcessGroup *entry = NULL;
    int i = 0;

    if ((status = apr_proc_fork(&daemon->process, p)) < 0) {
        ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server,
                     "mod_wsgi: Couldn't spawn process '%s'.",
                     daemon->group->name);
        return DECLINED;
    }
    else if (status == APR_INCHILD) {
        if (!geteuid()) {
            ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Starting process '%s' with "
                         "uid=%ld, gid=%u and threads=%d.", getpid(),
                         daemon->group->name, (long)daemon->group->uid,
                         (unsigned)daemon->group->gid, daemon->group->threads);
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Starting process '%s' with "
                         "threads=%d.", getpid(), daemon->group->name,
                         daemon->group->threads);
        }

#ifdef HAVE_BINDPROCESSOR
        /*
         * By default, AIX binds to a single processor.  This
         * bit unbinds children which will then bind to another
         * CPU.
         */

        status = bindprocessor(BINDPROCESS, (int)getpid(),
                               PROCESSOR_CLASS_ANY);
        if (status != OK) {
            ap_log_error(APLOG_MARK, APLOG_ERR, errno, wsgi_server,
                         "mod_wsgi (pid=%d): Failed to unbind processor.",
                         getpid());
        }
#endif

        /* Setup daemon process name displayed by 'ps'. */

        wsgi_setup_daemon_name(daemon, p);

        /* Adjust CPU priority if overridden. */

        if (daemon->group->cpu_priority != 0) {
            if (setpriority(PRIO_PROCESS, 0,
                            daemon->group->cpu_priority) == -1) {
                ap_log_error(APLOG_MARK, APLOG_ERR, errno, wsgi_server,
                             "mod_wsgi (pid=%d): Couldn't set CPU priority "
                             "in daemon process '%d'.", getpid(),
                             daemon->group->cpu_priority);
            }
        }

        /* Setup daemon process user/group/umask etc. */

        if (wsgi_setup_access(daemon) == -1) {
            /*
             * If we get any failure from setting up the appropriate
             * permissions or working directory for the daemon process
             * then we exit the process. Don't die immediately to avoid
             * a fork bomb.
             */

            ap_log_error(APLOG_MARK, APLOG_ALERT, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Failure to configure the "
                         "daemon process correctly and process left in "
                         "unspecified state. Restarting daemon process "
                         "after delay.", getpid());

            sleep(20);

            wsgi_exit_daemon_process(-1);
        }

        /* Reinitialise accept mutex in daemon process. */

        if (daemon->group->mutex) {
            status = apr_proc_mutex_child_init(&daemon->group->mutex,
                                               daemon->group->mutex_path, p);

            if (status != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, 0, wsgi_server,
                             "mod_wsgi (pid=%d): Couldn't initialise accept "
                             "mutex in daemon process '%s'.",
                             getpid(), daemon->group->mutex_path);

                /* Don't die immediately to avoid a fork bomb. */

                sleep(20);

                wsgi_exit_daemon_process(-1);
            }
        }

        /*
         * Create a lookup table of listener socket address
         * details so can use it later in daemon when trying
         * to map request to correct virtual host server.
         */

        wsgi_daemon_listeners = apr_hash_make(p);

        for (lr = ap_listeners; lr; lr = lr->next) {
            char *key;
            char *host;
            apr_port_t port;

            host = lr->bind_addr->hostname;
            port = lr->bind_addr->port;

            if (!host)
                host = "";

            key = apr_psprintf(p, "%s|%d", host, port);

            apr_hash_set(wsgi_daemon_listeners, key, APR_HASH_KEY_STRING,
                         lr->bind_addr);
        }

        /*
         * Close child copy of the listening sockets for the
         * Apache parent process so we don't interfere with
         * the parent process.
         */

        ap_close_listeners();

        /*
         * Cleanup the Apache scoreboard to ensure that any
         * shared memory segments or memory mapped files not
         * available to code in daemon processes.
         */

        /*
         * XXX If this is closed, under Apache 2.4 then daemon
         * mode processes will crash. Not much choice but to
         * leave it open. Daemon mode really needs to be
         * rewritten not to use normal Apache request object and
         * output bucket chain to avoid potential for problems.
         */

#if 0
        ap_cleanup_scoreboard(0);
#endif

        /*
         * Wipe out random value used in magic token so that not
         * possible for user code running in daemon process to
         * discover this value for other daemon process groups.
         * In other words, wipe out all but our own.
         */

        entries = (WSGIProcessGroup *)wsgi_daemon_list->elts;

        for (i = 0; i < wsgi_daemon_list->nelts; ++i) {
            entry = &entries[i];

            if (entry != daemon->group)
                entry->random = 0;
        }

        /*
         * Close listener socket for daemon processes for other
         * daemon process groups. In other words, close all but
         * our own.
         */

        entries = (WSGIProcessGroup *)wsgi_daemon_list->elts;

        for (i = 0; i < wsgi_daemon_list->nelts; ++i) {
            entry = &entries[i];

            if (entry != daemon->group && entry->listener_fd != -1) {
                close(entry->listener_fd);
                entry->listener_fd = -1;
            }
        }

        /*
         * Register signal handler to receive shutdown signal
         * from Apache parent process. We need to first create
         * pipe by which signal handler can notify the main
         * thread that signal has arrived indicating that
         * process needs to shutdown.
         */

        status = apr_file_pipe_create(&wsgi_signal_pipe_in,
                                      &wsgi_signal_pipe_out, p);

        if (status != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_EMERG, status, wsgi_server,
                         "mod_wsgi (pid=%d): Couldn't initialise signal "
                         "pipe in daemon process '%s'.", getpid(),
                         daemon->group->name);

            /* Don't die immediately to avoid a fork bomb. */

            sleep(20);

            wsgi_exit_daemon_process(-1);
        }

        wsgi_daemon_shutdown = 0;

        wsgi_daemon_pid = getpid();

        apr_signal(SIGINT, wsgi_signal_handler);
        apr_signal(SIGTERM, wsgi_signal_handler);

        apr_signal(AP_SIG_GRACEFUL, wsgi_signal_handler);

#ifdef SIGXCPU
        apr_signal(SIGXCPU, wsgi_signal_handler);
#endif

        /* Set limits on amount of CPU time that can be used. */

        if (daemon->group->cpu_time_limit > 0) {
            struct rlimit limit;
            int result = -1;
            errno = ENOSYS;

            limit.rlim_cur = daemon->group->cpu_time_limit;

            limit.rlim_max = daemon->group->cpu_time_limit + 1;
            limit.rlim_max += daemon->group->shutdown_timeout;

#if defined(RLIMIT_CPU)
            result = setrlimit(RLIMIT_CPU, &limit);
#endif

            if (result == -1) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server,
                             "mod_wsgi (pid=%d): Couldn't set CPU time "
                             "limit of %d seconds for process '%s'.", getpid(),
                             daemon->group->cpu_time_limit,
                             daemon->group->name);
            }
        }

        /*
         * Set limits on amount of date segment memory that can
         * be used. Although this is done, some platforms
         * doesn't actually support it.
         */

        if (daemon->group->memory_limit > 0) {
            struct rlimit limit;
            int result = -1;
            errno = ENOSYS;

            limit.rlim_cur = daemon->group->memory_limit;

            limit.rlim_max = daemon->group->memory_limit;

#if defined(RLIMIT_DATA)
            result = setrlimit(RLIMIT_DATA, &limit);
#endif

            if (result == -1) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server,
                             "mod_wsgi (pid=%d): Couldn't set memory "
                             "limit of %ld for process '%s'.", getpid(),
                             (long)daemon->group->memory_limit,
                             daemon->group->name);
            }
        }

        /*
         * Set limits on amount of virtual memory that can be used.
         * Although this is done, some platforms doesn't actually
         * support it.
         */

        if (daemon->group->virtual_memory_limit > 0) {
            struct rlimit limit;
            int result = -1;
            errno = ENOSYS;

            limit.rlim_cur = daemon->group->virtual_memory_limit;

            limit.rlim_max = daemon->group->virtual_memory_limit;

#if defined(RLIMIT_AS)
            result = setrlimit(RLIMIT_AS, &limit);
#elif defined(RLIMIT_VMEM)
            result = setrlimit(RLIMIT_VMEM, &limit);
#endif

            if (result == -1) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server,
                             "mod_wsgi (pid=%d): Couldn't set virtual memory "
                             "limit of %ld for process '%s'.", getpid(),
                             (long)daemon->group->virtual_memory_limit,
                             daemon->group->name);
            }
        }

        /*
         * Flag whether multiple daemon processes or denoted
         * that requests could be spread across multiple daemon
         * process groups.
         */

        wsgi_multiprocess = daemon->group->multiprocess;
        wsgi_multithread = daemon->group->threads != 1;

        /*
         * Create a pool for the child daemon process so
         * we can trigger various events off it at shutdown.
         */

        apr_pool_create(&wsgi_daemon_pool, p);

        /*
         * Retain a reference to daemon process details. Do
         * this here as when doing lazy initialisation of
         * the interpreter we want to know if in a daemon
         * process so can pick any daemon process specific
         * home directory for Python installation.
         */

        wsgi_daemon_group = daemon->group->name;
        wsgi_daemon_process = daemon;

        /* Set lang/locale if specified for daemon process. */

        if (daemon->group->lang) {
            char *envvar;

            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Setting lang to %s for "
                         "daemon process group %s.", getpid(),
                         daemon->group->lang, daemon->group->name);

            envvar = apr_pstrcat(p, "LANG=", daemon->group->lang, NULL);
            putenv(envvar);
        }

        if (daemon->group->locale) {
            char *envvar;
            char *result;

            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Setting locale to %s for "
                         "daemon process group %s.", getpid(),
                         daemon->group->locale, daemon->group->name);

            envvar = apr_pstrcat(p, "LC_ALL=", daemon->group->locale, NULL);
            putenv(envvar);

            result = setlocale(LC_ALL, daemon->group->locale);

            if (!result) {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                             "mod_wsgi (pid=%d): Unsupported locale setting "
                             "%s specified for daemon process group %s. "
                             "Consider using 'C.UTF-8' as fallback setting.",
                             getpid(), daemon->group->locale,
                             daemon->group->name);
            }
        }

        /* Create lock for request monitoring. */

        apr_thread_mutex_create(&wsgi_monitor_lock,
                                APR_THREAD_MUTEX_UNNESTED, p);

        /*
         * Initialise Python if required to be done in the child
         * process. Note that it will not be initialised if
         * mod_python loaded and it has already been done.
         */

        if (wsgi_python_after_fork)
            wsgi_python_init(p);

#if PY_MAJOR_VERSION < 3
        /*
         * If mod_python is also being loaded and thus it was
         * responsible for initialising Python it can leave in
         * place an active thread state. Under normal conditions
         * this would be eliminated in Apache child process by
         * the time that mod_wsgi got to do its own child
         * initialisation but in daemon process we skip the
         * mod_python child initialisation so the active thread
         * state still exists. Thus need to do a bit of a fiddle
         * to ensure there is no active thread state. Don't need
         * to worry about this with Python 3.X as mod_python
         * only supports Python 2.X.
         */

        if (!wsgi_python_initialized) {
            PyGILState_STATE state;

            PyEval_AcquireLock();

            state = PyGILState_Ensure();
            PyGILState_Release(state);

            if (state == PyGILState_LOCKED)
                PyThreadState_Swap(NULL);

            PyEval_ReleaseLock();
        }
#endif

        /*
         * If the daemon is associated with a virtual host then
         * we can close all other error logs so long as they
         * aren't the same one as being used for the virtual
         * host. If the virtual host error log is different to
         * the main server error log, then also tie stderr to
         * that log file instead. This way any debugging sent
         * direct to stderr from C code also goes to the virtual
         * host error log. We close the error logs that aren't
         * required as that eliminates possibility that user
         * code executing in daemon process could maliciously
         * dump messages into error log for a different virtual
         * host, as well as stop them being reopened with mode
         * that would allow seeking back to start of file and
         * read any information in them.
         */

        if (daemon->group->server->is_virtual) {
            server_rec *server = NULL;
            apr_file_t *errfile = NULL;

            /*
             * Iterate over all servers and close any error
             * logs different to that for virtual host. Note that
             * if errors are being redirected to syslog, then
             * the server error log reference will actually be
             * a null pointer, so need to ensure that check for
             * that and don't attempt to close it in that case.
             */

            server = wsgi_server;

            while (server != NULL) {
                if (server->error_log &&
                    server->error_log != daemon->group->server->error_log) {
                    apr_file_close(server->error_log);
                }

                server = server->next;
            }

            /*
             * Reassociate stderr output with error log from the
             * virtual host the daemon is associated with. Close
             * the virtual host error log and point it at stderr
             * log instead. Do the latter so don't get two
             * references to same open file. Just in case
             * anything still accesses error log of main server,
             * map main server error log to that of the virtual
             * host. Note that cant do this if errors are being
             * redirected to syslog, as indicated by virtual
             * host error log being a null pointer. In that case
             * just leave everything as it was. Also can't remap
             * the error log for main server if it was being
             * redirected to syslog but virtual host wasn't.
             */

            if (daemon->group->server->error_log  &&
                daemon->group->server->error_log != wsgi_server->error_log) {

                apr_file_t *oldfile = NULL;

                apr_file_open_stderr(&errfile, wsgi_server->process->pool);
                apr_file_dup2(errfile, daemon->group->server->error_log,
                              wsgi_server->process->pool);

                oldfile = daemon->group->server->error_log;

                server = wsgi_server;

                while (server != NULL) {
                    if (server->error_log == oldfile)
                        server->error_log = errfile;
                    server = server->next;
                }

                apr_file_close(oldfile);

                if (wsgi_server->error_log)
                    wsgi_server->error_log = errfile;
            }
        }

        /*
         * Update reference to server object in case daemon
         * process is actually associated with a virtual host.
         * This way all logging actually goes into the virtual
         * hosts log file.
         */

        if (daemon->group->server) {
            if (wsgi_server_config->verbose_debugging) {
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                             "mod_wsgi (pid=%d): Process '%s' logging to "
                             "'%s'.", getpid(), daemon->group->name,
                             daemon->group->server->server_hostname);
            }

            wsgi_server = daemon->group->server;
        }
        else {
            if (wsgi_server_config->verbose_debugging) {
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                             "mod_wsgi (pid=%d): Process '%s' forced to log "
                             "to '%s'.", getpid(), daemon->group->name,
                             wsgi_server->server_hostname);
            }
        }

        /* Time daemon process started waiting for requests. */

        wsgi_restart_time = apr_time_now();

        /*
         * Setup Python in the child daemon process. Note that
         * we ensure that we are now marked as the original
         * initialiser of the Python interpreter even though
         * mod_python might have done it, as we will be the one
         * to cleanup the child daemon process and not
         * mod_python. We also need to perform the special
         * Python setup which has to be done after a fork.
         */

        wsgi_python_initialized = 1;

        wsgi_python_path = daemon->group->python_path;
        wsgi_python_eggs = daemon->group->python_eggs;

        wsgi_newrelic_config_file = daemon->group->newrelic_config_file;
        wsgi_newrelic_environment = daemon->group->newrelic_environment;

        wsgi_python_child_init(wsgi_daemon_pool);

        /*
         * Create socket wrapper for listener file descriptor
         * and mutex for controlling which thread gets to
         * perform the accept() when a connection is ready.
         */

        apr_os_sock_put(&daemon->listener, &daemon->group->listener_fd, p);

        /*
         * Run the main routine for the daemon process if there
         * is a non zero number of threads. When number of threads
         * is zero we actually go on and shutdown immediately.
         */

        if (daemon->group->threads != 0)
            wsgi_daemon_main(p, daemon);

        /*
         * Destroy the pool for the daemon process. This will
         * have the side affect of also destroying Python.
         */

        ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Stopping process '%s'.", getpid(),
                     daemon->group->name);

        apr_pool_destroy(wsgi_daemon_pool);

        /* Exit the daemon process when being shutdown. */

        wsgi_exit_daemon_process(0);
    }

#ifdef HAVE_FORK
    if (wsgi_python_initialized) {
#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7)
#if 0
        /*
         * XXX Appears to be wrong to call this at this point especially
         * since we haven't acquired the GIL. It wouldn't have been possible
         * for any user code to have registered a Python callback to run
         * in parent after fork either. Leave in code for now but disabled.
         */

        PyOS_AfterFork_Parent();
#endif
#endif
    }
#endif

    apr_pool_note_subprocess(p, &daemon->process, APR_KILL_AFTER_TIMEOUT);
    apr_proc_other_child_register(&daemon->process, wsgi_manage_process,
                                  daemon, NULL, p);

    return OK;
}

static int wsgi_start_daemons(apr_pool_t *p)
{
    WSGIProcessGroup *entries = NULL;
    WSGIProcessGroup *entry = NULL;
    WSGIDaemonProcess *process = NULL;

    int mpm_generation = 0;

    int i, j;

    /* Do we need to create any daemon processes. */

    if (!wsgi_daemon_list)
        return OK;

    /* What server generation is this. */

#if defined(AP_MPMQ_GENERATION)
    ap_mpm_query(AP_MPMQ_GENERATION, &mpm_generation);
#else
    mpm_generation = ap_my_generation;
#endif

    /*
     * Cache references to root server and pool as will need
     * to access these when restarting daemon process when
     * they die.
     */

    wsgi_parent_pool = p;

    /*
     * Startup in turn the required number of daemon processes
     * for each of the named process groups.
     */

    wsgi_daemon_index = apr_hash_make(p);

    entries = (WSGIProcessGroup *)wsgi_daemon_list->elts;

    for (i = 0; i < wsgi_daemon_list->nelts; ++i) {
        int status;

        entry = &entries[i];

        /*
         * Check for whether the daemon process user and
         * group are the default Apache values. If they are
         * then reset them to the current values configured for
         * Apache. This is to work around where the User/Group
         * directives had not been set before the WSGIDaemonProcess
         * directive was used in configuration file. In this case,
         * where no 'user' and 'group' options were provided,
         * the default values would have been used, but these
         * were later overridden thus why we need to update it.
         */

        if (entry->uid == ap_uname2id(DEFAULT_USER)) {
            entry->uid = ap_unixd_config.user_id;
            entry->user = ap_unixd_config.user_name;

            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Reset default user for "
                         "daemon process group '%s' to uid=%ld.",
                         getpid(), entry->name, (long)entry->uid);
        }

        if (entry->gid == ap_gname2id(DEFAULT_GROUP)) {
            entry->gid = ap_unixd_config.group_id;

            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                         "mod_wsgi (pid=%d): Reset default group for "
                         "daemon process group '%s' to gid=%ld.",
                         getpid(), entry->name, (long)entry->gid);
        }

        /*
         * Calculate path for socket to accept requests on and
         * create the socket.
         */

        entry->socket_rotation = wsgi_server_config->socket_rotation;

        if (entry->socket_rotation) {
            entry->socket_path = apr_psprintf(p, "%s.%d.%d.%d.sock",
                                         wsgi_server_config->socket_prefix,
                                         getpid(), mpm_generation, entry->id);
        }
        else {
            entry->socket_path = apr_psprintf(p, "%s.%d.u%d.%d.sock",
                                         wsgi_server_config->socket_prefix,
                                         getpid(), entry->uid, entry->id);
        }

        apr_hash_set(wsgi_daemon_index, entry->name, APR_HASH_KEY_STRING,
                     entry);

        entry->listener_fd = wsgi_setup_socket(entry);

        if (entry->listener_fd == -1)
            return DECLINED;

        /*
         * Register cleanup so that listener socket is cleaned
         * up properly on a restart and on shutdown.
         */

        apr_pool_cleanup_register(p, entry, wsgi_cleanup_process,
                                  apr_pool_cleanup_null);

        /*
         * If there is more than one daemon process in the group
         * then need to create an accept mutex for the daemon
         * processes to use so they don't interfere with each
         * other.
         */

        if (entry->processes > 1) {
            entry->mutex_path = apr_psprintf(p, "%s.%d.%d.%d.lock",
                                             wsgi_server_config->socket_prefix,
                                             getpid(), mpm_generation,
                                             entry->id);

            status = apr_proc_mutex_create(&entry->mutex, entry->mutex_path,
                                           wsgi_server_config->lock_mechanism,
                                           p);

            if (status != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server,
                             "mod_wsgi (pid=%d): Couldn't create accept "
                             "lock '%s' (%d).", getpid(), entry->mutex_path,
                             wsgi_server_config->lock_mechanism);
                return DECLINED;
            }

            /*
             * Depending on the locking mechanism being used
             * need to change the permissions of the lock. Can't
             * use unixd_set_proc_mutex_perms() as it uses the
             * default Apache child process uid/gid where the
             * daemon process uid/gid can be different.
             */

            if (!geteuid()) {
#if APR_HAS_SYSVSEM_SERIALIZE
                if (!strcmp(apr_proc_mutex_name(entry->mutex), "sysvsem")) {
                    apr_os_proc_mutex_t ospmutex;
#if !APR_HAVE_UNION_SEMUN
                    union semun {
                        long val;
                        struct semid_ds *buf;
                        unsigned short *array;
                    };
#endif
                    union semun ick;
                    struct semid_ds buf;

                    apr_os_proc_mutex_get(&ospmutex, entry->mutex);
                    buf.sem_perm.uid = entry->uid;
                    buf.sem_perm.gid = entry->gid;
                    buf.sem_perm.mode = 0600;
                    ick.buf = &buf;
                    if (semctl(ospmutex.crossproc, 0, IPC_SET, ick) < 0) {
                        ap_log_error(APLOG_MARK, APLOG_CRIT, errno,
                                     wsgi_server, "mod_wsgi (pid=%d): "
                                     "Couldn't set permissions on accept "
                                     "mutex '%s' (sysvsem).", getpid(),
                                     entry->mutex_path);
                        return DECLINED;
                    }
                }
#endif
#if APR_HAS_FLOCK_SERIALIZE
                if (!strcmp(apr_proc_mutex_name(entry->mutex), "flock")) {
                    if (chown(entry->mutex_path, entry->uid, -1) < 0) {
                        ap_log_error(APLOG_MARK, APLOG_CRIT, errno,
                                     wsgi_server, "mod_wsgi (pid=%d): "
                                     "Couldn't set permissions on accept "
                                     "mutex '%s' (flock).", getpid(),
                                     entry->mutex_path);
                        return DECLINED;
                    }
                }
#endif
            }
        }

        /* Create the actual required daemon processes. */

        for (j = 1; j <= entry->processes; j++) {
            process = (WSGIDaemonProcess *)apr_pcalloc(p, sizeof(
                                                       WSGIDaemonProcess));

            process->group = entry;
            process->instance = j;

            status = wsgi_start_process(p, process);

            if (status != OK)
                return status;
        }
    }

    return OK;
}

static apr_pool_t *wsgi_pconf_pool = NULL;

static int wsgi_deferred_start_daemons(apr_pool_t *p, ap_scoreboard_e sb_type)
{
    return wsgi_start_daemons(wsgi_pconf_pool);
}

static apr_status_t wsgi_socket_connect_un(apr_socket_t *sock,
                                           struct sockaddr_un *sa)
{
    apr_status_t rv;
    apr_os_sock_t rawsock;
    apr_interval_time_t t;

    rv = apr_os_sock_get(&rawsock, sock);
    if (rv != APR_SUCCESS) {
        return rv;
    }

    rv = apr_socket_timeout_get(sock, &t);
    if (rv != APR_SUCCESS) {
        return rv;
    }

    do {
        rv = connect(rawsock, (struct sockaddr*)sa,
                     APR_OFFSETOF(struct sockaddr_un, sun_path)
                     + strlen(sa->sun_path) + 1);
    } while (rv == -1 && errno == EINTR);

    if ((rv == -1) && (errno == EINPROGRESS || errno == EALREADY)
        && (t > 0)) {
#if APR_MAJOR_VERSION < 2
        rv = apr_wait_for_io_or_timeout(NULL, sock, 0);
#else
        rv = apr_socket_wait(sock, APR_WAIT_WRITE);
#endif

        if (rv != APR_SUCCESS) {
            return rv;
        }
    }

    if (rv == -1 && errno != EISCONN) {
        return errno;
    }

    return APR_SUCCESS;
}

static int wsgi_connect_daemon(request_rec *r, WSGIDaemonSocket *daemon)
{
    WSGIRequestConfig *config = NULL;

    apr_status_t rv;

    struct sockaddr_un addr;

    int retries = 0;
    apr_interval_time_t timer = 0;
    apr_interval_time_t total_time = 0;

    apr_time_t start_time = 0;

    /* Grab request configuration. */

    config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
                                                       &wsgi_module);

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    apr_cpystrn(addr.sun_path, daemon->socket_path, sizeof(addr.sun_path));

    start_time = apr_time_now();

    while (1) {
        retries++;

        config->daemon_connects++;

        rv = apr_socket_create(&daemon->socket, AF_UNIX, SOCK_STREAM,
                               0, r->pool);
        
        if (rv != APR_SUCCESS) {
            ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r,
                         "mod_wsgi (pid=%d): Unable to create socket to "
                         "connect to WSGI daemon process.", getpid());

            return HTTP_INTERNAL_SERVER_ERROR;
        }

        /*
         * Apply timeout before issuing the socket connection in
         * case this hangs for some reason. Would have to be an extreme
         * event for a UNIX socket connect to hang, but have had some
         * unexplained situations which look exactly like that.
         */

        if (daemon->socket_timeout)
            apr_socket_timeout_set(daemon->socket, daemon->socket_timeout);
        else
            apr_socket_timeout_set(daemon->socket, r->server->timeout);

        rv = wsgi_socket_connect_un(daemon->socket, &addr);

        if (rv != APR_SUCCESS) {
            /*
             * We need to check for both connection refused and
             * connection unavailable as Linux systems when
             * connecting to a UNIX listener socket in non
             * blocking mode, where the listener backlog is full
             * will return the error EAGAIN rather than returning
             * ECONNREFUSED as is supposedly dictated by POSIX.
             */

            if (APR_STATUS_IS_ECONNREFUSED(rv) || APR_STATUS_IS_EAGAIN(rv)) {
                if ((apr_time_now()-start_time) < daemon->connect_timeout) {
                    if (wsgi_server_config->verbose_debugging) {
                        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
                                     "mod_wsgi (pid=%d): Connection attempt "
                                     "#%d to WSGI daemon process '%s' on "
                                     "'%s' failed, sleeping before retrying "
                                     "again.", getpid(), retries,
                                     daemon->name, daemon->socket_path);
                    }

                    apr_socket_close(daemon->socket);

                    /*
                     * Progressively increase time we wait between
                     * connection attempts. Start at 0.125 second, but
                     * back off to 1 second interval after 2 seconds.
                     */

                    if (total_time < apr_time_make(2, 0))
                        timer = apr_time_make(0, 125000);
                    else
                        timer = apr_time_make(1, 0);

                    apr_sleep(timer);

                    total_time += timer;
                }
                else {
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                                 "mod_wsgi (pid=%d): Unable to connect to "
                                 "WSGI daemon process '%s' on '%s' after "
                                 "multiple attempts as listener backlog "
                                 "limit was exceeded or the socket does "
                                 "not exist.", getpid(), daemon->name,
                                 daemon->socket_path);

                    apr_socket_close(daemon->socket);

                    return HTTP_SERVICE_UNAVAILABLE;
                }
            }
            else {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                             "mod_wsgi (pid=%d): Unable to connect to "
                             "WSGI daemon process '%s' on '%s' as user "
                             "with uid=%ld.", getpid(), daemon->name,
                             daemon->socket_path, (long)geteuid());

                apr_socket_close(daemon->socket);

                return HTTP_SERVICE_UNAVAILABLE;
            }
        }
        else
            break;
    }

    return OK;
}

static apr_status_t wsgi_socket_send(apr_socket_t *sock, const char *buf,
                                     size_t buf_size)
{
    apr_status_t rv;
    apr_size_t len;

    while (buf_size > 0)
    {
        len = buf_size;

        rv = apr_socket_send(sock, buf, &len);

        if (rv != APR_SUCCESS)
            return rv;

        buf += len;
        buf_size -= len;
    }

    return APR_SUCCESS;
}

static apr_status_t wsgi_socket_sendv_limit(apr_socket_t *sock,
        struct iovec *vec, size_t nvec)
{
    apr_status_t rv;
    apr_size_t written = 0;
    apr_size_t to_write = 0;
    size_t i, offset;

    /* Calculate how much has to be sent. */

    for (i = 0; i < nvec; i++) {
        to_write += vec[i].iov_len;
    }

    /* Loop until all data has been sent. */

    offset = 0;

    while (to_write) {
        apr_size_t n = 0;

        rv = apr_socket_sendv(sock, vec+offset, nvec-offset, &n);

        if (rv != APR_SUCCESS)
            return rv;

        if (n > 0) {
            /* Bail out of all data has been sent. */

            written += n;

            if (written >= to_write)
                break;

            /*
             * Not all data was sent, so ween need to try
             * again with the remainder of the data. We
             * first need to work out where to start from.
             */

            for (i = offset; i < nvec; ) {
                if (n >= vec[i].iov_len) {
                    offset++;
                    n -= vec[i++].iov_len;
                } else {
                    vec[i].iov_len -= n;
                    vec[i].iov_base = (char *) vec[i].iov_base + n;
                    break;
                }
            }
        }
    }

    return APR_SUCCESS;
}

static apr_status_t wsgi_socket_sendv(apr_socket_t *sock, struct iovec *vec,
                                      size_t nvec)
{
#if defined(_SC_IOV_MAX)
    static size_t iov_max = 0;
    
    if (iov_max == 0)
        iov_max = sysconf(_SC_IOV_MAX);
#else
    static size_t iov_max = APR_MAX_IOVEC_SIZE;
#endif

    if (nvec > iov_max) {
        int offset = 0;

        while (nvec != 0) {
            apr_status_t rv;

            rv = wsgi_socket_sendv_limit(sock, &vec[offset],
                    (nvec < iov_max ? nvec : (int)iov_max));

            if (rv != APR_SUCCESS)
                return rv;

            if (nvec > iov_max) {
                nvec -= iov_max;
                offset += iov_max;
            } else {
                nvec = 0;
            }
        }

        return APR_SUCCESS;
    }
    else
        return wsgi_socket_sendv_limit(sock, vec, nvec);
}

static apr_status_t wsgi_send_request(request_rec *r,
                                      WSGIRequestConfig *config,
                                      WSGIDaemonSocket *daemon)
{
    int rv;

    const apr_array_header_t *env_arr;
    const apr_table_entry_t *elts;
    int i;

    struct iovec *vec;
    struct iovec *vec_start;
    struct iovec *vec_next;

    apr_size_t total = 0;
    apr_size_t count = 0;

    apr_table_setn(r->subprocess_env, "mod_wsgi.daemon_connects",
                   apr_psprintf(r->pool, "%d", config->daemon_connects));
    apr_table_setn(r->subprocess_env, "mod_wsgi.daemon_restarts",
                   apr_psprintf(r->pool, "%d", config->daemon_restarts));

    /* Send subprocess environment from request object. */

    env_arr = apr_table_elts(r->subprocess_env);
    elts = (const apr_table_entry_t *)env_arr->elts;

    /*
     * Sending total amount of data, followed by count of separate
     * strings and then each null terminated string. The total is
     * inclusive of the bytes used for the count of the strings.
     */

    vec = (struct iovec *)apr_palloc(r->pool, (2+(2*env_arr->nelts))*
                                     sizeof(struct iovec));

    vec_start = &vec[2];
    vec_next = vec_start;

    for (i=0; i<env_arr->nelts; ++i) {
        if (!elts[i].key)
            continue;

        vec_next->iov_base = (void*)elts[i].key;
        vec_next->iov_len = strlen(elts[i].key) + 1;

        total += vec_next->iov_len;

        vec_next++;

        if (elts[i].val) {
            vec_next->iov_base = (void*)elts[i].val;
            vec_next->iov_len = strlen(elts[i].val) + 1;
        }
        else
        {
            vec_next->iov_base = (void*)"";
            vec_next->iov_len = 1;
        }

        total += vec_next->iov_len;

        vec_next++;
    }

    count = vec_next - vec_start;

    vec[1].iov_base = (void*)&count;
    vec[1].iov_len = sizeof(count);

    total += vec[1].iov_len;

    vec[0].iov_base = (void*)&total;
    vec[0].iov_len = sizeof(total);

    rv = wsgi_socket_sendv(daemon->socket, vec, (int)(vec_next-vec));

    if (rv != APR_SUCCESS)
        return rv;

    return APR_SUCCESS;
}

static void wsgi_discard_output(apr_bucket_brigade *bb)
{
    apr_bucket *e;
    const char *buf;
    apr_size_t len;
    apr_status_t rv;

    for (e = APR_BRIGADE_FIRST(bb);
         e != APR_BRIGADE_SENTINEL(bb);
         e = APR_BUCKET_NEXT(e))
    {
        if (APR_BUCKET_IS_EOS(e)) {
            break;
        }
        rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
        if (rv != APR_SUCCESS) {
            break;
        }
    }
}

static int wsgi_copy_header(void *v, const char *key, const char *val)
{
    apr_table_addn(v, key, val);
    return 1;
}

#define HTTP_UNSET (-HTTP_OK)

static int wsgi_scan_headers(request_rec *r, char *buffer, int buflen,
                             int (*getsfunc) (char *, int, void *),
                             void *getsfunc_data)
{
    char x[32768];
    char *w, *l;
    size_t p;

    int cgi_status = HTTP_UNSET;

    apr_table_t *merge;
    apr_table_t *cookie_table;
    apr_table_t *authen_table;

    WSGIRequestConfig *config = NULL;

    config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
                                                       &wsgi_module);

    /*
     * Default to internal fixed size buffer for reading headers if one
     * is not supplied explicitly with the call.
     */

    if (buffer)
        *buffer = '\0';

    w = buffer ? buffer : x;
    buflen = buffer ? buflen : sizeof(x);

    /* Temporary place to hold headers as we read them. */

    merge = apr_table_make(r->pool, 10);

    /*
     * The HTTP specification says that it is legal to merge duplicate
     * headers into one. Some browsers don't like certain headers being
     * merged however. These headers are Set-Cookie and WWW-Authenticate.
     * We will therefore keep these separate and merge them back in
     * independently at the end. Before we start though, we need to make
     * sure we save away any instances of these headers which may already
     * be listed in the request structure for some reason.
     */

    cookie_table = apr_table_make(r->pool, 2);
    apr_table_do(wsgi_copy_header, cookie_table, r->headers_out,
                 "Set-Cookie", NULL);

    authen_table = apr_table_make(r->pool, 2);
    apr_table_do(wsgi_copy_header, authen_table, r->err_headers_out,
                 "WWW-Authenticate", NULL);

    while (1) {
        int rv = (*getsfunc) (w, buflen - 1, getsfunc_data);

        if (rv == 0) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Truncated or "
                                  "oversized response headers received from "
                                  "daemon process '%s'",
                                  config->process_group), r->filename);

            r->status_line = NULL;

            return HTTP_INTERNAL_SERVER_ERROR;
        }
        else if (rv == -1) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Timeout when "
                                  "reading response headers from daemon "
                                  "process '%s'", config->process_group),
                                  r->filename);

            r->status_line = NULL;

            return HTTP_GATEWAY_TIME_OUT;
        }

        /*
         * Delete any trailing (CR?)LF. Indeed, the host's '\n':
         * '\012' for UNIX; '\015' for MacOS; '\025' for OS/390.
         */

        p = strlen(w);

        if (p > 0 && w[p - 1] == '\n') {
            if (p > 1 && w[p - 2] == CR) {
                w[p - 2] = '\0';
            }
            else {
                w[p - 1] = '\0';
            }
        }

        /*
         * If we've finished reading the headers, check to make sure
         * any HTTP/1.1 conditions are met. If so, we're done; normal
         * processing will handle the script's output. If not, just
         * return the error.
         */

        if (w[0] == '\0') {
            int cond_status = OK;

           /*
            * This fails because it gets confused when a CGI Status
            * header overrides ap_meets_conditions.
            *
            * We can fix that by dropping ap_meets_conditions when
            * Status has been set.  Since this is the only place
            * cgi_status gets used, let's test it explicitly.
            *
            * The alternative would be to ignore CGI Status when
            * ap_meets_conditions returns anything interesting. That
            * would be safer wrt HTTP, but would break CGI.
            */

            if ((cgi_status == HTTP_UNSET) && (r->method_number == M_GET)) {
                cond_status = ap_meets_conditions(r);
            }

            /*
             * Merge the headers received back into the request
             * structure. There should only be one per header with
             * values combined for these.
             */

            apr_table_overlap(r->headers_out, merge,
                              APR_OVERLAP_TABLES_MERGE);

            /*
             * Now add in the special headers which we can't merge
             * because it gives certain browsers problems.
             */

            if (!apr_is_empty_table(cookie_table)) {
                apr_table_unset(r->headers_out, "Set-Cookie");
                r->headers_out = apr_table_overlay(r->pool,
                    r->headers_out, cookie_table);
            }

            if (!apr_is_empty_table(authen_table)) {
                apr_table_unset(r->err_headers_out, "WWW-Authenticate");
                r->err_headers_out = apr_table_overlay(r->pool,
                    r->err_headers_out, authen_table);
            }

            return cond_status;
        }

        /* If we see a bogus header don't ignore it. Shout and scream. */

        if (!(l = strchr(w, ':'))) {
            char malformed[32];

            strncpy(malformed, w, sizeof(malformed)-1);
            malformed[sizeof(malformed)-1] = '\0';

            if (!buffer) {
                /* Soak up all the script output. */

                while ((*getsfunc)(w, buflen - 1, getsfunc_data) > 0) {
                    continue;
                }
            }

            wsgi_log_script_error(r, apr_psprintf(r->pool, "Malformed "
                                  "header '%s' found when reading script "
                                  "headers from daemon process '%s'",
                                  malformed, config->process_group),
                                  r->filename);

            r->status_line = NULL;

            return HTTP_INTERNAL_SERVER_ERROR;
        }

        /* Strip leading white space from header value. */

        *l++ = '\0';
        while (*l && apr_isspace(*l)) {
            ++l;
        }

        if (!strcasecmp(w, "Content-type")) {
            char *tmp;

            /* Nuke trailing whitespace. */

            char *endp = l + strlen(l) - 1;
            while (endp > l && apr_isspace(*endp)) {
                *endp-- = '\0';
            }

            tmp = apr_pstrdup(r->pool, l);
            ap_content_type_tolower(tmp);
            ap_set_content_type(r, tmp);
        }
        else if (!strcasecmp(w, "Status")) {
            /*
             * If the script returned a specific status, that's what
             * we'll use, otherwise we assume 200 OK.
             */

            r->status = cgi_status = atoi(l);
            r->status_line = apr_pstrdup(r->pool, l);
        }
        else if (!strcasecmp(w, "Location")) {
            apr_table_set(r->headers_out, w, l);
        }
        else if (!strcasecmp(w, "Content-Length")) {
            apr_table_set(r->headers_out, w, l);
        }
        else if (!strcasecmp(w, "Content-Range")) {
            apr_table_set(r->headers_out, w, l);
        }
        else if (!strcasecmp(w, "Transfer-Encoding")) {
            apr_table_set(r->headers_out, w, l);
        }
        else if (!strcasecmp(w, "Last-Modified")) {
            /*
             * If the script gave us a Last-Modified header, we can't just
             * pass it on blindly because of restrictions on future values.
             */

            ap_update_mtime(r, apr_date_parse_http(l));
            ap_set_last_modified(r);
        }
        else if (!strcasecmp(w, "Set-Cookie")) {
            apr_table_add(cookie_table, w, l);
        }
        else if (!strcasecmp(w, "WWW-Authenticate")) {
            apr_table_add(authen_table, w, l);
        }
        else {
            apr_table_add(merge, w, l);
        }
    }

    return OK;
}

static int wsgi_getsfunc_brigade(char *buf, int len, void *arg)
{
    apr_bucket_brigade *bb = (apr_bucket_brigade *)arg;
    const char *dst_end = buf + len - 1;
    char *dst = buf;
    apr_bucket *e = APR_BRIGADE_FIRST(bb);
    apr_status_t rv;
    int done = 0;

    while ((dst < dst_end) && !done && e != APR_BRIGADE_SENTINEL(bb)
           && !APR_BUCKET_IS_EOS(e)) {
        const char *bucket_data;
        apr_size_t bucket_data_len;
        const char *src;
        const char *src_end;
        apr_bucket * next;

        rv = apr_bucket_read(e, &bucket_data, &bucket_data_len,
                             APR_BLOCK_READ);
        if (rv != APR_SUCCESS || (bucket_data_len == 0)) {
            *dst = '\0';
            return APR_STATUS_IS_TIMEUP(rv) ? -1 : 0;
        }
        src = bucket_data;
        src_end = bucket_data + bucket_data_len;
        while ((src < src_end) && (dst < dst_end) && !done) {
            if (*src == '\n') {
                done = 1;
            }
            else if (*src != '\r') {
                *dst++ = *src;
            }
            src++;
        }

        if (src < src_end) {
            apr_bucket_split(e, src - bucket_data);
        }
        next = APR_BUCKET_NEXT(e);
        APR_BUCKET_REMOVE(e);
        apr_bucket_destroy(e);
        e = next;
    }
    *dst = '\0';
    return done;
}

static int wsgi_scan_headers_brigade(request_rec *r,
                                     apr_bucket_brigade *bb,
                                     char *buffer, int buflen)
{
    return wsgi_scan_headers(r, buffer, buflen, wsgi_getsfunc_brigade, bb);
}

static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
                                  apr_size_t buffer_size, apr_time_t timeout)
{
    apr_bucket *e;
    apr_read_type_e mode = APR_NONBLOCK_READ;

    apr_bucket_brigade *tmpbb;

    const char *data = NULL;
    apr_size_t length = 0;

    apr_size_t bytes_transfered = 0;

    int bucket_count = 0;

    apr_status_t rv;

#if AP_MODULE_MAGIC_AT_LEAST(20110605, 2)
    apr_socket_t *sock;
    apr_interval_time_t existing_timeout = 0;
#endif

    if (buffer_size == 0)
        buffer_size = 65536;

    /*
     * Override the socket timeout for writing back data to the
     * client. If that wasn't defined this will be the same as
     * the timeout for the socket used in communicating with the
     * daemon, or left as the overall server timeout if that
     * isn't specified. Just to be safe we remember the existing
     * timeout and restore it at the end of a successful request
     * in case the same connection if kept alive and used for a
     * subsequent request with a different handler.
     */

#if AP_MODULE_MAGIC_AT_LEAST(20110605, 2)
    sock = ap_get_conn_socket(r->connection);

    rv = apr_socket_timeout_get(sock, &existing_timeout);

    if (rv != APR_SUCCESS) {
        existing_timeout = 0;
    }
    else {
        if (timeout)
            apr_socket_timeout_set(sock, timeout);
    }
#endif

    /*
     * Transfer any response content. We want to avoid the
     * problem where the core output filter has no flow control
     * to deal with slow HTTP clients and can actually buffer up
     * excessive amounts of response content in memory. A fix
     * for this was only introduced in Apache 2.3.3, with
     * possible further tweaks in Apache 2.4.1. To avoid issue of
     * what version it was implemented in, just employ a
     * strategy of forcing a flush every time we pass through
     * more than a certain amount of data.
     */

    tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc);

    while ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
        /* If we have reached end of stream, we need to pass it on */

        if (APR_BUCKET_IS_EOS(e)) {
            /*
             * Probably do not need to force a flush as EOS should
             * do that, but do it just in case when we potentially
             * have pending data to be written out.
             */

            if (bytes_transfered != 0) {
                APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_flush_create(
                                        r->connection->bucket_alloc));
            }

            APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_eos_create(
                                    r->connection->bucket_alloc));

            rv = ap_pass_brigade(r->output_filters, tmpbb);

            apr_brigade_cleanup(tmpbb);

            if (rv != APR_SUCCESS) {
                apr_brigade_destroy(bb);

                /*
                 * Don't flag error if client connection was aborted
                 * so that access log still records the original HTTP
                 * response code returned by the WSGI application.
                 */

                if (r->connection->aborted)
                    return OK;

                return HTTP_INTERNAL_SERVER_ERROR;
            }

            break;
        }

        /*
         * Force the reading in of next block of data to be
         * transfered if necessary. If the bucket is a heap
         * bucket, then it will be whatever data is in it. If it
         * is a socket bucket, this will result in the bucket
         * being converted to a heap bucket with some amount of
         * data and the socket bucket added back in after it. Any
         * non data buckets should be skipped and discarded. The
         * result should always be that the first bucket is a
         * heap bucket.
         */

        rv = apr_bucket_read(e, &data, &length, mode);

        /*
         * If we would have blocked if not in non blocking mode
         * we send a flush bucket to ensure that all buffered
         * data is sent out before we block waiting for more.
         */

        if (rv == APR_EAGAIN && mode == APR_NONBLOCK_READ) {
            APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_flush_create(
                                    r->connection->bucket_alloc));

            rv = ap_pass_brigade(r->output_filters, tmpbb);

            apr_brigade_cleanup(tmpbb);

            if (rv == APR_TIMEUP) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                             "mod_wsgi (pid=%d): Failed to proxy response "
                             "to client.", getpid());
            }

            if (rv != APR_SUCCESS) {
                apr_brigade_destroy(bb);

                /*
                 * Don't flag error if client connection was aborted
                 * so that access log still records the original HTTP
                 * response code returned by the WSGI application.
                 */

                if (r->connection->aborted)
                    return OK;

                return HTTP_INTERNAL_SERVER_ERROR;
            }

            bytes_transfered = 0;

            bucket_count = 0;

            /*
             * Retry read from daemon using a blocking read. We do
             * not delete the bucket as we want to operate on the
             * same one as we would have blocked.
             */

            mode = APR_BLOCK_READ;

            continue;

        } else if (rv != APR_SUCCESS) {
            apr_brigade_destroy(bb);

            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                         "mod_wsgi (pid=%d): Failed to proxy response "
                         "from daemon.", getpid());

            /*
             * Don't flag error if couldn't read from daemon
             * so that access log still records the original HTTP
             * response code returned by the WSGI application.
             */

            return OK;
        }

        /*
         * We had some data to transfer. Next time round we need to
         * always be try a non-blocking read first.
         */

        mode = APR_NONBLOCK_READ;

        /*
         * Now we don't actually work with the data which was
         * read direct and instead simply remove what should be a
         * heap bucket from the start of the bucket brigade and
         * then place in a new bucket brigade to be pushed out to
         * the client. By passing down the bucket, it avoids the
         * need to create a transient bucket holding a reference
         * to the data from the first bucket.
         */

        APR_BUCKET_REMOVE(e);
        APR_BRIGADE_INSERT_TAIL(tmpbb, e);

        /*
         * If we have reached the buffer size threshold, we want
         * to flush the data so that we aren't buffering too much
         * in memory and blowing out memory size. We also have a
         * check on the number of buckets we have accumulated as
         * a large number of buckets with very small amounts of
         * data will also accumulate a lot of memory. Apache's
         * own flow control doesn't cope with such a situation.
         * Right now hard wire the max number of buckets at 16
         * which equates to worst case number of separate data
         * blocks can be written by a writev() call on systems
         * such as Solaris.
         */

        bytes_transfered += length;

        bucket_count += 1;

        if (bytes_transfered > buffer_size || bucket_count >= 16) {
            APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_flush_create(
                                    r->connection->bucket_alloc));

            bytes_transfered = 0;

            bucket_count = 0;

            /*
             * Since we flushed the data out to the client, it is
             * okay to go back and do a blocking read the next time.
             */

            mode = APR_BLOCK_READ;
        }

        /* Pass the heap bucket and any flush bucket on. */

        rv = ap_pass_brigade(r->output_filters, tmpbb);

        apr_brigade_cleanup(tmpbb);

        if (rv == APR_TIMEUP) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                         "mod_wsgi (pid=%d): Failed to proxy response "
                         "to client.", getpid());
        }

        if (rv != APR_SUCCESS) {
            apr_brigade_destroy(bb);

            /*
             * Don't flag error if client connection was aborted
             * so that access log still records the original HTTP
             * response code returned by the WSGI application.
             */

            if (r->connection->aborted)
                return OK;

            return HTTP_INTERNAL_SERVER_ERROR;
        }
    }

#if AP_MODULE_MAGIC_AT_LEAST(20110605, 2)
    if (existing_timeout)
        apr_socket_timeout_set(sock, existing_timeout);
#endif

    apr_brigade_destroy(bb);

    return OK;
}

#define ASCII_CRLF  "\015\012"
#define ASCII_ZERO  "\060"

static int wsgi_execute_remote(request_rec *r)
{
    WSGIRequestConfig *config = NULL;
    WSGIDaemonSocket *daemon = NULL;
    WSGIProcessGroup *group = NULL;

    char *key = NULL;
    const char *hash = NULL;

    int status;
    apr_status_t rv;

    int seen_eos;
    int child_stopped_reading;
    apr_bucket_brigade *bbout;
    apr_bucket_brigade *bbin;
    apr_bucket *b;

    const char *location = NULL;

    char *header_buffer = NULL;
    int header_buflen = 0;

    /* Grab request configuration. */

    config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
                                                       &wsgi_module);

    /*
     * Only allow the process group to match against a restricted
     * set of processes if such a restricted set has been defined.
     */

    if (config->restrict_process) {
        if (!apr_table_get(config->restrict_process,
                           config->process_group)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Daemon "
                                  "process called '%s' cannot be "
                                  "accessed by this WSGI application "
                                  "as not a member of allowed groups",
                                  config->process_group), r->filename);

            return HTTP_INTERNAL_SERVER_ERROR;
        }
    }

    /*
     * Do not process request as remote if actually targeted at
     * the main Apache processes.
     */

    if (!*config->process_group)
        return DECLINED;

    /* Grab details of matching process group. */

    if (!wsgi_daemon_index) {
        wsgi_log_script_error(r, apr_psprintf(r->pool, "No WSGI daemon "
                              "process called '%s' has been configured",
                              config->process_group), r->filename);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    group = (WSGIProcessGroup *)apr_hash_get(wsgi_daemon_index,
                                             config->process_group,
                                             APR_HASH_KEY_STRING);

    if (!group) {
        wsgi_log_script_error(r, apr_psprintf(r->pool, "No WSGI daemon "
                              "process called '%s' has been configured",
                              config->process_group), r->filename);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /*
     * Only allow the process group to match against a daemon
     * process defined within a virtual host with the same
     * server name or a daemon process defined at global server
     * scope.
     */

    if (group->server != r->server && group->server != wsgi_server) {
        if (strcmp(group->server->server_hostname,
                   r->server->server_hostname) != 0) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Daemon "
                                  "process called '%s' cannot be "
                                  "accessed by this WSGI application",
                                  config->process_group), r->filename);

            return HTTP_INTERNAL_SERVER_ERROR;
        }
    }

    /*
     * Check restrictions related to the group of the WSGI
     * script file and who has write access to the directory it
     * is contained in. If not satisfied forbid access.
     */

    if (group->script_group) {
        apr_uid_t gid;
        struct group *grent = NULL;
        const char *grname = NULL;
        apr_finfo_t finfo;
        const char *path = NULL;

        if (!(r->finfo.valid & APR_FINFO_GROUP)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Group "
                                  "information not available for WSGI "
                                  "script file"), r->filename);
            return HTTP_FORBIDDEN;
        }

        gid = r->finfo.group;

        if ((grent = getgrgid(gid)) == NULL) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Couldn't "
                                  "determine group of WSGI script file, "
                                  "gid=%ld", (long)gid), r->filename);
            return HTTP_FORBIDDEN;
        }

        grname = grent->gr_name;

        if (strcmp(group->script_group, grname)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Group of WSGI "
                                  "script file does not match required group "
                                  "for daemon process, group=%s", grname),
                                  r->filename);
            return HTTP_FORBIDDEN;
        }

        if (!(r->finfo.valid & APR_FINFO_WPROT)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "World "
                                  "permissions not available for WSGI "
                                  "script file"), r->filename);
            return HTTP_FORBIDDEN;
        }

        if (r->finfo.protection & APR_FPROT_WWRITE) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "WSGI script "
                                  "file is writable to world"), r->filename);
            return HTTP_FORBIDDEN;
        }

        path = ap_make_dirstr_parent(r->pool, r->filename);

        if (apr_stat(&finfo, path, APR_FINFO_NORM, r->pool) != APR_SUCCESS) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Unable to stat "
                                  "parent directory of WSGI script"), path);
            return HTTP_FORBIDDEN;
        }

        gid = finfo.group;

        if ((grent = getgrgid(gid)) == NULL) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Couldn't "
                                  "determine group of parent directory of "
                                  "WSGI script file, gid=%ld", (long)gid),
                                  r->filename);
            return HTTP_FORBIDDEN;
        }

        grname = grent->gr_name;

        if (strcmp(group->script_group, grname)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Group of parent "
                                  "directory of WSGI script file does not "
                                  "match required group for daemon process, "
                                  "group=%s", grname), r->filename);
            return HTTP_FORBIDDEN;
        }

        if (finfo.protection & APR_FPROT_WWRITE) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Parent directory "
                                  "of WSGI script file is writable to world"),
                                  r->filename);
            return HTTP_FORBIDDEN;
        }
    }

    /*
     * Check restrictions related to who can be the owner of
     * the WSGI script file and who has write access to the
     * directory it is contained in. If not satisfied forbid
     * access.
     */

    if (group->script_user) {
        apr_uid_t uid;
        struct passwd *pwent = NULL;
        const char *pwname = NULL;
        apr_finfo_t finfo;
        const char *path = NULL;

        if (!(r->finfo.valid & APR_FINFO_USER)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "User "
                                  "information not available for WSGI "
                                  "script file"), r->filename);
            return HTTP_FORBIDDEN;
        }

        uid = r->finfo.user;

        if ((pwent = getpwuid(uid)) == NULL) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Couldn't "
                                  "determine owner of WSGI script file, "
                                  "uid=%ld", (long)uid), r->filename);
            return HTTP_FORBIDDEN;
        }

        pwname = pwent->pw_name;

        if (strcmp(group->script_user, pwname)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Owner of WSGI "
                                  "script file does not match required user "
                                  "for daemon process, user=%s", pwname),
                                  r->filename);
            return HTTP_FORBIDDEN;
        }

        if (!(r->finfo.valid & APR_FINFO_GPROT)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Group "
                                  "permissions not available for WSGI "
                                  "script file"), r->filename);
            return HTTP_FORBIDDEN;
        }

        if (r->finfo.protection & APR_FPROT_GWRITE) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "WSGI script "
                                  "file is writable to group"), r->filename);
            return HTTP_FORBIDDEN;
        }

        if (!(r->finfo.valid & APR_FINFO_WPROT)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "World "
                                  "permissions not available for WSGI "
                                  "script file"), r->filename);
            return HTTP_FORBIDDEN;
        }

        if (r->finfo.protection & APR_FPROT_WWRITE) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "WSGI script "
                                  "file is writable to world"), r->filename);
            return HTTP_FORBIDDEN;
        }

        path = ap_make_dirstr_parent(r->pool, r->filename);

        if (apr_stat(&finfo, path, APR_FINFO_NORM, r->pool) != APR_SUCCESS) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Unable to stat "
                                  "parent directory of WSGI script"), path);
            return HTTP_FORBIDDEN;
        }

        uid = finfo.user;

        if ((pwent = getpwuid(uid)) == NULL) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Couldn't "
                                  "determine owner of parent directory of "
                                  "WSGI script file, uid=%ld", (long)uid),
                                  r->filename);
            return HTTP_FORBIDDEN;
        }

        pwname = pwent->pw_name;

        if (strcmp(group->script_user, pwname)) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Owner of parent "
                                  "directory of WSGI script file does not "
                                  "match required user for daemon process, "
                                  "user=%s", pwname), r->filename);
            return HTTP_FORBIDDEN;
        }

        if (finfo.protection & APR_FPROT_WWRITE) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Parent directory "
                                  "of WSGI script file is writable to world"),
                                  r->filename);
            return HTTP_FORBIDDEN;
        }

        if (finfo.protection & APR_FPROT_GWRITE) {
            wsgi_log_script_error(r, apr_psprintf(r->pool, "Parent directory "
                                  "of WSGI script file is writable to group"),
                                  r->filename);
            return HTTP_FORBIDDEN;
        }
    }

    /*
     * Add magic marker into request environment so that daemon
     * process can verify that request is from a sender that can
     * be trusted. Wipe out original key to make it a bit harder
     * for rogue code in Apache child processes to trawl through
     * memory looking for unhashed string.
     */

    key = apr_psprintf(r->pool, "%ld|%s|%s|%s", group->random,
                       group->socket_path, r->filename,
                       config->handler_script);
    hash = ap_md5(r->pool, (const unsigned char *)key);
    memset(key, '\0', strlen(key));

    apr_table_setn(r->subprocess_env, "mod_wsgi.magic", hash);

    /* Create connection to the daemon process. */

    apr_table_setn(r->subprocess_env, "mod_wsgi.queue_start",
                   apr_psprintf(r->pool, "%" APR_TIME_T_FMT, apr_time_now()));

    daemon = (WSGIDaemonSocket *)apr_pcalloc(r->pool,
                                             sizeof(WSGIDaemonSocket));

    daemon->name = config->process_group;
    daemon->socket_path = group->socket_path;
    daemon->connect_timeout = group->connect_timeout;
    daemon->socket_timeout = group->socket_timeout;

    if ((status = wsgi_connect_daemon(r, daemon)) != OK)
        return status;

    /* Send request details and subprocess environment. */

    if (wsgi_server_config->verbose_debugging) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Request server was "
                     "'%s|%d'.", getpid(), r->server->server_hostname,
                     r->server->port);
    }

    if ((rv = wsgi_send_request(r, config, daemon)) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                     "mod_wsgi (pid=%d): Unable to send request details "
                     "to WSGI daemon process '%s' on '%s'.", getpid(),
                     daemon->name, daemon->socket_path);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* Setup bucket brigade for reading response from daemon. */

    bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    b = apr_bucket_socket_create(daemon->socket, r->connection->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bbin, b);
    b = apr_bucket_eos_create(r->connection->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bbin, b);

    /* Create alternate buffer for reading in response header values. */

    if (group->header_buffer_size != 0) {
        header_buflen = group->header_buffer_size;
        header_buffer = apr_pcalloc(r->pool, header_buflen);
    }

    /*
     * If process reload mechanism enabled, or a queue timeout is
     * specified, then we need to look for marker indicating it
     * is okay to transfer content, or whether process is being
     * restarted and that we should therefore create a
     * connection to daemon process again.
     */

    if (*config->process_group && (config->script_reloading ||
            group->queue_timeout != 0)) {

        int retries = 0;
        int maximum = (2*group->processes)+1;

        /*
         * While special header indicates a restart is being
         * done, then keep trying to reconnect. Cap the number
         * of retries to at most about 2 times the number of
         * daemon processes in the process group. If still being
         * told things are being restarted, then we will error
         * indicating service is unavailable.
         */

        while (retries < maximum) {
            /* Scan the CGI script like headers from daemon. */

            status = wsgi_scan_headers_brigade(r, bbin, header_buffer,
                                               header_buflen);

            if (status != OK)
                return status;

            /*
             * Status must be 200 for our special headers. Ideally
             * we would use 0 as did in the past but Apache 2.4
             * complains if use 0 as not a valid status value.
             */

            if (r->status != 200) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                             "mod_wsgi (pid=%d): Unexpected status from "
                             "WSGI daemon process '%d'.", getpid(),
                             r->status);

                r->status_line = NULL;

                return HTTP_INTERNAL_SERVER_ERROR;
            }

            if (!strcmp(r->status_line, "200 Continue")) {
                r->status_line = NULL;

                break;
            }

            if (!strcmp(r->status_line, "200 Timeout")) {
                r->status_line = NULL;

                return HTTP_GATEWAY_TIME_OUT;
            }

            if (strcmp(r->status_line, "200 Rejected")) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                             "mod_wsgi (pid=%d): Unexpected status from "
                             "WSGI daemon process '%d'.", getpid(), r->status);

                r->status_line = NULL;

                return HTTP_INTERNAL_SERVER_ERROR;
            }

            r->status_line = NULL;

            /* Need to close previous socket connection first. */

            apr_socket_close(daemon->socket);

            /* Has maximum number of attempts been reached. */

            if (retries == maximum) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                             "mod_wsgi (pid=%d): Maximum number of WSGI "
                             "daemon process restart connects reached '%d'.",
                             getpid(), maximum);
                return HTTP_SERVICE_UNAVAILABLE;
            }

            retries++;

            config->daemon_restarts++;

            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
                         "mod_wsgi (pid=%d): Connect after WSGI daemon "
                         "process restart, attempt #%d.", getpid(),
                         retries);

            /* Connect and setup connection just like before. */

            if ((status = wsgi_connect_daemon(r, daemon)) != OK)
                return status;

            if ((rv = wsgi_send_request(r, config, daemon)) != APR_SUCCESS) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                             "mod_wsgi (pid=%d): Unable to send request "
                             "details to WSGI daemon process '%s' on '%s'.",
                             getpid(), daemon->name, daemon->socket_path);

                return HTTP_INTERNAL_SERVER_ERROR;
            }

            apr_brigade_destroy(bbin);

            bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc);
            b = apr_bucket_socket_create(daemon->socket,
                                       r->connection->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(bbin, b);
            b = apr_bucket_eos_create(r->connection->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(bbin, b);
        }
    }


    /*
     * Need to reset request status value to HTTP_OK else it
     * screws up HTTP input filter when processing a POST
     * request with 100-continue requirement.
     */

    r->status = HTTP_OK;

    /*
     * Transfer any request content which was provided. Note that we
     * actually frame each data block sent with same format as is used
     * for chunked transfer encoding. This will be decoded in the
     * daemon process. This is done so that the EOS can be properly
     * identified by the daemon process in the absence of a value for
     * CONTENT_LENGTH that can be relied on. The CONTENT_LENGTH is
     * dodgy when have mutating input filters and none will be present
     * at all if chunked request content was used.
     */

    seen_eos = 0;
    child_stopped_reading = 0;

    bbout = apr_brigade_create(r->pool, r->connection->bucket_alloc);

    do {
        apr_bucket *bucket;

        rv = ap_get_brigade(r->input_filters, bbout, AP_MODE_READBYTES,
                            APR_BLOCK_READ, HUGE_STRING_LEN);

        if (rv != APR_SUCCESS) {
            char status_buffer[512];
            const char *error_message;

            error_message = apr_psprintf(r->pool, "Request data read "
                    "error when proxying data to daemon process: %s",
                    apr_strerror(rv, status_buffer, sizeof(
                    status_buffer)-1));

            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                         "mod_wsgi (pid=%d): %s.", getpid(), error_message);

            if (APR_STATUS_IS_TIMEUP(rv))
                return HTTP_REQUEST_TIME_OUT;

            return HTTP_INTERNAL_SERVER_ERROR;
        }

        for (bucket = APR_BRIGADE_FIRST(bbout);
             bucket != APR_BRIGADE_SENTINEL(bbout);
             bucket = APR_BUCKET_NEXT(bucket))
        {
            const char *data;
            apr_size_t len;

            char chunk_hdr[20];
            apr_size_t hdr_len;

            struct iovec vec[3];

            if (APR_BUCKET_IS_EOS(bucket)) {
                /* Send closing frame for chunked content. */

                rv = wsgi_socket_send(daemon->socket,
                        ASCII_ZERO ASCII_CRLF ASCII_CRLF, 5);

                if (rv != APR_SUCCESS) {
                    char status_buffer[512];
                    const char *error_message;

                    error_message = apr_psprintf(r->pool, "Request data write "
                            "error when proxying data to daemon process: %s",
                            apr_strerror(rv, status_buffer, sizeof(
                            status_buffer)-1));

                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                 "mod_wsgi (pid=%d): %s.", getpid(),
                                 error_message);
                }

                seen_eos = 1;
                break;
            }

            /* We can't do much with this. */

            if (APR_BUCKET_IS_FLUSH(bucket)) {
                continue;
            }

            /* If the child stopped, we still must read to EOS. */

            if (child_stopped_reading) {
                continue;
            }

            /* Read block. */

            rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);

            if (rv != APR_SUCCESS) {
                char status_buffer[512];
                const char *error_message;

                error_message = apr_psprintf(r->pool, "Request data read "
                        "error when proxying data to daemon process: %s",
                        apr_strerror(rv, status_buffer, sizeof(
                        status_buffer)-1));

                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                             "mod_wsgi (pid=%d): %s.", getpid(),
                             error_message);

                break;
            }

            /*
             * Keep writing data to the child until done or too
             * much time elapses with no progress or an error
             * occurs. Frame the data being sent with format used
             * for chunked transfer encoding.
             */

            hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr),
                    "%" APR_UINT64_T_HEX_FMT ASCII_CRLF, (apr_uint64_t)len);

            vec[0].iov_base = (void *)chunk_hdr;
            vec[0].iov_len = hdr_len;
            vec[1].iov_base = (void *)data;
            vec[1].iov_len = len;
            vec[2].iov_base = (void *)ASCII_CRLF;
            vec[2].iov_len = 2;

            rv = wsgi_socket_sendv(daemon->socket, vec, 3);

            if (rv != APR_SUCCESS) {
                char status_buffer[512];
                const char *error_message;

                error_message = apr_psprintf(r->pool, "Request data write "
                        "error when proxying data to daemon process: %s",
                        apr_strerror(rv, status_buffer, sizeof(
                        status_buffer)-1));

                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                             "mod_wsgi (pid=%d): %s.", getpid(),
                             error_message);

                /* Daemon stopped reading, discard remainder. */

                child_stopped_reading = 1;
            }
        }
        apr_brigade_cleanup(bbout);
    }
    while (!seen_eos);

    /*
     * Close socket for writing so that daemon detects end of
     * request content.
     */

    apr_socket_shutdown(daemon->socket, APR_SHUTDOWN_WRITE);

    /* Scan the CGI script like headers from daemon. */

    status = wsgi_scan_headers_brigade(r, bbin, header_buffer,
                                       header_buflen);

    if (status != OK)
        return status;

    /*
     * Look for the special case of status being 200 but the
     * status line indicating an error and translate it into a
     * 500 error so that error document processing will occur
     * for those cases where WSGI application wouldn't have
     * supplied their own error document. We used to use 0
     * here for status but Apache 2.4 prohibits it now.
     */

    if (r->status == 200 && !strcmp(r->status_line, "200 Error")) {
        r->status_line = NULL;

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /*
     * Look for 'Location' header and if an internal
     * redirect, execute the redirect. This behaviour is
     * consistent with how mod_cgi and mod_cgid work and
     * what is permitted by the CGI specification.
     */

    location = apr_table_get(r->headers_out, "Location");

    if (location && location[0] == '/' && r->status == 200) {
        /*
         * Discard all response content returned from
         * the daemon process.
         */

        wsgi_discard_output(bbin);
        apr_brigade_destroy(bbin);

        /*
         * The internal redirect needs to be a GET no
         * matter what the original method was.
         */

        r->method = apr_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. Not sure if we need to worry
         * about removing 'Transfer-Encoding' header.
         */

        apr_table_unset(r->headers_in, "Content-Length");

        ap_internal_redirect_handler(location, r);

        return OK;
    }

    /*
     * Allow the web server to override any error
     * page produced by the WSGI application.
     */

    if (config->error_override && ap_is_HTTP_ERROR(r->status)) {
        status = r->status;

        r->status = HTTP_OK;
        r->status_line = NULL;

        /*
         * Discard all response content returned from
         * the daemon process if any expected.
         */

        if (!r->header_only && /* not HEAD request */
            (status != HTTP_NO_CONTENT) && /* not 204 */
            (status != HTTP_NOT_MODIFIED)) { /* not 304 */
            wsgi_discard_output(bbin);
            apr_brigade_destroy(bbin);
        }

        return status;
    }

    /* Transfer any response content. */

    return wsgi_transfer_response(r, bbin, group->response_buffer_size,
                                  group->response_socket_timeout);
}

static apr_status_t wsgi_socket_read(apr_socket_t *sock, void *vbuf,
                                     apr_size_t size)
{
    char *buf = vbuf;
    apr_status_t rv;
    apr_size_t count = 0;
    apr_size_t len = 0;

    do {
        len = size - count;
        if ((rv = apr_socket_recv(sock, buf + count, &len)) != APR_SUCCESS)
             return rv;
        count += len;
    } while (count < size);

    return APR_SUCCESS;
}

static apr_status_t wsgi_read_strings(apr_socket_t *sock, char ***s,
                                      apr_pool_t *p)
{
    apr_status_t rv;

    apr_size_t total;

    apr_size_t n;
    apr_size_t i;
    apr_size_t l;

    char *buffer;
    char *offset;

    if ((rv = wsgi_socket_read(sock, &total, sizeof(total))) != APR_SUCCESS)
        return rv;

    buffer = apr_palloc(p, total);
    offset = buffer;

    if ((rv = wsgi_socket_read(sock, buffer, total)) != APR_SUCCESS)
        return rv;

    memcpy(&n, offset, sizeof(n));
    offset += sizeof(n);

    *s = apr_pcalloc(p, (n+1)*sizeof(**s));

    for (i = 0; i < n; i++) {
        l = strlen(offset) + 1;
        (*s)[i] = offset;
        offset += l;
    }

    return APR_SUCCESS;
}

static apr_status_t wsgi_read_request(apr_socket_t *sock, request_rec *r)
{
    int rv;

    char **vars;

    /* Read subprocess environment from request object. */

    rv = wsgi_read_strings(sock, &vars, r->pool);

    if (rv != APR_SUCCESS)
        return rv;

    while (*vars) {
        char *key = *vars++;

        apr_table_setn(r->subprocess_env, key, *vars++);
    }

    return APR_SUCCESS;
}

static ap_filter_rec_t *wsgi_header_filter_handle;

static apr_status_t wsgi_header_filter(ap_filter_t *f, apr_bucket_brigade *b)
{
    request_rec *r = f->r;

    struct iovec vec1[4];
    apr_bucket_brigade *b2;
    char crlf[] = CRLF;
    apr_size_t buflen;

    const apr_array_header_t *elts;
    const apr_table_entry_t *t_elt;
    const apr_table_entry_t *t_end;
    struct iovec *vec2;
    struct iovec *vec2_next;

    /* Output status line. */

    if (!r->status_line)
        r->status_line = ap_get_status_line(r->status);

    vec1[0].iov_base = (void *)"Status:";
    vec1[0].iov_len  = strlen("Status:");
    vec1[1].iov_base = (void *)" ";
    vec1[1].iov_len  = sizeof(" ") - 1;
    vec1[2].iov_base = (void *)(r->status_line);
    vec1[2].iov_len  = strlen(r->status_line);
    vec1[3].iov_base = (void *)CRLF;
    vec1[3].iov_len  = sizeof(CRLF) - 1;

    b2 = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    apr_brigade_writev(b2, NULL, NULL, vec1, 4);

    /* Merge response header tables together. */

    if (!apr_is_empty_table(r->err_headers_out)) {
        r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
                                           r->headers_out);
    }

    /* Override the content type for response. */

    if (r->content_type)
        apr_table_setn(r->headers_out, "Content-Type", r->content_type);

    /* Formt the response headers for output. */

    elts = apr_table_elts(r->headers_out);
    if (elts->nelts != 0) {
        t_elt = (const apr_table_entry_t *)(elts->elts);
        t_end = t_elt + elts->nelts;
        vec2 = (struct iovec *)apr_palloc(r->pool, 4 * elts->nelts *
                                          sizeof(struct iovec));
        vec2_next = vec2;

        do {
            vec2_next->iov_base = (void*)(t_elt->key);
            vec2_next->iov_len = strlen(t_elt->key);
            vec2_next++;
            vec2_next->iov_base = ": ";
            vec2_next->iov_len = sizeof(": ") - 1;
            vec2_next++;
            vec2_next->iov_base = (void*)(t_elt->val);
            vec2_next->iov_len = strlen(t_elt->val);
            vec2_next++;
            vec2_next->iov_base = CRLF;
            vec2_next->iov_len = sizeof(CRLF) - 1;
            vec2_next++;
            t_elt++;
        } while (t_elt < t_end);

        apr_brigade_writev(b2, NULL, NULL, vec2, vec2_next - vec2);
    }

    /* Format terminating blank line for response headers. */

    buflen = strlen(crlf);
    apr_brigade_write(b2, NULL, NULL, crlf, buflen);

    /* Output the response headers. */

    ap_pass_brigade(f->next, b2);

    /* Remove ourselves from filter chain so we aren't called again. */

    ap_remove_output_filter(f);

    /* Output the partial response content. */

    return ap_pass_brigade(f->next, b);
}

typedef struct cve_2013_5704_fields cve_2013_5704_fields;
typedef struct cve_2013_5704_apache22 cve_2013_5704_apache22;
typedef struct cve_2013_5704_apache24 cve_2013_5704_apache24;

struct cve_2013_5704_fields {
    apr_table_t *trailers_in;
    apr_table_t *trailers_out;
};

struct cve_2013_5704_apache22 {
    struct ap_filter_t *proto_input_filters;
    int eos_sent;
    cve_2013_5704_fields fields;
};

struct cve_2013_5704_apache24 {
    apr_sockaddr_t *useragent_addr;
    char *useragent_ip;
    cve_2013_5704_fields fields;
};

static int wsgi_hook_daemon_handler(conn_rec *c)
{
    apr_socket_t *csd;
    request_rec *r;
    apr_pool_t *p;
    apr_status_t rv;

    char *key;
    apr_sockaddr_t *addr;

    const char *filename;
    const char *script;
    const char *magic;
    const char *hash;

    WSGIRequestConfig *config;

    apr_bucket *e;
    apr_bucket_brigade *bb;

    core_request_config *req_cfg;
    core_dir_config *d;

    ap_filter_t *current = NULL;
    ap_filter_t *next = NULL;

    const char *item;

    int queue_timeout_occurred = 0;

    apr_time_t daemon_start = 0;

#if ! (AP_MODULE_MAGIC_AT_LEAST(20120211, 37) || \
    (AP_SERVER_MAJORVERSION_NUMBER == 2 && \
     AP_SERVER_MINORVERSION_NUMBER <= 2 && \
     AP_MODULE_MAGIC_AT_LEAST(20051115, 36)))
    apr_size_t size = 0;
#endif

    /* Don't do anything if not in daemon process. */

    if (!wsgi_daemon_pool)
        return DECLINED;

    /*
     * Mark this as start of daemon process even though connection
     * setup has already been done. Otherwise need to carry through
     * a time value somehow.
     */

    daemon_start = apr_time_now();

    /*
     * Remove all input/output filters except the core filters.
     * This will ensure that any SSL filters we don't want are
     * removed. This is a bit of a hack. Only other option is to
     * duplicate the code for core input/output filters so can
     * avoid full Apache connection processing, which is what is
     * installed the SSL filters and possibly other filters for
     * logging etc.
     */

    current = c->input_filters;
    next = current->next;

    while (current) {
        if (current->frec == ap_core_input_filter_handle) {
            current = next;
            if (!current)
                break;
            next = current->next;
            continue;
        }

        ap_remove_input_filter(current);

        current = next;
        if (current)
            next = current->next;
    }

    current = c->output_filters;
    next = current->next;

    while (current) {
        if (current->frec == ap_core_output_filter_handle) {
            current = next;
            if (!current)
                break;
            next = current->next;
            continue;
        }

        ap_remove_output_filter(current);

        current = next;
        if (current)
            next = current->next;
    }

    /*
     * Create and populate our own request object. We allocate more
     * memory than we require here for the request_rec in order to
     * implement an opimistic hack for the case where mod_wsgi is built
     * against an Apache version prior to CVE-2013-6704 being applied to
     * it. If that Apache is upgraded but mod_wsgi not recompiled then
     * it will crash in daemon mode. We therefore use the extra space to
     * set the structure members which are added by CVE-2013-6704 to try
     * and avoid that situation. Note that this is distinct from the
     * hack down below to deal with where mod_wsgi was compiled against
     * an Apache version which had CVE-2013-6704 backported.
     */

    apr_pool_create(&p, c->pool);

    r = apr_pcalloc(p, sizeof(request_rec)+sizeof(cve_2013_5704_fields));

    r->pool = p;
    r->connection = c;
    r->server = c->base_server;

    r->user = NULL;
    r->ap_auth_type = NULL;

    r->allowed_methods = ap_make_method_list(p, 2);

    r->headers_in = apr_table_make(r->pool, 25);
    r->subprocess_env = apr_table_make(r->pool, 25);
    r->headers_out = apr_table_make(r->pool, 12);
    r->err_headers_out = apr_table_make(r->pool, 5);
    r->notes = apr_table_make(r->pool, 5);

    r->request_config  = ap_create_request_config(r->pool);

    r->proto_output_filters = c->output_filters;
    r->output_filters = r->proto_output_filters;
    r->proto_input_filters = c->input_filters;
    r->input_filters = r->proto_input_filters;

#if AP_MODULE_MAGIC_AT_LEAST(20120211, 37) || \
    (AP_SERVER_MAJORVERSION_NUMBER == 2 && \
     AP_SERVER_MINORVERSION_NUMBER <= 2 && \
     AP_MODULE_MAGIC_AT_LEAST(20051115, 36))

    /*
     * New request_rec fields were added to Apache because of changes
     * related to CVE-2013-5704. The change means that mod_wsgi version
     * 4.4.0-4.4.5 will crash if run on the Apache versions with the
     * addition fields if mod_wsgi daemon mode is used. If we are using
     * Apache 2.2.29 or 2.4.11, we set the fields direct against the
     * new structure members.
     */

    r->trailers_in = apr_table_make(r->pool, 5);
    r->trailers_out = apr_table_make(r->pool, 5);
#else
    /*
     * We use a huge hack here to try and identify when CVE-2013-5704
     * has been back ported to older Apache version. This is necessary
     * as when backported the Apache module magic number will not be
     * updated and it isn't possible to determine from that at compile
     * time if the new structure members exist and so that they should
     * be set. We therefore try and work out whether the extra structure
     * members exist through looking at the size of request_rec and
     * whether memory has been allocated above what is known to be the
     * last member in the structure before the new members were added.
     */

#if AP_SERVER_MINORVERSION_NUMBER <= 2
    size = offsetof(request_rec, eos_sent);
    size += sizeof(r->eos_sent);
#else
    size = offsetof(request_rec, useragent_ip);
    size += sizeof(r->useragent_ip);
#endif

    /*
     * Check whether request_rec is at least as large as minimal size
     * plus the size of the extra fields. If it is, then we need to
     * set the additional fields.
     */

    if (sizeof(request_rec) >= size + sizeof(cve_2013_5704_fields)) {
#if AP_SERVER_MINORVERSION_NUMBER <= 2
        cve_2013_5704_apache22 *rext;
        rext = (cve_2013_5704_apache22 *)&r->proto_input_filters;
#else
        cve_2013_5704_apache24 *rext;
        rext = (cve_2013_5704_apache24 *)&r->useragent_addr;
#endif

        rext->fields.trailers_in = apr_table_make(r->pool, 5);
        rext->fields.trailers_out = apr_table_make(r->pool, 5);
    }
    else {
        /*
         * Finally, to allow forward portability of a compiled mod_wsgi
         * binary from an Apache version without the CVE-2013-5704
         * change to one where it is, without needing to recompile
         * mod_wsgi, we set fields in the extra memory we added before
         * the actual request_rec.
         */

        cve_2013_5704_fields *rext;
        rext = (cve_2013_5704_fields *)(r+1);

        rext->trailers_in = apr_table_make(r->pool, 5);
        rext->trailers_out = apr_table_make(r->pool, 5);
    }
#endif

    r->per_dir_config  = r->server->lookup_defaults;

    /*
     * Try and ensure that request body limit in daemon mode process
     * is unlimited as Apache 2.4.54 changed rules for limit and if
     * unset is now overridden by HTTP filters to be 1GiB rather than
     * unlimited. This is required since we populate configuration
     * from the base server config only so setting unlimited in a more
     * specific context such as a virtual host wouldn't be visible.
     * Note that setting this to unlimited in the daemon mode process
     * is okay as the request limit body is checked in the Apache
     * child process before request is proxied specifically to avoid
     * unecessarily passing the content across to the daemon process.
     */

    d = (core_dir_config *)ap_get_core_module_config(r->per_dir_config);

    d->limit_req_body = 0;

    r->sent_bodyct = 0;

    r->read_length = 0;
    r->read_body = REQUEST_NO_BODY;

    r->status = HTTP_OK;
    r->status_line = NULL;
    r->the_request = NULL;

    r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;

    /*
     * Install our own output filter for writing back headers in
     * CGI script style.
     */

    ap_add_output_filter_handle(wsgi_header_filter_handle,
                                NULL, r, r->connection);

    /* Create and install the WSGI request config. */

    config = (WSGIRequestConfig *)apr_pcalloc(r->pool,
                                              sizeof(WSGIRequestConfig));
    ap_set_module_config(r->request_config, &wsgi_module, (void *)config);

    /* Grab the socket from the connection core config. */

    csd = ap_get_module_config(c->conn_config, &core_module);

    /*
     * Fake up parts of the internal per request core
     * configuration. If we don't do this then when Apache is
     * compiled with the symbol AP_DEBUG, internal checks made
     * by Apache will result in process crashing.
     */

    req_cfg = apr_pcalloc(r->pool, sizeof(core_request_config));

    req_cfg->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);

    ap_set_module_config(r->request_config, &core_module, req_cfg);

    /* Read in the request details and setup request object. */

    if ((rv = wsgi_read_request(csd, r)) != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server,
                     "mod_wsgi (pid=%d): Unable to read WSGI request.",
                     getpid());

        apr_pool_destroy(p);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* Check magic marker used to validate origin of request. */

    filename = apr_table_get(r->subprocess_env, "SCRIPT_FILENAME");
    script = apr_table_get(r->subprocess_env, "mod_wsgi.handler_script");

    magic = apr_table_get(r->subprocess_env, "mod_wsgi.magic");

    if (!magic) {
        ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server,
                     "mod_wsgi (pid=%d): Request origin could not be "
                     "validated.", getpid());

        apr_pool_destroy(p);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    key = apr_psprintf(r->pool, "%ld|%s|%s|%s",
                       wsgi_daemon_process->group->random,
                       wsgi_daemon_process->group->socket_path,
                       filename, script);
    hash = ap_md5(r->pool, (const unsigned char *)key);
    memset(key, '\0', strlen(key));

    if (strcmp(magic, hash) != 0) {
        ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server,
                     "mod_wsgi (pid=%d): Request origin could not be "
                     "validated.", getpid());

        apr_pool_destroy(p);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    apr_table_unset(r->subprocess_env, "mod_wsgi.magic");

    /*
     * If we are executing in a chroot environment, we need to
     * adjust SCRIPT_FILENAME to remove leading portion of path
     * that corresponds to the location of the chroot directory.
     * Also need to adjust DOCUMENT_ROOT as well, although in
     * that case if it doesn't actually fall within the choot
     * directory, we just delete it outright as would be incorrect
     * if that directory lay outside of the chroot directory.
     */

    if (wsgi_daemon_process->group->root) {
        const char *root;
        const char *path;

        root = wsgi_daemon_process->group->root;

        path = filename;

        if (strstr(path, root) == path && path[strlen(root)] == '/') {
            path += strlen(root);

            apr_table_set(r->subprocess_env, "SCRIPT_FILENAME", path);

            filename = path;
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server,
                         "mod_wsgi (pid=%d): WSGI script '%s' not located "
                         "within chroot directory '%s'.", getpid(), path, root);

            return HTTP_INTERNAL_SERVER_ERROR;
        }

        path = (char *)apr_table_get(r->subprocess_env, "DOCUMENT_ROOT");

        if (strstr(path, root) == path) {
            path += strlen(root);

            apr_table_set(r->subprocess_env, "DOCUMENT_ROOT", path);
        }
        else {
            apr_table_unset(r->subprocess_env, "DOCUMENT_ROOT");
        }
    }

    r->filename = (char *)filename;

    /* Recalculate WSGI script or handler script modification time. */

    if (script && *script) {
        if ((rv = apr_stat(&r->finfo, script, APR_FINFO_NORM,
                           r->pool)) != APR_SUCCESS) {
            /*
             * Don't fail at this point. Allow the lack of file to
             * be detected later when trying to load the script file.
             */

            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, wsgi_server,
                         "mod_wsgi (pid=%d): Unable to stat target handler "
                         "script '%s'.", getpid(), script);

            r->finfo.mtime = 0;
        }
    }
    else {
        if ((rv = apr_stat(&r->finfo, filename, APR_FINFO_NORM,
                           r->pool)) != APR_SUCCESS) {
            /*
             * Don't fail at this point. Allow the lack of file to
             * be detected later when trying to load the script file.
             */

            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, wsgi_server,
                         "mod_wsgi (pid=%d): Unable to stat target WSGI "
                         "script '%s'.", getpid(), filename);

            r->finfo.mtime = 0;
        }
    }

    /*
     * Trigger mapping of host information to server configuration
     * so that when logging errors they go to the correct error log
     * file for the host.
     */

#if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
    r->connection->client_ip = (char *)apr_table_get(r->subprocess_env,
                                                     "REMOTE_ADDR");
    r->connection->client_addr->port = atoi(apr_table_get(r->subprocess_env,
                                                          "REMOTE_PORT"));
#else
    r->connection->remote_ip = (char *)apr_table_get(r->subprocess_env,
                                                     "REMOTE_ADDR");
    r->connection->remote_addr->port = atoi(apr_table_get(r->subprocess_env,
                                                          "REMOTE_PORT"));
#endif

#if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
    r->useragent_addr = c->client_addr;
    r->useragent_ip = c->client_ip;
#endif

    key = apr_psprintf(p, "%s|%s",
                       apr_table_get(r->subprocess_env,
                                     "mod_wsgi.listener_host"),
                       apr_table_get(r->subprocess_env,
                                     "mod_wsgi.listener_port"));

    if (wsgi_server_config->verbose_debugging) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Server listener address '%s'.",
                     getpid(), key);
    }

    addr = (apr_sockaddr_t *)apr_hash_get(wsgi_daemon_listeners,
                                          key, APR_HASH_KEY_STRING);

    if (wsgi_server_config->verbose_debugging) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Server listener address '%s' was"
                     "%s found.", getpid(), key, addr ? "" : " not");
    }

    if (addr) {
        c->local_addr = addr;
    }

    ap_update_vhost_given_ip(r->connection);

    if (wsgi_server_config->verbose_debugging) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Connection server matched was "
                     "'%s|%d'.", getpid(), c->base_server->server_hostname,
                     c->base_server->port);
    }

    r->server = c->base_server;

    if (apr_table_get(r->subprocess_env, "HTTP_HOST")) {
        apr_table_setn(r->headers_in, "Host",
                       apr_table_get(r->subprocess_env, "HTTP_HOST"));
    }

    ap_update_vhost_from_headers(r);

    if (wsgi_server_config->verbose_debugging) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Request server matched was '%s|%d'.",
                     getpid(), r->server->server_hostname, r->server->port);
    }

    /*
     * Set content length of any request content and add the
     * standard HTTP input filter so that standard input routines
     * for request content will work.
     */

    item = apr_table_get(r->subprocess_env, "CONTENT_LENGTH");

    if (item)
        apr_table_setn(r->headers_in, "Content-Length", item);

    /* Set details of WSGI specific request config. */

    config->process_group = apr_table_get(r->subprocess_env,
                                          "mod_wsgi.process_group");
    config->application_group = apr_table_get(r->subprocess_env,
                                              "mod_wsgi.application_group");
    config->callable_object = apr_table_get(r->subprocess_env,
                                            "mod_wsgi.callable_object");

    config->handler_script = apr_table_get(r->subprocess_env,
                                           "mod_wsgi.handler_script");

    config->script_reloading = atoi(apr_table_get(r->subprocess_env,
                                                  "mod_wsgi.script_reloading"));

    item = apr_table_get(r->subprocess_env, "mod_wsgi.enable_sendfile");

    if (item && !strcasecmp(item, "1"))
        config->enable_sendfile = 1;
    else
        config->enable_sendfile = 0;

    item = apr_table_get(r->subprocess_env, "mod_wsgi.ignore_activity");

    if (item && !strcasecmp(item, "1"))
        config->ignore_activity = 1;
    else
        config->ignore_activity = 0;

    config->daemon_connects = atoi(apr_table_get(r->subprocess_env,
                                                 "mod_wsgi.daemon_connects"));
    config->daemon_restarts = atoi(apr_table_get(r->subprocess_env,
                                                 "mod_wsgi.daemon_restarts"));

    item = apr_table_get(r->subprocess_env, "mod_wsgi.request_start");

    if (item) {
        errno = 0;
        config->request_start = apr_strtoi64(item, (char **)&item, 10);

        if (!*item && errno != ERANGE)
            r->request_time = config->request_start;
        else
            config->request_start = 0.0;
    }

    item = apr_table_get(r->subprocess_env, "mod_wsgi.queue_start");

    if (item) {
        errno = 0;
        config->queue_start = apr_strtoi64(item, (char **)&item, 10);

        if (!(!*item && errno != ERANGE))
            config->queue_start = 0.0;
    }

    config->daemon_start = daemon_start;

    apr_table_setn(r->subprocess_env, "mod_wsgi.daemon_start",
                   apr_psprintf(r->pool, "%" APR_TIME_T_FMT,
                   config->daemon_start));

#if AP_MODULE_MAGIC_AT_LEAST(20100923,2)
    item = apr_table_get(r->subprocess_env, "mod_wsgi.request_id");

    if (item)
        r->log_id = item;

    item = apr_table_get(r->subprocess_env, "mod_wsgi.connection_id");

    if (item)
        r->connection->log_id = item;
#endif

    /*
     * Install the standard HTTP input filter and set header for
     * chunked transfer encoding to force it to dechunk the input.
     * This is necessary as we chunk the data that is proxied to
     * the daemon processes so that we can determining whether we
     * actually receive all input or it was truncated.
     *
     * Note that the subprocess_env table that gets passed to the
     * WSGI environ dictionary has already been populated, so the
     * Transfer-Encoding header will not be passed in the WSGI
     * environ dictionary as a result of this.
     */

    apr_table_setn(r->headers_in, "Transfer-Encoding", "chunked");

    ap_add_input_filter("HTTP_IN", NULL, r, r->connection);

    /* Check for queue timeout. */

    r->status = HTTP_OK;

    if (wsgi_daemon_process->group->queue_timeout) {
        if (config->request_start) {
            apr_time_t queue_time = 0;

            queue_time = config->daemon_start - config->request_start;

            if (queue_time > wsgi_daemon_process->group->queue_timeout) {
                queue_timeout_occurred = 1;

                r->status = HTTP_INTERNAL_SERVER_ERROR;
                r->status_line = "200 Timeout";

                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                             "mod_wsgi (pid=%d): Queue timeout expired "
                             "for WSGI daemon process '%s'.", getpid(),
                             wsgi_daemon_process->group->name);
            }
        }
    }

    /*
     * Execute the actual target WSGI application. In
     * normal cases OK should always be returned. If
     * however an error occurs in importing or executing
     * the script or the Python code raises an exception
     * which is not caught and handled, then an internal
     * server error can be returned. As we don't want to
     * be triggering any error document handlers in the
     * daemon process we use a fake status line with 0
     * as the status value. This will be picked up in
     * the Apache child process which will translate it
     * back to a 500 error so that normal error document
     * processing occurs.
     */

    if (!queue_timeout_occurred) {
        if (wsgi_execute_script(r) != OK) {
            r->status = HTTP_INTERNAL_SERVER_ERROR;
            r->status_line = "200 Error";
        }
    }

    /*
     * Ensure that request is finalised and any response
     * is flushed out. This will as a side effect read
     * any input data which wasn't consumed, thus
     * ensuring that the Apache child process isn't hung
     * waiting to send the request content and can
     * therefore process the response correctly.
     */

    ap_finalize_request_protocol(r);

    bb = apr_brigade_create(r->pool, c->bucket_alloc);
    e = apr_bucket_flush_create(c->bucket_alloc);
    APR_BRIGADE_INSERT_HEAD(bb, e);
    ap_pass_brigade(r->connection->output_filters, bb);

    apr_pool_destroy(p);

    return OK;
}

#endif

/*
 * Apache 2.X module initialisation functions.
 */

static int wsgi_hook_init(apr_pool_t *pconf, apr_pool_t *ptemp,
                          apr_pool_t *plog, server_rec *s)
{
    void *data = NULL;
    const char *userdata_key;
    char package[128];
    char interpreter[256];

    int status = OK;

    /*
     * No longer support using mod_python at the same time as
     * mod_wsgi as becoming too painful to hack around
     * mod_python's broken usage of threading APIs when align
     * code to the stricter API requirements of Python 3.2.
     */

    userdata_key = "python_init";

    apr_pool_userdata_get(&data, userdata_key, s->process->pool);
    if (data) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
                     "mod_wsgi (pid=%d): The mod_python module can "
                     "not be used in conjunction with mod_wsgi 4.0+. "
                     "Remove the mod_python module from the Apache "
                     "configuration.", getpid());

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /*
     * Init function gets called twice during startup, we only
     * need to actually do anything on the second time it is
     * called. This avoids unecessarily initialising and then
     * destroying Python for no reason. We also though have to
     * deal with a special case when a graceful restart is done.
     * For that we are only called once, which is generally okay
     * as the 'wsgi_init' key will be set from initial start up
     * of the server. The exception to this is where the module
     * is only loaded into Apache when the server is already
     * running. In this case we have to detect that it is not
     * the initial startup, but a subsequent restart. We can do
     * this by looking at whether the scoreboard has been
     * initialised yet. That is probably enough, but to be safe,
     * also check what generation it is.
     */

    userdata_key = "wsgi_init";

    apr_pool_userdata_get(&data, userdata_key, s->process->pool);

    if (!data) {
        apr_pool_userdata_set((const void *)1, userdata_key,
                              apr_pool_cleanup_null, s->process->pool);

        /*
         * Check for the special case of a graceful restart and
         * the module being loaded for the first time. In this
         * case we still go onto perform initialisation as the
         * initialisation routine for the module will not be
         * called a second time.
         */

        if (!ap_scoreboard_image ||
            ap_get_scoreboard_global()->running_generation == 0) {

            return OK;
        }
    }

    /* Setup module version information. */

    sprintf(package, "mod_wsgi/%s", MOD_WSGI_VERSION_STRING);

    ap_add_version_component(pconf, package);

    /* Record Python version string with Apache. */

    sprintf(interpreter, "Python/%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION);
    ap_add_version_component(pconf, interpreter);

    /* Retain reference to base server. */

    wsgi_server = s;

    /* Retain record of parent process ID. */

    wsgi_parent_pid = getpid();

    /* Determine whether multiprocess and/or multithread. */

    ap_mpm_query(AP_MPMQ_IS_THREADED, &wsgi_multithread);
    if (wsgi_multithread != AP_MPMQ_NOT_SUPPORTED) {
        ap_mpm_query(AP_MPMQ_MAX_THREADS, &wsgi_multithread);
        wsgi_multithread = (wsgi_multithread != 1);
    }

    ap_mpm_query(AP_MPMQ_IS_FORKED, &wsgi_multiprocess);
    if (wsgi_multiprocess != AP_MPMQ_NOT_SUPPORTED) {
        ap_mpm_query(AP_MPMQ_MAX_DAEMONS, &wsgi_multiprocess);
        wsgi_multiprocess = (wsgi_multiprocess != 1);
    }

    /* Retain reference to main server config. */

    wsgi_server_config = ap_get_module_config(s->module_config, &wsgi_module);

    /*
     * Check that the version of Python found at
     * runtime is what was used at compilation.
     *
     * XXX Can't do this as will cause Anaconda
     * Python to fail as not safe to call the
     * Py_GetVersion() function before one calls
     * the Py_Initialize() function when using
     * Anaconda Python.
     */

#if 0
    wsgi_python_version();
#endif

    /*
     * Initialise Python if required to be done in
     * the parent process. Note that it will not be
     * initialised if mod_python loaded and it has
     * already been done.
     */

    if (wsgi_python_required == -1)
        wsgi_python_required = 1;

    if (!wsgi_python_after_fork)
        wsgi_python_init(pconf);

    /*
     * Startup separate named daemon processes. This is
     * a bit tricky as we only want to do this after the
     * scoreboard has been created. On the initial server
     * startup though, this hook function is called prior
     * to the MPM being run, which means the scoreboard
     * hasn't been created yet. In that case we need to
     * defer process creation until after that, which we
     * can only do by hooking into the pre_mpm hook after
     * scoreboard creation has been done. On a server
     * restart, the scoreboard will be preserved, so we
     * can do it here, which is just as well as the pre_mpm
     * hook isn't run on a restart.
     */

#if defined(MOD_WSGI_WITH_DAEMONS)
    if (!ap_scoreboard_image) {
        /*
         * Need to remember the pool we were given here as
         * the pre_mpm hook functions get given a different
         * pool which isn't the one we want and if we use
         * that then Apache will crash when it is being
         * shutdown. So our pre_mpm hook will use the pool
         * we have remembered here.
         */

        wsgi_pconf_pool = pconf;

        ap_hook_pre_mpm(wsgi_deferred_start_daemons, NULL, NULL,
                        APR_HOOK_REALLY_LAST);
    }
    else
        status = wsgi_start_daemons(pconf);
#endif

    return status;
}

static void wsgi_hook_child_init(apr_pool_t *p, server_rec *s)
{
#if defined(MOD_WSGI_WITH_DAEMONS) 
    WSGIProcessGroup *entries = NULL;
    WSGIProcessGroup *entry = NULL;

    int i;

    /* Close listener sockets for daemon processes. */

    if (wsgi_daemon_list) {
        entries = (WSGIProcessGroup *)wsgi_daemon_list->elts;

        for (i = 0; i < wsgi_daemon_list->nelts; ++i) {
            entry = &entries[i];

            if (entry->listener_fd != -1) {
                close(entry->listener_fd);
                entry->listener_fd = -1;
            }
        }
    }
#endif

    /* Remember worker process ID. */

    wsgi_worker_pid = getpid();

    /* Time child process started waiting for requests. */

    wsgi_restart_time = apr_time_now();

    /* Create lock for request monitoring. */

    apr_thread_mutex_create(&wsgi_monitor_lock,
                            APR_THREAD_MUTEX_UNNESTED, p);

    if (wsgi_python_required) {
        /*
         * Initialise Python if required to be done in
         * the child process. Note that it will not be
         * initialised if mod_python loaded and it has
         * already been done.
         */

        if (wsgi_python_after_fork)
            wsgi_python_init(p);

        /*
         * Now perform additional initialisation steps
         * always done in child process.
         */

        wsgi_python_child_init(p);
    }
}

#include "apr_lib.h"

static char *wsgi_original_uri(request_rec *r)
{
    char *first, *last;

    if (r->the_request == NULL) {
        return (char *) apr_pcalloc(r->pool, 1);
    }

    first = r->the_request;     /* use the request-line */

    while (*first && !apr_isspace(*first)) {
        ++first;                /* skip over the method */
    }
    while (apr_isspace(*first)) {
        ++first;                /*   and the space(s)   */
    }

    last = first;
    while (*last && !apr_isspace(*last)) {
        ++last;                 /* end at next whitespace */
    }

    return apr_pstrmemdup(r->pool, first, last - first);
}

static int wsgi_http_invalid_header(const char *w)
{
    char c;

    while ((c = *w++) != 0) {
        if (!apr_isalnum(c) && c != '-')
            return 1;
    }

    return 0;
}

static void wsgi_drop_invalid_headers(request_rec *r)
{
    /*
     * Apache 2.2 when converting headers for CGI variables, doesn't
     * ignore headers with invalid names. That is, any which use any
     * characters besides alphanumerics and the '-' character. This
     * opens us up to header spoofing whereby something can inject
     * multiple headers which differ by using non alphanumeric
     * characters in the same position, which would then encode to same
     * value. Since not easy to cleanup after the fact, as a workaround,
     * is easier to simply remove the invalid headers. This will make
     * things end up being the same as Apache 2.4. Doing this could
     * annoy some users of Apache 2.2 who were using invalid headers,
     * but things will break for them under Apache 2.4 anyway.
     */

    apr_array_header_t *to_delete = NULL;

    const apr_array_header_t *hdrs_arr;
    const apr_table_entry_t *hdrs;

    int i;

    hdrs_arr = apr_table_elts(r->headers_in);
    hdrs = (const apr_table_entry_t *) hdrs_arr->elts;

    for (i = 0; i < hdrs_arr->nelts; ++i) {
        if (!hdrs[i].key) {
            continue;
        }

        if (wsgi_http_invalid_header(hdrs[i].key)) {
            char **new;

            if (!to_delete)
                to_delete = apr_array_make(r->pool, 1, sizeof(char *));

            new = (char **)apr_array_push(to_delete);
            *new = hdrs[i].key;
        }
    }

    if (to_delete) {
        char *key;

        for (i = 0; i < to_delete->nelts; i++) {
            key = ((char **)to_delete->elts)[i];

            apr_table_unset(r->headers_in, key);
        }
    }
}

static const char *wsgi_proxy_client_headers[] = {
    "HTTP_X_FORWARDED_FOR",
    "HTTP_X_CLIENT_IP",
    "HTTP_X_REAL_IP",
    NULL,
};

static const char *wsgi_proxy_scheme_headers[] = {
    "HTTP_X_FORWARDED_HTTPS",
    "HTTP_X_FORWARDED_PROTO",
    "HTTP_X_FORWARDED_SCHEME",
    "HTTP_X_FORWARDED_SSL",
    "HTTP_X_HTTPS",
    "HTTP_X_SCHEME",
    NULL,
};

static const char *wsgi_proxy_host_headers[] = {
    "HTTP_X_FORWARDED_HOST",
    "HTTP_X_HOST",
    NULL,
};

static const char *wsgi_proxy_script_name_headers[] = {
    "HTTP_X_SCRIPT_NAME",
    "HTTP_X_FORWARDED_SCRIPT_NAME",
    NULL,
};

static int wsgi_ip_is_in_array(apr_sockaddr_t *client_ip,
                               apr_array_header_t *proxy_ips) {
    int i;
    apr_ipsubnet_t **subs = (apr_ipsubnet_t **)proxy_ips->elts;

    for (i = 0; i < proxy_ips->nelts; i++) {
        if (apr_ipsubnet_test(subs[i], client_ip)) {
            return 1;
        }
    }

    return 0;
}

static void wsgi_process_forwarded_for(request_rec *r,
                                       WSGIRequestConfig *config,
                                       const char *value
)
{
    if (config->trusted_proxies) {
        /*
         * A potentially comma separated list where client we are
         * interested in will be that immediately before the last
         * trusted proxy working from the end forwards. If there
         * are no trusted proxies then we use the last.
         */

        apr_array_header_t *arr;

        arr = apr_array_make(r->pool, 3, sizeof(char *));

        while (*value != '\0') {
            /* Skip leading whitespace for item. */

            while (*value != '\0' && apr_isspace(*value))
                value++;

            if (*value != '\0') {
                const char *end = NULL;
                const char *next = NULL;

                char **entry = NULL;

                end = value;

                while (*end != '\0' && *end != ',')
                    end++;

                if (*end == '\0')
                    next = end;
                else if (*end == ',')
                    next = end+1;

                /* Need deal with trailing whitespace. */

                while (end != value) {
                    if (!apr_isspace(*(end-1)))
                        break;

                    end--;
                }

                entry = (char **)apr_array_push(arr);
                *entry = apr_pstrndup(r->pool, value, (end-value));

                value = next;
            }
        }

        if (arr->nelts != 0) {
            /* HTTP_X_FORDWARDED_FOR wasn't just an empty string. */

            char **items;
            int first = -1;
            int i;

            items = (char **)arr->elts;

            /*
             * Work out the position of the IP closest to the start
             * that we actually trusted.
             */

            for (i=arr->nelts; i>0; ) {
                apr_sockaddr_t *sa;
                apr_status_t rv;

                i--;

                rv = apr_sockaddr_info_get(&sa, items[i], APR_UNSPEC,
                                           0, 0, r->pool);

                if (rv == APR_SUCCESS) {
                    if (!wsgi_ip_is_in_array(sa, config->trusted_proxies))
                        break;

                    first = i;
                }
                else {
                    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, 0, r,
                              "mod_wsgi (pid=%d): Forwarded IP of \"%s\" is "
                              "not a valid IP address.", getpid(), items[i]);
                    break;
                }
            }

            if (first >= 0) {
                /*
                 * We found at least one trusted IP. We use the
                 * IP that may have appeared before that as
                 * REMOTE_ADDR. We rewrite HTTP_X_FORWARDED_FOR
                 * to record only from REMOTE_ADDR onwards.
                 */

                char *list;

                i = first-1;
                if (i<0)
                    i = 0;

                apr_table_setn(r->subprocess_env, "REMOTE_ADDR", items[i]);

                list = items[i];

                i++;

                while (arr->nelts != i) {
                    list = apr_pstrcat(r->pool, list, ", ", items[i], NULL);
                    i++;
                }

                apr_table_setn(r->subprocess_env, "HTTP_X_FORWARDED_FOR",
                               list);
            }
            else {
                /*
                 * No trusted IP. Use the last for REMOTE_ADDR.
                 * We rewrite HTTP_X_FORWARDED_FOR to record only
                 * the last.
                 */

                apr_table_setn(r->subprocess_env, "REMOTE_ADDR",
                        items[arr->nelts-1]);
                apr_table_setn(r->subprocess_env, "HTTP_X_FORWARDED_FOR",
                        items[arr->nelts-1]);
            }
        }
    }
    else {
        /*
         * We do not need to validate the proxies. We will have a
         * potentially comma separated list where the client we
         * are interested in will be listed first.
         */

        const char *end = NULL;

        /* Skip leading whitespace for item. */

        while (*value != '\0' && apr_isspace(*value))
            value++;

        if (*value != '\0') {
            end = value;

            while (*end != '\0' && *end != ',')
                end++;

            /* Need deal with trailing whitespace. */

            while (end != value) {
                if (!apr_isspace(*(end-1)))
                    break;

                end--;
            }

            /* Override REMOTE_ADDR. Leave HTTP_X_FORWARDED_FOR. */

            apr_table_setn(r->subprocess_env, "REMOTE_ADDR",
                    apr_pstrndup(r->pool, value, (end-value)));
        }
    }
}

static void wsgi_process_proxy_headers(request_rec *r)
{
    WSGIRequestConfig *config = NULL;

    apr_array_header_t *trusted_proxy_headers = NULL;

    int match_client_header = 0;
    int match_host_header = 0;
    int match_script_name_header = 0;
    int match_scheme_header = 0;

    const char *trusted_client_header = NULL;
    const char *trusted_host_header = NULL;
    const char *trusted_script_name_header = NULL;
    const char *trusted_scheme_header = NULL;

    int i = 0;

    int trusted_proxy = 1;

    const char *client_ip = NULL;

    apr_status_t rv;

    config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
                                                       &wsgi_module);

    trusted_proxy_headers = config->trusted_proxy_headers;

    /* Nothing to do if no trusted headers have been specified. */

    if (!trusted_proxy_headers)
        return;

    /*
     * Check for any special processing required for each trusted
     * header which has been specified. We should only do this if
     * there was no list of trusted proxies, or if the client IP
     * was that of a trusted proxy.
     */

    if (config->trusted_proxies) {
        client_ip = apr_table_get(r->subprocess_env, "REMOTE_ADDR");

        if (client_ip) {
            apr_sockaddr_t *sa;

            rv = apr_sockaddr_info_get(&sa, client_ip, APR_UNSPEC,
                                       0, 0, r->pool);

            if (rv == APR_SUCCESS) {
                if (!wsgi_ip_is_in_array(sa, config->trusted_proxies))
                    trusted_proxy = 0;
            }
            else {
                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                              "mod_wsgi (pid=%d): REMOTE_ADDR of \"%s\" is "
                              "not a valid IP address.", getpid(), client_ip);

                trusted_proxy = 0;
            }
        }
        else
            trusted_proxy = 0;
    }

    if (trusted_proxy) {
        for (i=0; i<trusted_proxy_headers->nelts; i++) {
            const char *name;
            const char *value;

            name = ((const char**)trusted_proxy_headers->elts)[i];
            value = apr_table_get(r->subprocess_env, name);

            if (!strcmp(name, "HTTP_X_FORWARDED_FOR")) {
                match_client_header = 1;

                if (value) {
                    wsgi_process_forwarded_for(r, config, value);

                    trusted_client_header = name;
                }
            }
            else if (!strcmp(name, "HTTP_X_CLIENT_IP") ||
                    !strcmp(name, "HTTP_X_REAL_IP")) {

                match_client_header = 1;

                if (value) {
                    /* Use the value as is. */

                    apr_table_setn(r->subprocess_env, "REMOTE_ADDR", value);

                    trusted_client_header = name;
                }
            }
            else if (!strcmp(name, "HTTP_X_FORWARDED_HOST") ||
                     !strcmp(name, "HTTP_X_HOST")) {

                match_host_header = 1;

                if (value) {
                    /* Use the value as is. May include a port. */

                    trusted_host_header = name;

                    apr_table_setn(r->subprocess_env, "HTTP_HOST", value);
                }
            }
            else if (!strcmp(name, "HTTP_X_FORWARDED_SERVER")) {
                if (value) {
                    /* Use the value as is. */

                    apr_table_setn(r->subprocess_env, "SERVER_NAME", value);
                }
            }
            else if (!strcmp(name, "HTTP_X_FORWARDED_PORT")) {
                if (value) {
                    /* Use the value as is. */

                    apr_table_setn(r->subprocess_env, "SERVER_PORT", value);
                }
            }
            else if (!strcmp(name, "HTTP_X_SCRIPT_NAME") ||
                     !strcmp(name, "HTTP_X_FORWARDED_SCRIPT_NAME")) {

                match_script_name_header = 1;

                if (value) {
                    /*
                     * Use the value as is. We want to remember what the
                     * original value for SCRIPT_NAME was though.
                     */

                    apr_table_setn(r->subprocess_env, "mod_wsgi.mount_point",
                                   value);

                    trusted_script_name_header = name;

                    apr_table_setn(r->subprocess_env, "SCRIPT_NAME", value);
                }
            }
            else if (!strcmp(name, "HTTP_X_FORWARDED_PROTO") ||
                !strcmp(name, "HTTP_X_FORWARDED_SCHEME") ||
                !strcmp(name, "HTTP_X_SCHEME")) {

                match_scheme_header = 1;

                if (value) {
                    trusted_scheme_header = name;

                    /* Value can be either 'http' or 'https'. */

                    if (!strcasecmp(value, "https"))
                        apr_table_setn(r->subprocess_env, "HTTPS", "1");
                    else if (!strcasecmp(value, "http"))
                        apr_table_unset(r->subprocess_env, "HTTPS");
                }
            }
            else if (!strcmp(name, "HTTP_X_FORWARDED_HTTPS") ||
                     !strcmp(name, "HTTP_X_FORWARDED_SSL") ||
                     !strcmp(name, "HTTP_X_HTTPS")) {

                match_scheme_header = 1;

                if (value) {
                    trusted_scheme_header = name;

                    /*
                     * Value can be a boolean like flag such as 'On',
                     * 'Off', 'true', 'false', '1' or '0'.
                     */

                    if (!strcasecmp(value, "On") ||
                        !strcasecmp(value, "true") ||
                        !strcasecmp(value, "1")) {

                        apr_table_setn(r->subprocess_env, "HTTPS", "1");
                    }
                    else if (!strcasecmp(value, "Off") ||
                        !strcasecmp(value, "false") ||
                        !strcasecmp(value, "0")) {

                        apr_table_unset(r->subprocess_env, "HTTPS");
                    }
                }
            }
        }
    }
    else {
        /*
         * If it isn't a trusted proxy, we still need to knock
         * out any headers for categories we were interested in.
         */

        for (i=0; i<trusted_proxy_headers->nelts; i++) {
            const char *name;

            name = ((const char**)trusted_proxy_headers->elts)[i];

            if (!strcmp(name, "HTTP_X_FORWARDED_FOR") ||
                     !strcmp(name, "HTTP_X_CLIENT_IP") ||
                     !strcmp(name, "HTTP_X_REAL_IP")) {

                match_client_header = 1;
            }
            else if (!strcmp(name, "HTTP_X_FORWARDED_HOST") ||
                     !strcmp(name, "HTTP_X_HOST")) {

                match_host_header = 1;
            }
            else if (!strcmp(name, "HTTP_X_SCRIPT_NAME") ||
                     !strcmp(name, "HTTP_X_FORWARDED_SCRIPT_NAME")) {

                match_script_name_header = 1;
            }
            else if (!strcmp(name, "HTTP_X_FORWARDED_PROTO") ||
                !strcmp(name, "HTTP_X_FORWARDED_SCHEME") ||
                !strcmp(name, "HTTP_X_SCHEME") ||
                !strcmp(name, "HTTP_X_FORWARDED_HTTPS") ||
                !strcmp(name, "HTTP_X_FORWARDED_SSL") ||
                !strcmp(name, "HTTP_X_HTTPS")) {

                match_scheme_header = 1;
            }
        }
    }

    /*
     * Remove all client IP headers from request environment which
     * weren't matched as being trusted.
     */

    if (match_client_header) {
        const char *name = NULL;

        for (i=0; (name=wsgi_proxy_client_headers[i]); i++) {
            if (!trusted_client_header || strcmp(name, trusted_client_header)) {
                apr_table_unset(r->subprocess_env, name);
            }
        }
    }

    /*
     * Remove all proxy scheme headers from request environment
     * which weren't matched as being trusted.
     */

    if (match_scheme_header) {
        const char *name = NULL;

        for (i=0; (name=wsgi_proxy_scheme_headers[i]); i++) {
            if (!trusted_scheme_header || strcmp(name, trusted_scheme_header)) {
                apr_table_unset(r->subprocess_env, name);
            }
        }
    }

    /*
     * Remove all proxy host from request environment which weren't
     * matched as being trusted.
     */

    if (match_host_header) {
        const char *name = NULL;

        for (i=0; (name=wsgi_proxy_host_headers[i]); i++) {
            if (!trusted_host_header || strcmp(name, trusted_host_header))
                apr_table_unset(r->subprocess_env, name);
        }
    }

    /*
     * Remove all proxy script name headers from request environment
     * which weren't matched as being trusted.
     */

    if (match_script_name_header) {
        const char *name = NULL;

        for (i=0; (name=wsgi_proxy_script_name_headers[i]); i++) {
            if (!trusted_script_name_header ||
                strcmp(name, trusted_script_name_header)) {
                apr_table_unset(r->subprocess_env, name);
            }
        }
    }
}

static char *wsgi_http2env(apr_pool_t *a, const char *w)
{
    char *res = (char *)apr_palloc(a, sizeof("HTTP_") + strlen(w));
    char *cp = res;
    char c;

    *cp++ = 'H';
    *cp++ = 'T';
    *cp++ = 'T';
    *cp++ = 'P';
    *cp++ = '_';

    while ((c = *w++) != 0) {
        if (apr_isalnum(c)) {
            *cp++ = apr_toupper(c);
        }
        else if (c == '-') {
            *cp++ = '_';
        }
        else
            return NULL;
    }
    *cp = 0;

    return res;
}

typedef struct {
        PyObject_HEAD
        request_rec *r;
        WSGIRequestConfig *config;
        PyObject *log;
} AuthObject;

static AuthObject *newAuthObject(request_rec *r, WSGIRequestConfig *config)
{
    AuthObject *self;

    self = PyObject_New(AuthObject, &Auth_Type);
    if (self == NULL)
        return NULL;

    self->config = config;

    self->r = r;

    self->log = newLogObject(r, APLOG_ERR, NULL, 0);

    return self;
}

static void Auth_dealloc(AuthObject *self)
{
    Py_DECREF(self->log);

    PyObject_Del(self);
}

static PyObject *Auth_environ(AuthObject *self, const char *group)
{
    PyObject *vars;
    PyObject *object;

    request_rec *r = self->r;
    server_rec *s = r->server;
    conn_rec *c = r->connection;
    apr_port_t rport;

    const apr_array_header_t *hdrs_arr;
    const apr_table_entry_t *hdrs;

    const char *value = NULL;

    int i;

    vars = PyDict_New();

    hdrs_arr = apr_table_elts(r->headers_in);
    hdrs = (const apr_table_entry_t *) hdrs_arr->elts;

    for (i = 0; i < hdrs_arr->nelts; ++i) {
        if (!hdrs[i].key) {
            continue;
        }

        if (!strcasecmp(hdrs[i].key, "Content-type")) {
#if PY_MAJOR_VERSION >= 3
            object = PyUnicode_DecodeLatin1(hdrs[i].val,
                                            strlen(hdrs[i].val), NULL);
#else
            object = PyString_FromString(hdrs[i].val);
#endif
            PyDict_SetItemString(vars, "CONTENT_TYPE", object);
            Py_DECREF(object);
        }
        else if (!strcasecmp(hdrs[i].key, "Content-length")) {
#if PY_MAJOR_VERSION >= 3
            object = PyUnicode_DecodeLatin1(hdrs[i].val,
                                            strlen(hdrs[i].val), NULL);
#else
            object = PyString_FromString(hdrs[i].val);
#endif
            PyDict_SetItemString(vars, "CONTENT_LENGTH", object);
            Py_DECREF(object);
        }
        else if (!strcasecmp(hdrs[i].key, "Authorization")
                 || !strcasecmp(hdrs[i].key, "Proxy-Authorization")) {
            continue;
        }
        else {
            if (hdrs[i].val) {
                char *header = wsgi_http2env(r->pool, hdrs[i].key);

                if (header) {
#if PY_MAJOR_VERSION >= 3
                    object = PyUnicode_DecodeLatin1(hdrs[i].val,
                                                    strlen(hdrs[i].val), NULL);
#else
                    object = PyString_FromString(hdrs[i].val);
#endif

                    PyDict_SetItemString(vars, header, object);

                    Py_DECREF(object);
                }
            }
        }
    }

    value = ap_psignature("", r);
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "SERVER_SIGNATURE", object);
    Py_DECREF(object);

#if AP_MODULE_MAGIC_AT_LEAST(20060905,0)
    value = ap_get_server_banner();
#else
    value = ap_get_server_version();
#endif
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "SERVER_SOFTWARE", object);
    Py_DECREF(object);

    value = ap_escape_html(r->pool, ap_get_server_name(r));
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "SERVER_NAME", object);
    Py_DECREF(object);

    if (r->connection->local_ip) {
        value = r->connection->local_ip;
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "SERVER_ADDR", object);
        Py_DECREF(object);
    }

    value = apr_psprintf(r->pool, "%u", ap_get_server_port(r));
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "SERVER_PORT", object);
    Py_DECREF(object);

    value = ap_get_remote_host(c, r->per_dir_config, REMOTE_HOST, NULL);
    if (value) {
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "REMOTE_HOST", object);
        Py_DECREF(object);
    }

#if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
    if (r->useragent_ip) {
        value = r->useragent_ip;
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "REMOTE_ADDR", object);
        Py_DECREF(object);
    }
#else
    if (c->remote_ip) {
        value = c->remote_ip;
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "REMOTE_ADDR", object);
        Py_DECREF(object);
    }
#endif

#if PY_MAJOR_VERSION >= 3
    value = ap_document_root(r);
    object = PyUnicode_Decode(value, strlen(value),
                             Py_FileSystemDefaultEncoding,
                             "surrogateescape");
#else
    object = PyString_FromString(ap_document_root(r));
#endif
    PyDict_SetItemString(vars, "DOCUMENT_ROOT", object);
    Py_DECREF(object);

    if (s->server_admin) {
        value = s->server_admin;
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "SERVER_ADMIN", object);
        Py_DECREF(object);
    }

#if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
    rport = c->client_addr->port;
    value = apr_itoa(r->pool, rport);
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "REMOTE_PORT", object);
    Py_DECREF(object);
#else
    rport = c->remote_addr->port;
    value = apr_itoa(r->pool, rport);
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "REMOTE_PORT", object);
    Py_DECREF(object);
#endif

    value = r->protocol;
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "SERVER_PROTOCOL", object);
    Py_DECREF(object);

    value = r->method;
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "REQUEST_METHOD", object);
    Py_DECREF(object);

    value = r->args ? r->args : "";
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "QUERY_STRING", object);
    Py_DECREF(object);

    value = wsgi_original_uri(r);
#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    object = PyString_FromString(value);
#endif
    PyDict_SetItemString(vars, "REQUEST_URI", object);
    Py_DECREF(object);

    /*
     * XXX Apparently webdav does actually do modifications to
     * the uri and path_info attributes of request and they
     * could be used as part of authorisation.
     */

    if (!strcmp(r->protocol, "INCLUDED")) {
        value = r->uri;
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "SCRIPT_NAME", object);
        Py_DECREF(object);

        value = r->path_info ? r->path_info : "";
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "PATH_INFO", object);
        Py_DECREF(object);
    }
    else if (!r->path_info || !*r->path_info) {
        value = r->uri;
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "SCRIPT_NAME", object);
        Py_DECREF(object);

        value = "";
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "PATH_INFO", object);
        Py_DECREF(object);
    }
    else {
        int path_info_start = ap_find_path_info(r->uri, r->path_info);
        value = apr_pstrndup(r->pool, r->uri, path_info_start);
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "SCRIPT_NAME", object);
        Py_DECREF(object);

        value = r->path_info ? r->path_info : "";
#if PY_MAJOR_VERSION >= 3
        object = PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
        object = PyString_FromString(value);
#endif
        PyDict_SetItemString(vars, "PATH_INFO", object);
        Py_DECREF(object);
    }

    object = Py_BuildValue("(iii)", AP_SERVER_MAJORVERSION_NUMBER,
                           AP_SERVER_MINORVERSION_NUMBER,
                           AP_SERVER_PATCHLEVEL_NUMBER);
    PyDict_SetItemString(vars, "apache.version", object);
    Py_DECREF(object);

    object = Py_BuildValue("(iii)", MOD_WSGI_MAJORVERSION_NUMBER,
                           MOD_WSGI_MINORVERSION_NUMBER,
                           MOD_WSGI_MICROVERSION_NUMBER);
    PyDict_SetItemString(vars, "mod_wsgi.version", object);
    Py_DECREF(object);

#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_FromString("");
#else
    object = PyString_FromString("");
#endif
    PyDict_SetItemString(vars, "mod_wsgi.process_group", object);
    Py_DECREF(object);

#if PY_MAJOR_VERSION >= 3
    object = PyUnicode_DecodeLatin1(group, strlen(group), NULL);
#else
    object = PyString_FromString(group);
#endif
    PyDict_SetItemString(vars, "mod_wsgi.application_group", object);
    Py_DECREF(object);

    object = PyLong_FromLong(self->config->script_reloading);
    PyDict_SetItemString(vars, "mod_wsgi.script_reloading", object);
    Py_DECREF(object);

    /*
     * Setup log object for WSGI errors. Don't decrement
     * reference to log object as keep reference to it.
     */

    object = (PyObject *)self->log;
    PyDict_SetItemString(vars, "wsgi.errors", object);

    /*
     * If Apache extensions are enabled add a CObject reference
     * to the Apache request_rec structure instance.
     */

    if (!wsgi_daemon_pool && self->config->pass_apache_request) {
#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2) || \
    (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7)
        object = PyCapsule_New(self->r, 0, 0);
#else
        object = PyCObject_FromVoidPtr(self->r, 0);
#endif
        PyDict_SetItemString(vars, "apache.request_rec", object);
        Py_DECREF(object);
    }

    /*
     * Extensions for accessing SSL certificate information from
     * mod_ssl when in use.
     */

    object = PyObject_GetAttrString((PyObject *)self, "ssl_is_https");
    PyDict_SetItemString(vars, "mod_ssl.is_https", object);
    Py_DECREF(object);

    object = PyObject_GetAttrString((PyObject *)self, "ssl_var_lookup");
    PyDict_SetItemString(vars, "mod_ssl.var_lookup", object);
    Py_DECREF(object);

    return vars;
}

static PyObject *Auth_ssl_is_https(AuthObject *self, PyObject *args)
{
    APR_OPTIONAL_FN_TYPE(ssl_is_https) *ssl_is_https = 0;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    if (!PyArg_ParseTuple(args, ":ssl_is_https"))
        return NULL;

    ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);

    if (ssl_is_https == 0)
      return Py_BuildValue("i", 0);

    return Py_BuildValue("i", ssl_is_https(self->r->connection));
}

static PyObject *Auth_ssl_var_lookup(AuthObject *self, PyObject *args)
{
    APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *ssl_var_lookup = 0;

    PyObject *item = NULL;
    PyObject *latin_item = NULL;

    char *name = 0;
    char *value = 0;

    if (!self->r) {
        PyErr_SetString(PyExc_RuntimeError, "request object has expired");
        return NULL;
    }

    if (!PyArg_ParseTuple(args, "O:ssl_var_lookup", &item))
        return NULL;

#if PY_MAJOR_VERSION >= 3
    if (PyUnicode_Check(item)) {
        latin_item = PyUnicode_AsLatin1String(item);
        if (!latin_item) {
            PyErr_Format(PyExc_TypeError, "byte string value expected, "
                         "value containing non 'latin-1' characters found");

            return NULL;
        }

        item = latin_item;
    }
#endif

    if (!PyString_Check(item)) {
        PyErr_Format(PyExc_TypeError, "byte string value expected, value "
                     "of type %.200s found", item->ob_type->tp_name);

        Py_XDECREF(latin_item);

        return NULL;
    }

    name = PyString_AsString(item);

    ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);

    if (ssl_var_lookup == 0)
    {
        Py_XDECREF(latin_item);

        Py_INCREF(Py_None);

        return Py_None;
    }

    value = ssl_var_lookup(self->r->pool, self->r->server,
                           self->r->connection, self->r, name);

    Py_XDECREF(latin_item);

    if (!value) {
        Py_INCREF(Py_None);

        return Py_None;
    }

#if PY_MAJOR_VERSION >= 3
    return PyUnicode_DecodeLatin1(value, strlen(value), NULL);
#else
    return PyString_FromString(value);
#endif
}

static PyMethodDef Auth_methods[] = {
    { "ssl_is_https",   (PyCFunction)Auth_ssl_is_https, METH_VARARGS, 0 },
    { "ssl_var_lookup", (PyCFunction)Auth_ssl_var_lookup, METH_VARARGS, 0 },
    { NULL, NULL}
};

static PyTypeObject Auth_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "mod_wsgi.Auth",        /*tp_name*/
    sizeof(AuthObject),     /*tp_basicsize*/
    0,                      /*tp_itemsize*/
    /* methods */
    (destructor)Auth_dealloc, /*tp_dealloc*/
    0,                      /*tp_print*/
    0,                      /*tp_getattr*/
    0,                      /*tp_setattr*/
    0,                      /*tp_compare*/
    0,                      /*tp_repr*/
    0,                      /*tp_as_number*/
    0,                      /*tp_as_sequence*/
    0,                      /*tp_as_mapping*/
    0,                      /*tp_hash*/
    0,                      /*tp_call*/
    0,                      /*tp_str*/
    0,                      /*tp_getattro*/
    0,                      /*tp_setattro*/
    0,                      /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,     /*tp_flags*/
    0,                      /*tp_doc*/
    0,                      /*tp_traverse*/
    0,                      /*tp_clear*/
    0,                      /*tp_richcompare*/
    0,                      /*tp_weaklistoffset*/
    0,                      /*tp_iter*/
    0,                      /*tp_iternext*/
    Auth_methods,           /*tp_methods*/
    0,                      /*tp_members*/
    0,                      /*tp_getset*/
    0,                      /*tp_base*/
    0,                      /*tp_dict*/
    0,                      /*tp_descr_get*/
    0,                      /*tp_descr_set*/
    0,                      /*tp_dictoffset*/
    0,                      /*tp_init*/
    0,                      /*tp_alloc*/
    0,                      /*tp_new*/
    0,                      /*tp_free*/
    0,                      /*tp_is_gc*/
};

#if defined(MOD_WSGI_WITH_AUTHN_PROVIDER)
static authn_status wsgi_check_password(request_rec *r, const char *user,
                                        const char *password)
{
    WSGIRequestConfig *config;

    InterpreterObject *interp = NULL;
    PyObject *modules = NULL;
    PyObject *module = NULL;
    char *name = NULL;
    int exists = 0;

    const char *script;
    const char *group;

    authn_status status;

    config = wsgi_create_req_config(r->pool, r);

    if (!config->auth_user_script) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Location of WSGI user "
                     "authentication script not provided.", getpid());

        return AUTH_GENERAL_ERROR;
    }

    /*
     * Acquire the desired python interpreter. Once this is done
     * it is safe to start manipulating python objects.
     */

    script = config->auth_user_script->handler_script;
    group = wsgi_server_group(r, config->auth_user_script->application_group);

    interp = wsgi_acquire_interpreter(group);

    if (!interp) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "mod_wsgi (pid=%d): Cannot acquire interpreter '%s'.",
                      getpid(), group);

        return AUTH_GENERAL_ERROR;
    }

    /* Calculate the Python module name to be used for script. */

    name = wsgi_module_name(r->pool, script);

    /*
     * Use a lock around the check to see if the module is
     * already loaded and the import of the module to prevent
     * two request handlers trying to import the module at the
     * same time.
     */

#if APR_HAS_THREADS
    if (config->script_reloading) {
        Py_BEGIN_ALLOW_THREADS
        apr_thread_mutex_lock(wsgi_module_lock);
        Py_END_ALLOW_THREADS
    }
#endif

    modules = PyImport_GetModuleDict();
    module = PyDict_GetItemString(modules, name);

    Py_XINCREF(module);

    if (module)
        exists = 1;

    /*
     * If script reloading is enabled and the module for it has
     * previously been loaded, see if it has been modified since
     * the last time it was accessed.
     */

    if (module && config->script_reloading) {
        if (wsgi_reload_required(r->pool, r, script, module, NULL)) {
            /*
             * Script file has changed. Only support module
             * reloading for authentication scripts. Remove the
             * module from the modules dictionary before
             * reloading it again. If code is executing within
             * the module at the time, the callers reference
             * count on the module should ensure it isn't
             * actually destroyed until it is finished.
             */

            Py_DECREF(module);
            module = NULL;

            PyDict_DelItemString(modules, name);
        }
    }

    if (!module) {
        module = wsgi_load_source(r->pool, r, name, exists, script,
                                  "", group, 0);
    }

    /* Safe now to release the module lock. */

#if APR_HAS_THREADS
    if (config->script_reloading)
        apr_thread_mutex_unlock(wsgi_module_lock);
#endif

    /* Log any details of exceptions if import failed. */

    if (PyErr_Occurred())
        wsgi_log_python_error(r, NULL, script, 0);

    /* Assume an internal server error unless everything okay. */

    status = AUTH_GENERAL_ERROR;

    /* Determine if script exists and execute it. */

    if (module) {
        PyObject *module_dict = NULL;
        PyObject *object = NULL;

        module_dict = PyModule_GetDict(module);
        object = PyDict_GetItemString(module_dict, "check_password");

        if (object) {
            PyObject *vars = NULL;
            PyObject *args = NULL;
            PyObject *result = NULL;
            PyObject *method = NULL;

            AuthObject *adapter = NULL;

            adapter = newAuthObject(r, config);

            if (adapter) {
                vars = Auth_environ(adapter, group);

                Py_INCREF(object);
                args = Py_BuildValue("(Oss)", vars, user, password);
                result = PyObject_CallObject(object, args);
                Py_DECREF(args);
                Py_DECREF(object);
                Py_DECREF(vars);

                if (result) {
                    if (result == Py_None) {
                        status = AUTH_USER_NOT_FOUND;
                    }
                    else if (result == Py_True) {
                        status = AUTH_GRANTED;
                    }
                    else if (result == Py_False) {
                        status = AUTH_DENIED;
                    }
#if PY_MAJOR_VERSION >= 3
                    else if (PyUnicode_Check(result)) {
                        PyObject *str = NULL;

                        str = PyUnicode_AsUTF8String(result);

                        if (str) {
                            adapter->r->user = apr_pstrdup(adapter->r->pool,
                                    PyString_AsString(str));

                            status = AUTH_GRANTED;
                        }
                    }
#else
                    else if (PyString_Check(result)) {
                        adapter->r->user = apr_pstrdup(adapter->r->pool,
                                PyString_AsString(result));

                        status = AUTH_GRANTED;
                    }
#endif
                    else {
                        PyErr_SetString(PyExc_TypeError, "Basic auth "
                                        "provider must return True, False "
                                        "None or user name as string");
                    }

                    Py_DECREF(result);
                }

                /*
                 * Wipe out references to Apache request object
                 * held by Python objects, so can detect when an
                 * application holds on to the transient Python
                 * objects beyond the life of the request and
                 * thus raise an exception if they are used.
                 */

                adapter->r = NULL;

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                /* Close the log object so data is flushed. */

                method = PyObject_GetAttrString(adapter->log, "close");

                if (!method) {
                    PyErr_Format(PyExc_AttributeError,
                                 "'%s' object has no attribute 'close'",
                                 adapter->log->ob_type->tp_name);
                }
                else {
                    result = PyObject_CallObject(method, NULL);
                    Py_XDECREF(result);
                }

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                Py_XDECREF(method);

                /* No longer need adapter object. */

                Py_DECREF((PyObject *)adapter);
            }
        }
        else {
            Py_BEGIN_ALLOW_THREADS
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "mod_wsgi (pid=%d): Target WSGI user "
                          "authentication script '%s' does not provide "
                          "'Basic' auth provider.", getpid(), script);
            Py_END_ALLOW_THREADS
        }
    }

    /* Cleanup and release interpreter, */

    Py_XDECREF(module);

    wsgi_release_interpreter(interp);

    return status;
}

static authn_status wsgi_get_realm_hash(request_rec *r, const char *user,
                                        const char *realm, char **rethash)
{
    WSGIRequestConfig *config;

    InterpreterObject *interp = NULL;
    PyObject *modules = NULL;
    PyObject *module = NULL;
    char *name = NULL;
    int exists = 0;

    const char *script;
    const char *group;

    authn_status status;

    config = wsgi_create_req_config(r->pool, r);

    if (!config->auth_user_script) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Location of WSGI user "
                     "authentication script not provided.", getpid());

        return AUTH_GENERAL_ERROR;
    }

    /*
     * Acquire the desired python interpreter. Once this is done
     * it is safe to start manipulating python objects.
     */

    script = config->auth_user_script->handler_script;
    group = wsgi_server_group(r, config->auth_user_script->application_group);

    interp = wsgi_acquire_interpreter(group);

    if (!interp) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "mod_wsgi (pid=%d): Cannot acquire interpreter '%s'.",
                      getpid(), group);

        return AUTH_GENERAL_ERROR;
    }

    /* Calculate the Python module name to be used for script. */

    name = wsgi_module_name(r->pool, script);

    /*
     * Use a lock around the check to see if the module is
     * already loaded and the import of the module to prevent
     * two request handlers trying to import the module at the
     * same time.
     */

#if APR_HAS_THREADS
    Py_BEGIN_ALLOW_THREADS
    apr_thread_mutex_lock(wsgi_module_lock);
    Py_END_ALLOW_THREADS
#endif

    modules = PyImport_GetModuleDict();
    module = PyDict_GetItemString(modules, name);

    Py_XINCREF(module);

    if (module)
        exists = 1;

    /*
     * If script reloading is enabled and the module for it has
     * previously been loaded, see if it has been modified since
     * the last time it was accessed.
     */

    if (module && config->script_reloading) {
        if (wsgi_reload_required(r->pool, r, script, module, NULL)) {
            /*
             * Script file has changed. Only support module
             * reloading for authentication scripts. Remove the
             * module from the modules dictionary before
             * reloading it again. If code is executing within
             * the module at the time, the callers reference
             * count on the module should ensure it isn't
             * actually destroyed until it is finished.
             */

            Py_DECREF(module);
            module = NULL;

            PyDict_DelItemString(modules, name);
        }
    }

    if (!module) {
        module = wsgi_load_source(r->pool, r, name, exists, script,
                                  "", group, 0);
    }

    /* Safe now to release the module lock. */

#if APR_HAS_THREADS
    apr_thread_mutex_unlock(wsgi_module_lock);
#endif

    /* Log any details of exceptions if import failed. */

    if (PyErr_Occurred())
        wsgi_log_python_error(r, NULL, script, 0);

    /* Assume an internal server error unless everything okay. */

    status = AUTH_GENERAL_ERROR;

    /* Determine if script exists and execute it. */

    if (module) {
        PyObject *module_dict = NULL;
        PyObject *object = NULL;

        module_dict = PyModule_GetDict(module);
        object = PyDict_GetItemString(module_dict, "get_realm_hash");

        if (object) {
            PyObject *vars = NULL;
            PyObject *args = NULL;
            PyObject *result = NULL;
            PyObject *method = NULL;

            AuthObject *adapter = NULL;

            adapter = newAuthObject(r, config);

            if (adapter) {
                vars = Auth_environ(adapter, group);

                Py_INCREF(object);
                args = Py_BuildValue("(Oss)", vars, user, realm);
                result = PyObject_CallObject(object, args);
                Py_DECREF(args);
                Py_DECREF(object);
                Py_DECREF(vars);

                if (result) {
                    if (result == Py_None) {
                        status = AUTH_USER_NOT_FOUND;
                    }
                    else if (PyString_Check(result)) {
                        *rethash = PyString_AsString(result);
                        *rethash = apr_pstrdup(r->pool, *rethash);

                        status = AUTH_USER_FOUND;
                    }
#if PY_MAJOR_VERSION >= 3
                    else if (PyUnicode_Check(result)) {
                        PyObject *latin_item;
                        latin_item = PyUnicode_AsLatin1String(result);
                        if (!latin_item) {
                            PyErr_SetString(PyExc_TypeError, "Digest auth "
                                            "provider must return None "
                                            "or string object, value "
                                            "containing non 'latin-1' "
                                            "characters found");
                        }
                        else {
                            Py_DECREF(result);
                            result = latin_item;

                            *rethash = PyString_AsString(result);
                            *rethash = apr_pstrdup(r->pool, *rethash);

                            status = AUTH_USER_FOUND;
                        }
                    }
#endif
                    else {
                        PyErr_SetString(PyExc_TypeError, "Digest auth "
                                        "provider must return None "
                                        "or string object");
                    }

                    Py_DECREF(result);
                }

                /*
                 * Wipe out references to Apache request object
                 * held by Python objects, so can detect when an
                 * application holds on to the transient Python
                 * objects beyond the life of the request and
                 * thus raise an exception if they are used.
                 */

                adapter->r = NULL;

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                /* Close the log object so data is flushed. */

                method = PyObject_GetAttrString(adapter->log, "close");

                if (!method) {
                    PyErr_Format(PyExc_AttributeError,
                                 "'%s' object has no attribute 'close'",
                                 adapter->log->ob_type->tp_name);
                }
                else {
                    args = PyTuple_New(0);
                    result = PyObject_CallObject(method, args);
                    Py_XDECREF(result);
                    Py_DECREF(args);
                }

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                Py_XDECREF(method);

                /* No longer need adapter object. */

                Py_DECREF((PyObject *)adapter);
            }
        }
        else {
            Py_BEGIN_ALLOW_THREADS
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "mod_wsgi (pid=%d): Target WSGI user "
                          "authentication script '%s' does not provide "
                          "'Digest' auth provider.", getpid(), script);
            Py_END_ALLOW_THREADS
        }
    }

    /* Cleanup and release interpreter, */

    Py_XDECREF(module);

    wsgi_release_interpreter(interp);

    return status;
}

static const authn_provider wsgi_authn_provider =
{
    &wsgi_check_password,
    &wsgi_get_realm_hash
};
#endif

static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config,
                                apr_table_t **grpstatus)
{
    apr_table_t *grps = apr_table_make(r->pool, 15);

    InterpreterObject *interp = NULL;
    PyObject *modules = NULL;
    PyObject *module = NULL;
    char *name = NULL;
    int exists = 0;

    const char *script;
    const char *group;

    int status = HTTP_INTERNAL_SERVER_ERROR;

    if (!config->auth_group_script) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Location of WSGI group "
                     "authentication script not provided.", getpid());

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /*
     * Acquire the desired python interpreter. Once this is done
     * it is safe to start manipulating python objects.
     */

    script = config->auth_group_script->handler_script;
    group = wsgi_server_group(r, config->auth_group_script->application_group);

    interp = wsgi_acquire_interpreter(group);

    if (!interp) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "mod_wsgi (pid=%d): Cannot acquire interpreter '%s'.",
                      getpid(), group);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* Calculate the Python module name to be used for script. */

    name = wsgi_module_name(r->pool, script);

    /*
     * Use a lock around the check to see if the module is
     * already loaded and the import of the module to prevent
     * two request handlers trying to import the module at the
     * same time.
     */

#if APR_HAS_THREADS
    Py_BEGIN_ALLOW_THREADS
    apr_thread_mutex_lock(wsgi_module_lock);
    Py_END_ALLOW_THREADS
#endif

    modules = PyImport_GetModuleDict();
    module = PyDict_GetItemString(modules, name);

    Py_XINCREF(module);

    if (module)
        exists = 1;

    /*
     * If script reloading is enabled and the module for it has
     * previously been loaded, see if it has been modified since
     * the last time it was accessed.
     */

    if (module && config->script_reloading) {
        if (wsgi_reload_required(r->pool, r, script, module, NULL)) {
            /*
             * Script file has changed. Only support module
             * reloading for authentication scripts. Remove the
             * module from the modules dictionary before
             * reloading it again. If code is executing within
             * the module at the time, the callers reference
             * count on the module should ensure it isn't
             * actually destroyed until it is finished.
             */

            Py_DECREF(module);
            module = NULL;

            PyDict_DelItemString(modules, name);
        }
    }

    if (!module) {
        module = wsgi_load_source(r->pool, r, name, exists, script,
                                  "", group, 0);
    }

    /* Safe now to release the module lock. */

#if APR_HAS_THREADS
    apr_thread_mutex_unlock(wsgi_module_lock);
#endif

    /* Log any details of exceptions if import failed. */

    if (PyErr_Occurred())
        wsgi_log_python_error(r, NULL, script, 0);

    /* Assume an internal server error unless everything okay. */

    status = HTTP_INTERNAL_SERVER_ERROR;

    /* Determine if script exists and execute it. */

    if (module) {
        PyObject *module_dict = NULL;
        PyObject *object = NULL;

        module_dict = PyModule_GetDict(module);
        object = PyDict_GetItemString(module_dict, "groups_for_user");

        if (object) {
            PyObject *vars = NULL;
            PyObject *args = NULL;
            PyObject *result = NULL;
            PyObject *method = NULL;

            AuthObject *adapter = NULL;

            adapter = newAuthObject(r, config);

            if (adapter) {
                vars = Auth_environ(adapter, group);

                Py_INCREF(object);
                args = Py_BuildValue("(Os)", vars, r->user);
                result = PyObject_CallObject(object, args);
                Py_DECREF(args);
                Py_DECREF(object);
                Py_DECREF(vars);

                if (result) {
                    PyObject *iterator;

                    iterator = PyObject_GetIter(result);

                    if (iterator) {
                        PyObject *item;
                        const char *name;

                        status = OK;

                        while ((item = PyIter_Next(iterator))) {
#if PY_MAJOR_VERSION >= 3
                            if (PyUnicode_Check(item)) {
                                PyObject *latin_item;
                                latin_item = PyUnicode_AsLatin1String(item);
                                if (!latin_item) {
                                    Py_BEGIN_ALLOW_THREADS
                                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0,
                                                  r, "mod_wsgi (pid=%d): "
                                                  "Groups for user returned "
                                                  "from '%s' must be an "
                                                  "iterable sequence of "
                                                  "byte strings, value "
                                                  "containing non 'latin-1' "
                                                  "characters found",
                                                  getpid(), script);
                                    Py_END_ALLOW_THREADS

                                    Py_DECREF(item);

                                    status = HTTP_INTERNAL_SERVER_ERROR;

                                    break;
                                }
                                else {
                                    Py_DECREF(item);
                                    item = latin_item;
                                }
                            }
#endif

                            if (!PyString_Check(item)) {
                                Py_BEGIN_ALLOW_THREADS
                                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                              "mod_wsgi (pid=%d): Groups for "
                                              "user returned from '%s' must "
                                              "be an iterable sequence of "
                                              "byte strings.", getpid(),
                                              script);
                                Py_END_ALLOW_THREADS

                                Py_DECREF(item);

                                status = HTTP_INTERNAL_SERVER_ERROR;

                                break;
                            }

                            name = PyString_AsString(item);

                            apr_table_setn(grps, apr_pstrdup(r->pool, name),
                                           "1");

                            Py_DECREF(item);
                        }

                        Py_DECREF(iterator);
                    }
                    else {
                        Py_BEGIN_ALLOW_THREADS
                        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                      "mod_wsgi (pid=%d): Groups for user "
                                      "returned from '%s' must be an iterable "
                                      "sequence of byte strings.", getpid(),
                                      script);
                        Py_END_ALLOW_THREADS
                    }

                    Py_DECREF(result);
                }

                /*
                 * Wipe out references to Apache request object
                 * held by Python objects, so can detect when an
                 * application holds on to the transient Python
                 * objects beyond the life of the request and
                 * thus raise an exception if they are used.
                 */

                adapter->r = NULL;

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                /* Close the log object so data is flushed. */

                method = PyObject_GetAttrString(adapter->log, "close");

                if (!method) {
                    PyErr_Format(PyExc_AttributeError,
                                 "'%s' object has no attribute 'close'",
                                 adapter->log->ob_type->tp_name);
                }
                else {
                    args = PyTuple_New(0);
                    result = PyObject_CallObject(method, args);
                    Py_XDECREF(result);
                    Py_DECREF(args);
                }

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                Py_XDECREF(method);

                /* No longer need adapter object. */

                Py_DECREF((PyObject *)adapter);
            }
        }
        else {
            Py_BEGIN_ALLOW_THREADS
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "mod_wsgi (pid=%d): Target WSGI group "
                          "authentication script '%s' does not provide "
                          "group provider.", getpid(), script);
            Py_END_ALLOW_THREADS
        }
    }

    /* Cleanup and release interpreter, */

    Py_XDECREF(module);

    wsgi_release_interpreter(interp);

    if (status == OK)
        *grpstatus = grps;

    return status;
}

static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
                             const char *host)
{
    InterpreterObject *interp = NULL;
    PyObject *modules = NULL;
    PyObject *module = NULL;
    char *name = NULL;
    int exists = 0;

    const char *script;
    const char *group;

    int allow = 0;

    if (!config->access_script) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Location of WSGI host "
                     "access script not provided.", getpid());

        return 0;
    }

    /*
     * Acquire the desired python interpreter. Once this is done
     * it is safe to start manipulating python objects.
     */

    script = config->access_script->handler_script;
    group = wsgi_server_group(r, config->access_script->application_group);

    interp = wsgi_acquire_interpreter(group);

    if (!interp) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "mod_wsgi (pid=%d): Cannot acquire interpreter '%s'.",
                      getpid(), group);

        return 0;
    }

    /* Calculate the Python module name to be used for script. */

    name = wsgi_module_name(r->pool, script);

    /*
     * Use a lock around the check to see if the module is
     * already loaded and the import of the module to prevent
     * two request handlers trying to import the module at the
     * same time.
     */

#if APR_HAS_THREADS
    Py_BEGIN_ALLOW_THREADS
    apr_thread_mutex_lock(wsgi_module_lock);
    Py_END_ALLOW_THREADS
#endif

    modules = PyImport_GetModuleDict();
    module = PyDict_GetItemString(modules, name);

    Py_XINCREF(module);

    if (module)
        exists = 1;

    /*
     * If script reloading is enabled and the module for it has
     * previously been loaded, see if it has been modified since
     * the last time it was accessed.
     */

    if (module && config->script_reloading) {
        if (wsgi_reload_required(r->pool, r, script, module, NULL)) {
            /*
             * Script file has changed. Only support module
             * reloading for authentication scripts. Remove the
             * module from the modules dictionary before
             * reloading it again. If code is executing within
             * the module at the time, the callers reference
             * count on the module should ensure it isn't
             * actually destroyed until it is finished.
             */

            Py_DECREF(module);
            module = NULL;

            PyDict_DelItemString(modules, name);
        }
    }

    if (!module) {
        module = wsgi_load_source(r->pool, r, name, exists, script,
                                  "", group, 0);
    }

    /* Safe now to release the module lock. */

#if APR_HAS_THREADS
    apr_thread_mutex_unlock(wsgi_module_lock);
#endif

    /* Log any details of exceptions if import failed. */

    if (PyErr_Occurred())
        wsgi_log_python_error(r, NULL, script, 0);

    /* Assume not allowed unless everything okay. */

    allow = 0;

    /* Determine if script exists and execute it. */

    if (module) {
        PyObject *module_dict = NULL;
        PyObject *object = NULL;

        module_dict = PyModule_GetDict(module);
        object = PyDict_GetItemString(module_dict, "allow_access");

        if (object) {
            PyObject *vars = NULL;
            PyObject *args = NULL;
            PyObject *result = NULL;
            PyObject *method = NULL;

            AuthObject *adapter = NULL;

            adapter = newAuthObject(r, config);

            if (adapter) {
                vars = Auth_environ(adapter, group);

                Py_INCREF(object);
                args = Py_BuildValue("(Oz)", vars, host);
                result = PyObject_CallObject(object, args);
                Py_DECREF(args);
                Py_DECREF(object);
                Py_DECREF(vars);

                if (result) {
                    if (result == Py_None) {
                        allow = -1;
                    }
                    else if (PyBool_Check(result)) {
                        if (result == Py_True)
                            allow = 1;
                    }
                    else {
                        Py_BEGIN_ALLOW_THREADS
                        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                      "mod_wsgi (pid=%d): Indicator of "
                                      "host accessibility returned from '%s' "
                                      "must a boolean or None.", getpid(),
                                      script);
                        Py_END_ALLOW_THREADS
                    }

                    Py_DECREF(result);
                }

                /*
                 * Wipe out references to Apache request object
                 * held by Python objects, so can detect when an
                 * application holds on to the transient Python
                 * objects beyond the life of the request and
                 * thus raise an exception if they are used.
                 */

                adapter->r = NULL;

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                /* Close the log object so data is flushed. */

                method = PyObject_GetAttrString(adapter->log, "close");

                if (!method) {
                    PyErr_Format(PyExc_AttributeError,
                                 "'%s' object has no attribute 'close'",
                                 adapter->log->ob_type->tp_name);
                }
                else {
                    args = PyTuple_New(0);
                    result = PyObject_CallObject(method, args);
                    Py_XDECREF(result);
                    Py_DECREF(args);
                }

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                Py_XDECREF(method);

                /* No longer need adapter object. */

                Py_DECREF((PyObject *)adapter);
            }
        }
        else {
            Py_BEGIN_ALLOW_THREADS
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "mod_wsgi (pid=%d): Target WSGI host "
                          "access script '%s' does not provide "
                          "host validator.", getpid(), script);
            Py_END_ALLOW_THREADS
        }
    }

    /* Cleanup and release interpreter, */

    Py_XDECREF(module);

    wsgi_release_interpreter(interp);

    return allow;
}

static int wsgi_hook_access_checker(request_rec *r)
{
    WSGIRequestConfig *config;

    int allow = 0;
    const char *host = NULL;

    config = wsgi_create_req_config(r->pool, r);

    if (!config->access_script)
        return DECLINED;

    host = ap_get_remote_host(r->connection, r->per_dir_config,
                              REMOTE_HOST, NULL);

#if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
    if (!host)
        host = r->useragent_ip;
#else
    if (!host)
        host = r->connection->remote_ip;
#endif

    allow = wsgi_allow_access(r, config, host);

    if (allow < 0)
        return DECLINED;
    else if (allow)
        return OK;

    if (ap_satisfies(r) != SATISFY_ANY || !ap_some_auth_required(r)) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): "
                      "Client denied by server configuration: '%s'.",
                      getpid(), r->filename);
    }

    return HTTP_FORBIDDEN;
}

#if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER)
static int wsgi_hook_check_user_id(request_rec *r)
{
    WSGIRequestConfig *config;

    int status = -1;

    const char *password;

    InterpreterObject *interp = NULL;
    PyObject *modules = NULL;
    PyObject *module = NULL;
    char *name = NULL;
    int exists = 0;

    const char *script;
    const char *group;

    if ((status = ap_get_basic_auth_pw(r, &password)))
        return status;

    config = wsgi_create_req_config(r->pool, r);

    if (!config->auth_user_script)
        return DECLINED;

    /*
     * Acquire the desired python interpreter. Once this is done
     * it is safe to start manipulating python objects.
     */

    script = config->auth_user_script->handler_script;
    group = wsgi_server_group(r, config->auth_user_script->application_group);

    interp = wsgi_acquire_interpreter(group);

    if (!interp) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "mod_wsgi (pid=%d): Cannot acquire interpreter '%s'.",
                      getpid(), group);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* Calculate the Python module name to be used for script. */

    name = wsgi_module_name(r->pool, script);

    /*
     * Use a lock around the check to see if the module is
     * already loaded and the import of the module to prevent
     * two request handlers trying to import the module at the
     * same time.
     */

#if APR_HAS_THREADS
    Py_BEGIN_ALLOW_THREADS
    apr_thread_mutex_lock(wsgi_module_lock);
    Py_END_ALLOW_THREADS
#endif

    modules = PyImport_GetModuleDict();
    module = PyDict_GetItemString(modules, name);

    Py_XINCREF(module);

    if (module)
        exists = 1;

    /*
     * If script reloading is enabled and the module for it has
     * previously been loaded, see if it has been modified since
     * the last time it was accessed.
     */

    if (module && config->script_reloading) {
        if (wsgi_reload_required(r->pool, r, script, module, NULL)) {
            /*
             * Script file has changed. Only support module
             * reloading for authentication scripts. Remove the
             * module from the modules dictionary before
             * reloading it again. If code is executing within
             * the module at the time, the callers reference
             * count on the module should ensure it isn't
             * actually destroyed until it is finished.
             */

            Py_DECREF(module);
            module = NULL;

            PyDict_DelItemString(modules, name);
        }
    }

    if (!module) {
        module = wsgi_load_source(r->pool, r, name, exists, script,
                                  "", group, 0);
    }

    /* Safe now to release the module lock. */

#if APR_HAS_THREADS
    apr_thread_mutex_unlock(wsgi_module_lock);
#endif

    /* Log any details of exceptions if import failed. */

    if (PyErr_Occurred())
        wsgi_log_python_error(r, NULL, script, 0);

    /* Assume an internal server error unless everything okay. */

    status = HTTP_INTERNAL_SERVER_ERROR;

    /* Determine if script exists and execute it. */

    if (module) {
        PyObject *module_dict = NULL;
        PyObject *object = NULL;

        module_dict = PyModule_GetDict(module);
        object = PyDict_GetItemString(module_dict, "check_password");

        if (object) {
            PyObject *vars = NULL;
            PyObject *args = NULL;
            PyObject *result = NULL;
            PyObject *method = NULL;

            AuthObject *adapter = NULL;

            adapter = newAuthObject(r, config);

            if (adapter) {
                vars = Auth_environ(adapter, group);

                Py_INCREF(object);
                args = Py_BuildValue("(Oss)", vars, r->user, password);
                result = PyObject_CallObject(object, args);
                Py_DECREF(args);
                Py_DECREF(object);
                Py_DECREF(vars);

                if (result) {
                    if (result == Py_None) {
                        if (config->user_authoritative) {
                            ap_note_basic_auth_failure(r);
                            status = HTTP_UNAUTHORIZED;

                            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                          "mod_wsgi (pid=%d): User '%s' not "
                                          "found in executing authentication "
                                          "script '%s', for uri '%s'.",
                                          getpid(), r->user, script, r->uri);
                        }
                        else
                            status = DECLINED;
                    }
                    else if (result == Py_True) {
                        status = OK;
                    }
                    else if (result == Py_False) {
                        ap_note_basic_auth_failure(r);
                        status = HTTP_UNAUTHORIZED;

                        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                      "mod_wsgi (pid=%d): Password mismatch "
                                      "for user '%s' in executing "
                                      "authentication script '%s', for uri "
                                      "'%s'.", getpid(), r->user, script,
                                      r->uri);
                    }
                    else {
                        PyErr_SetString(PyExc_TypeError, "Basic auth "
                                        "provider must return True, False "
                                        "or None");
                    }

                    Py_DECREF(result);
                }

                /*
                 * Wipe out references to Apache request object
                 * held by Python objects, so can detect when an
                 * application holds on to the transient Python
                 * objects beyond the life of the request and
                 * thus raise an exception if they are used.
                 */

                adapter->r = NULL;

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                /* Close the log object so data is flushed. */

                method = PyObject_GetAttrString(adapter->log, "close");

                if (!method) {
                    PyErr_Format(PyExc_AttributeError,
                                 "'%s' object has no attribute 'close'",
                                 adapter->log->ob_type->tp_name);
                }
                else {
                    result = PyObject_CallObject(method, NULL);
                    Py_XDECREF(result);
                }

                /* Log any details of exceptions if execution failed. */

                if (PyErr_Occurred())
                    wsgi_log_python_error(r, NULL, script, 0);

                Py_XDECREF(method);

                /* No longer need adapter object. */

                Py_DECREF((PyObject *)adapter);
            }
        }
        else {
            Py_BEGIN_ALLOW_THREADS
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "mod_wsgi (pid=%d): Target WSGI user "
                          "authentication script '%s' does not provide "
                          "'Basic' auth provider.", getpid(), script);
            Py_END_ALLOW_THREADS
        }
    }

    /* Cleanup and release interpreter, */

    Py_XDECREF(module);

    wsgi_release_interpreter(interp);

    return status;
}
#endif

#if defined(MOD_WSGI_WITH_AUTHZ_PROVIDER)

#if MOD_WSGI_WITH_AUTHZ_PROVIDER_PARSED
static authz_status wsgi_check_authorization(request_rec *r,
                                             const char *require_args,
                                             const void *parsed_require_line)
#else
static authz_status wsgi_check_authorization(request_rec *r,
                                             const char *require_args)
#endif
{
    WSGIRequestConfig *config;

    apr_table_t *grpstatus = NULL;
    const char *t, *w;
    int status;

#if AP_MODULE_MAGIC_AT_LEAST(20100714,0)
    if (!r->user)
        return AUTHZ_DENIED_NO_USER;
#endif

    config = wsgi_create_req_config(r->pool, r);

    if (!config->auth_group_script) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
                     "mod_wsgi (pid=%d): Location of WSGI group "
                     "authorization script not provided.", getpid());

        return AUTHZ_DENIED;
    }

    status = wsgi_groups_for_user(r, config, &grpstatus);

    if (status != OK)
        return AUTHZ_DENIED;

    if (apr_table_elts(grpstatus)->nelts == 0) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): "
                      "Authorization of user '%s' to access '%s' failed. "
                      "User is not a member of any groups.", getpid(),
                      r->user, r->uri);
        return AUTHZ_DENIED;
    }

    t = require_args;
    while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
        if (apr_table_get(grpstatus, w)) {
            return AUTHZ_GRANTED;
        }
    }

    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): "
                  "Authorization of user '%s' to access '%s' failed. "
                  "User is not a member of designated groups.", getpid(),
                  r->user, r->uri);

    return AUTHZ_DENIED;
}

static const authz_provider wsgi_authz_provider =
{
    &wsgi_check_authorization,
#if MOD_WSGI_WITH_AUTHZ_PROVIDER_PARSED
    NULL,
#endif
};

#else

static int wsgi_hook_auth_checker(request_rec *r)
{
    WSGIRequestConfig *config;

    int m = r->method_number;
    const apr_array_header_t *reqs_arr;
    require_line *reqs;
    int required_group = 0;
    register int x;
    const char *t, *w;
    apr_table_t *grpstatus = NULL;
    char *reason = NULL;

    config = wsgi_create_req_config(r->pool, r);

    if (!config->auth_group_script)
        return DECLINED;

    reqs_arr = ap_requires(r);

    if (!reqs_arr)
        return DECLINED;

    reqs = (require_line *)reqs_arr->elts;

    for (x = 0; x < reqs_arr->nelts; x++) {

        if (!(reqs[x].method_mask & (AP_METHOD_BIT << m))) {
            continue;
        }

        t = reqs[x].requirement;
        w = ap_getword_white(r->pool, &t);

#if AP_MODULE_MAGIC_AT_LEAST(20100714,0)
        if (!strcasecmp(w, "wsgi-group")) {
#else
        if (!strcasecmp(w, "group") || !strcasecmp(w, "wsgi-group")) {
#endif
            required_group = 1;

            if (!grpstatus) {
                int status;

                status = wsgi_groups_for_user(r, config, &grpstatus);

                if (status != OK)
                    return status;

                if (apr_table_elts(grpstatus)->nelts == 0) {
                    reason = "User is not a member of any groups";
                    break;
                }
            }

            while (t[0]) {
                w = ap_getword_conf(r->pool, &t);
                if (apr_table_get(grpstatus, w)) {
                    return OK;
                }
            }
        }
    }

    if (!required_group || !config->group_authoritative)
        return DECLINED;

    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): "
                  "Authorization of user '%s' to access '%s' failed. %s.",
                  getpid(), r->user, r->uri, reason ? reason : "User is not "
                  "a member of designated groups");

    ap_note_auth_failure(r);

    return HTTP_UNAUTHORIZED;
}

#endif

APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *wsgi_logio_add_bytes_out;

static void ap_logio_add_bytes_out(conn_rec *c, apr_off_t bytes)
{
    if (!wsgi_daemon_pool && wsgi_logio_add_bytes_out)
        wsgi_logio_add_bytes_out(c, bytes);
}

static int wsgi_hook_logio(apr_pool_t *pconf, apr_pool_t *ptemp,
                           apr_pool_t *plog, server_rec *s)
{
    /*
     * This horrible fiddle is to insert a proxy function before
     * the normal ap_logio_add_bytes_out() function so that the
     * call to it can be disabled when mod_wsgi running in daemon
     * mode. If this is not done, then daemon process will crash
     * when mod_logio has been loaded.
     */

    wsgi_logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out);

    APR_REGISTER_OPTIONAL_FN(ap_logio_add_bytes_out);

    return OK;
}

static void wsgi_register_hooks(apr_pool_t *p)
{
    static const char * const p1[] = { "mod_alias.c", NULL };
    static const char * const n1[]= { "mod_userdir.c",
                                      "mod_vhost_alias.c", NULL };

    static const char * const n2[] = { "core.c", NULL };

#if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER)
    static const char * const p3[] = { "mod_auth.c", NULL };
#endif
#if !defined(MOD_WSGI_WITH_AUTHZ_PROVIDER)
    static const char * const n4[] = { "mod_authz_user.c", NULL };
#endif
    static const char * const n5[] = { "mod_authz_host.c", NULL };

    static const char * const p6[] = { "mod_python.c", NULL };

    static const char * const p7[] = { "mod_ssl.c", NULL };

    ap_hook_post_config(wsgi_hook_init, p6, NULL, APR_HOOK_MIDDLE);
    ap_hook_child_init(wsgi_hook_child_init, p6, NULL, APR_HOOK_MIDDLE);

    ap_hook_translate_name(wsgi_hook_intercept, p1, n1, APR_HOOK_MIDDLE);
    ap_hook_handler(wsgi_hook_handler, NULL, NULL, APR_HOOK_MIDDLE);

#if defined(MOD_WSGI_WITH_DAEMONS)
    ap_hook_post_config(wsgi_hook_logio, NULL, n2, APR_HOOK_REALLY_FIRST);

    wsgi_header_filter_handle =
        ap_register_output_filter("WSGI_HEADER", wsgi_header_filter,
                                  NULL, AP_FTYPE_PROTOCOL);
#endif

#if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER)
    ap_hook_check_user_id(wsgi_hook_check_user_id, p3, NULL, APR_HOOK_MIDDLE);
#else
    ap_register_provider(p, AUTHN_PROVIDER_GROUP, "wsgi",
                         AUTHN_PROVIDER_VERSION, &wsgi_authn_provider);
#endif
#if !defined(MOD_WSGI_WITH_AUTHZ_PROVIDER)
    ap_hook_auth_checker(wsgi_hook_auth_checker, NULL, n4, APR_HOOK_MIDDLE);
#else
    ap_register_provider(p, AUTHZ_PROVIDER_GROUP, "wsgi-group",
                         AUTHZ_PROVIDER_VERSION, &wsgi_authz_provider);
#endif
    ap_hook_access_checker(wsgi_hook_access_checker, p7, n5, APR_HOOK_MIDDLE);
}

static const command_rec wsgi_commands[] =
{
    AP_INIT_RAW_ARGS("WSGIScriptAlias", wsgi_add_script_alias,
        NULL, RSRC_CONF, "Map location to target WSGI script file."),
    AP_INIT_RAW_ARGS("WSGIScriptAliasMatch", wsgi_add_script_alias,
        "*", RSRC_CONF, "Map location pattern to target WSGI script file."),

#if defined(MOD_WSGI_WITH_DAEMONS)
    AP_INIT_RAW_ARGS("WSGIDaemonProcess", wsgi_add_daemon_process,
        NULL, RSRC_CONF, "Specify details of daemon processes to start."),
    AP_INIT_TAKE1("WSGISocketPrefix", wsgi_set_socket_prefix,
        NULL, RSRC_CONF, "Path prefix for the daemon process sockets."),
    AP_INIT_TAKE1("WSGISocketRotation", wsgi_set_socket_rotation,
        NULL, RSRC_CONF, "Enable/Disable rotation of daemon process sockets."),
    AP_INIT_TAKE1("WSGIAcceptMutex", wsgi_set_accept_mutex,
        NULL, RSRC_CONF, "Set accept mutex type for daemon processes."),

    AP_INIT_TAKE1("WSGILazyInitialization", wsgi_set_lazy_initialization,
        NULL, RSRC_CONF, "Enable/Disable lazy Python initialization."),
#endif

    AP_INIT_TAKE1("WSGIVerboseDebugging", wsgi_set_verbose_debugging,
        NULL, RSRC_CONF, "Enable/Disable verbose debugging messages."),

#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6
    AP_INIT_TAKE1("WSGIPy3kWarningFlag", wsgi_set_py3k_warning_flag,
        NULL, RSRC_CONF, "Enable/Disable Python 3.0 warnings."),
#endif

#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 3) || \
    (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6)
    AP_INIT_TAKE1("WSGIDontWriteBytecode", wsgi_set_dont_write_bytecode,
        NULL, RSRC_CONF, "Enable/Disable writing of byte code."),
#endif

    AP_INIT_TAKE1("WSGIPythonWarnings", wsgi_add_python_warnings,
        NULL, RSRC_CONF, "Control Python warning messages."),
    AP_INIT_TAKE1("WSGIPythonOptimize", wsgi_set_python_optimize,
        NULL, RSRC_CONF, "Set level of Python compiler optimisations."),
    AP_INIT_TAKE1("WSGIPythonHome", wsgi_set_python_home,
        NULL, RSRC_CONF, "Python prefix/exec_prefix absolute path names."),
    AP_INIT_TAKE1("WSGIPythonPath", wsgi_set_python_path,
        NULL, RSRC_CONF, "Python module search path."),
    AP_INIT_TAKE1("WSGIPythonEggs", wsgi_set_python_eggs,
        NULL, RSRC_CONF, "Python eggs cache directory."),
    AP_INIT_TAKE1("WSGIPythonHashSeed", wsgi_set_python_hash_seed,
        NULL, RSRC_CONF, "Python hash seed."),

    AP_INIT_TAKE1("WSGIDestroyInterpreter", wsgi_set_destroy_interpreter,
        NULL, RSRC_CONF, "Enable/Disable destruction of Python interpreter."),

#if defined(MOD_WSGI_WITH_DAEMONS)
    AP_INIT_TAKE1("WSGIRestrictEmbedded", wsgi_set_restrict_embedded,
        NULL, RSRC_CONF, "Enable/Disable use of embedded mode."),
#endif
    AP_INIT_TAKE1("WSGIRestrictStdin", wsgi_set_restrict_stdin,
        NULL, RSRC_CONF, "Enable/Disable restrictions on use of STDIN."),
    AP_INIT_TAKE1("WSGIRestrictStdout", wsgi_set_restrict_stdout,
        NULL, RSRC_CONF, "Enable/Disable restrictions on use of STDOUT."),
    AP_INIT_TAKE1("WSGIRestrictSignal", wsgi_set_restrict_signal,
        NULL, RSRC_CONF, "Enable/Disable restrictions on use of signal()."),

    AP_INIT_TAKE1("WSGICaseSensitivity", wsgi_set_case_sensitivity,
        NULL, RSRC_CONF, "Define whether file system is case sensitive."),

#if defined(MOD_WSGI_WITH_DAEMONS)
    AP_INIT_RAW_ARGS("WSGIRestrictProcess", wsgi_set_restrict_process,
        NULL, ACCESS_CONF|RSRC_CONF, "Limit selectable WSGI process groups."),
    AP_INIT_TAKE1("WSGIProcessGroup", wsgi_set_process_group,
        NULL, ACCESS_CONF|RSRC_CONF, "Name of the WSGI process group."),
#endif

    AP_INIT_TAKE1("WSGIApplicationGroup", wsgi_set_application_group,
        NULL, ACCESS_CONF|RSRC_CONF, "Application interpreter group."),
    AP_INIT_TAKE1("WSGICallableObject", wsgi_set_callable_object,
        NULL, OR_FILEINFO, "Name of entry point in WSGI script file."),

    AP_INIT_RAW_ARGS("WSGIImportScript", wsgi_add_import_script,
        NULL, RSRC_CONF, "Location of WSGI import script."),
    AP_INIT_RAW_ARGS("WSGIDispatchScript", wsgi_set_dispatch_script,
        NULL, ACCESS_CONF|RSRC_CONF, "Location of WSGI dispatch script."),

    AP_INIT_TAKE1("WSGIPassApacheRequest", wsgi_set_pass_apache_request,
        NULL, ACCESS_CONF|RSRC_CONF, "Enable/Disable Apache request object."),
    AP_INIT_TAKE1("WSGIPassAuthorization", wsgi_set_pass_authorization,
        NULL, OR_FILEINFO, "Enable/Disable WSGI authorization."),
    AP_INIT_TAKE1("WSGIScriptReloading", wsgi_set_script_reloading,
        NULL, OR_FILEINFO, "Enable/Disable script reloading mechanism."),
    AP_INIT_TAKE1("WSGIErrorOverride", wsgi_set_error_override,
        NULL, OR_FILEINFO, "Enable/Disable overriding of error pages."),
    AP_INIT_TAKE1("WSGIChunkedRequest", wsgi_set_chunked_request,
        NULL, OR_FILEINFO, "Enable/Disable support for chunked requests."),
    AP_INIT_TAKE1("WSGIMapHEADToGET", wsgi_set_map_head_to_get,
        NULL, OR_FILEINFO, "Enable/Disable mapping of HEAD to GET."),
    AP_INIT_TAKE1("WSGIIgnoreActivity", wsgi_set_ignore_activity,
        NULL, OR_FILEINFO, "Enable/Disable reset of inactvity timeout."),

    AP_INIT_RAW_ARGS("WSGITrustedProxyHeaders", wsgi_set_trusted_proxy_headers,
        NULL, OR_FILEINFO, "Specify a list of trusted proxy headers."),
    AP_INIT_RAW_ARGS("WSGITrustedProxies", wsgi_set_trusted_proxies,
        NULL, OR_FILEINFO, "Specify a list of trusted proxies."),

#ifndef WIN32
    AP_INIT_TAKE1("WSGIEnableSendfile", wsgi_set_enable_sendfile,
        NULL, OR_FILEINFO, "Enable/Disable support for kernel sendfile."),
#endif

    AP_INIT_RAW_ARGS("WSGIAccessScript", wsgi_set_access_script,
        NULL, OR_AUTHCFG, "Location of WSGI host access script file."),
    AP_INIT_RAW_ARGS("WSGIAuthUserScript", wsgi_set_auth_user_script,
        NULL, OR_AUTHCFG, "Location of WSGI user auth script file."),
    AP_INIT_RAW_ARGS("WSGIAuthGroupScript", wsgi_set_auth_group_script,
        NULL, OR_AUTHCFG, "Location of WSGI group auth script file."),
#if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER)
    AP_INIT_TAKE1("WSGIUserAuthoritative", wsgi_set_user_authoritative,
        NULL, OR_AUTHCFG, "Enable/Disable as being authoritative on users."),
#endif
    AP_INIT_TAKE1("WSGIGroupAuthoritative", wsgi_set_group_authoritative,
        NULL, OR_AUTHCFG, "Enable/Disable as being authoritative on groups."),

    AP_INIT_RAW_ARGS("WSGIHandlerScript", wsgi_add_handler_script,
        NULL, ACCESS_CONF|RSRC_CONF, "Location of WSGI handler script file."),

    AP_INIT_TAKE1("WSGIServerMetrics", wsgi_set_server_metrics,
        NULL, RSRC_CONF, "Enabled/Disable access to server metrics."),

    AP_INIT_TAKE1("WSGINewRelicConfigFile", wsgi_set_newrelic_config_file,
        NULL, RSRC_CONF, "New Relic monitoring agent configuration file."),
    AP_INIT_TAKE1("WSGINewRelicEnvironment", wsgi_set_newrelic_environment,
        NULL, RSRC_CONF, "New Relic monitoring agent environment."),

    { NULL }
};

/* Dispatch list for API hooks */

module AP_MODULE_DECLARE_DATA wsgi_module = {
    STANDARD20_MODULE_STUFF,
    wsgi_create_dir_config,    /* create per-dir    config structures */
    wsgi_merge_dir_config,     /* merge  per-dir    config structures */
    wsgi_create_server_config, /* create per-server config structures */
    wsgi_merge_server_config,  /* merge  per-server config structures */
    wsgi_commands,             /* table of config file commands       */
    wsgi_register_hooks        /* register hooks                      */
};

/* ------------------------------------------------------------------------- */

#if defined(_WIN32)
#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC initmod_wsgi(void)
{
}
#else
PyMODINIT_FUNC PyInit_mod_wsgi(void)
{
    return NULL;
}
#endif
#endif

/* ------------------------------------------------------------------------- */

/* vi: set sw=4 expandtab : */
