/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/***************************************************************************
 * Description: Apache 2 plugin for Tomcat                                 *
 * Author:      Gal Shachor <shachor@il.ibm.com>                           *
 *              Henri Gomez <hgomez@apache.org>                            *
 * Version:     $Revision: 1026100 $                                        *
 ***************************************************************************/

/*
 * mod_jk: keeps all servlet related ramblings together.
 */

#include "ap_config.h"
#include "apr_lib.h"
#include "apr_date.h"
#include "apr_file_info.h"
#include "apr_file_io.h"
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_main.h"
#include "http_log.h"
#include "util_script.h"
#include "ap_mpm.h"

#if defined(AS400) && !defined(AS400_UTF8)
#include "ap_charset.h"
#include "util_charset.h"       /* ap_hdrs_from_ascii */
#endif

/* deprecated with apr 0.9.3 */

#include "apr_version.h"
#if (APR_MAJOR_VERSION == 0) && \
    (APR_MINOR_VERSION <= 9) && \
    (APR_PATCH_VERSION < 3)
#define apr_filepath_name_get apr_filename_of_pathname
#endif

#include "apr_strings.h"

/* Yes; sorta sucks - with luck we will clean this up before httpd-2.2
 * ships, leaving AP_NEED_SET_MUTEX_PERMS def'd as 1 or 0 on all platforms.
 */
#ifdef AP_NEED_SET_MUTEX_PERMS
# define JK_NEED_SET_MUTEX_PERMS AP_NEED_SET_MUTEX_PERMS
#else
  /* A special case for httpd-2.0 */
# if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE) && !defined(AS400)
#  define JK_NEED_SET_MUTEX_PERMS 1
# else
#  define JK_NEED_SET_MUTEX_PERMS 0
# endif
#endif

#if JK_NEED_SET_MUTEX_PERMS
#include "unixd.h"      /* for unixd_set_global_mutex_perms */
#endif
/*
 * jk_ include files
 */
#ifdef NETWARE
#define __sys_types_h__
#define __sys_socket_h__
#define __netdb_h__
#define __netinet_in_h__
#define __arpa_inet_h__
#define __sys_timeval_h__
#endif

#include "jk_global.h"
#include "jk_ajp13.h"
#include "jk_logger.h"
#include "jk_map.h"
#include "jk_pool.h"
#include "jk_service.h"
#include "jk_uri_worker_map.h"
#include "jk_util.h"
#include "jk_worker.h"
#include "jk_shm.h"
#include "jk_url.h"

#define JK_LOG_DEF_FILE             ("logs/mod_jk.log")
#define JK_SHM_DEF_FILE             ("logs/jk-runtime-status")
#define JK_ENV_REMOTE_ADDR          ("JK_REMOTE_ADDR")
#define JK_ENV_REMOTE_HOST          ("JK_REMOTE_HOST")
#define JK_ENV_REMOTE_USER          ("JK_REMOTE_USER")
#define JK_ENV_AUTH_TYPE            ("JK_AUTH_TYPE")
#define JK_ENV_LOCAL_NAME           ("JK_LOCAL_NAME")
#define JK_ENV_LOCAL_PORT           ("JK_LOCAL_PORT")
#define JK_ENV_HTTPS                ("HTTPS")
#define JK_ENV_CERTS                ("SSL_CLIENT_CERT")
#define JK_ENV_CIPHER               ("SSL_CIPHER")
#define JK_ENV_SESSION              ("SSL_SESSION_ID")
#define JK_ENV_KEY_SIZE             ("SSL_CIPHER_USEKEYSIZE")
#define JK_ENV_CERTCHAIN_PREFIX     ("SSL_CLIENT_CERT_CHAIN_")
#define JK_ENV_REPLY_TIMEOUT        ("JK_REPLY_TIMEOUT")
#define JK_ENV_WORKER_NAME          ("JK_WORKER_NAME")
#define JK_NOTE_WORKER_NAME         ("JK_WORKER_NAME")
#define JK_NOTE_WORKER_TYPE         ("JK_WORKER_TYPE")
#define JK_NOTE_REQUEST_DURATION    ("JK_REQUEST_DURATION")
#define JK_NOTE_WORKER_ROUTE        ("JK_WORKER_ROUTE")
#define JK_HANDLER          ("jakarta-servlet")
#define JK_MAGIC_TYPE       ("application/x-jakarta-servlet")
#define NULL_FOR_EMPTY(x)   ((x && !strlen(x)) ? NULL : x)
#define STRNULL_FOR_NULL(x) ((x) ? (x) : "(null)")
#define JK_LOG_LOCK_KEY     ("jk_log_lock_key")
#define JKLOG_MARK          __FILE__,__LINE__

/*
 * If you are not using SSL, comment out the following line. It will make
 * apache run faster.
 *
 * Personally, I (DM), think this may be a lie.
 */
#define ADD_SSL_INFO

/* Needed for Apache 2.3/2.4 per-module log config */
#ifdef APLOG_USE_MODULE
APLOG_USE_MODULE(jk);
#endif

/* module MODULE_VAR_EXPORT jk_module; */
AP_MODULE_DECLARE_DATA module jk_module;

/*
 * Environment variable forward object
 */
typedef struct
{
    int has_default;
    char *name;
    char *value;
} envvar_item;

/*
 * Configuration object for the mod_jk module.
 */
typedef struct
{

    /*
     * Log stuff
     */
    char *log_file;
    int log_level;
    jk_logger_t *log;
    apr_file_t *jklogfp;

    /*
     * Mount stuff
     */
    char *mount_file;
    int mount_file_reload;
    jk_map_t *uri_to_context;

    int mountcopy;

    jk_uri_worker_map_t *uw_map;

    int was_initialized;

    /*
     * Automatic context path apache alias
     */
    char *alias_dir;

    /*
     * Request Logging
     */

    char *stamp_format_string;
    char *format_string;
    apr_array_header_t *format;

    /*
     * Setting target worker via environment
     */
    char *worker_indicator;

    /*
     * Configurable environment variables to overwrite
     * request information using meta data send by a
     * proxy in front of us.
     */
    char *remote_addr_indicator;
    char *remote_host_indicator;
    char *remote_user_indicator;
    char *auth_type_indicator;
    char *local_name_indicator;
    char *local_port_indicator;

    /*
     * SSL Support
     */
    int ssl_enable;
    char *https_indicator;
    char *certs_indicator;
    char *cipher_indicator;
    char *session_indicator;    /* Servlet API 2.3 requirement */
    char *key_size_indicator;   /* Servlet API 2.3 requirement */
    char *certchain_prefix;     /* Client certificate chain prefix */

    /*
     * Jk Options
     */
    int options;
    int exclude_options;

    int strip_session;
    char *strip_session_name;
    /*
     * Environment variables support
     */
    int envvars_has_own;
    apr_table_t *envvars;
    apr_table_t *envvars_def;
    apr_array_header_t *envvar_items;

    server_rec *s;
} jk_server_conf_t;

struct apache_private_data
{
    jk_pool_t p;

    int read_body_started;
    request_rec *r;
};
typedef struct apache_private_data apache_private_data_t;

static server_rec *main_server = NULL;
static jk_logger_t *main_log = NULL;
static apr_hash_t *jk_log_fps = NULL;
static jk_worker_env_t worker_env;
static apr_global_mutex_t *jk_log_lock = NULL;
static char *jk_shm_file = NULL;
static size_t jk_shm_size = 0;
static int jk_shm_size_set = 0;
static volatile int jk_watchdog_interval = 0;
static volatile int jk_watchdog_running  = 0;

/*
 * Worker stuff
*/
static jk_map_t *jk_worker_properties = NULL;
static char *jk_worker_file = NULL;
static int jk_mount_copy_all = JK_FALSE;

static int JK_METHOD ws_start_response(jk_ws_service_t *s,
                                       int status,
                                       const char *reason,
                                       const char *const *header_names,
                                       const char *const *header_values,
                                       unsigned num_of_headers);

static int JK_METHOD ws_read(jk_ws_service_t *s,
                             void *b, unsigned len, unsigned *actually_read);

static int init_jk(apr_pool_t * pconf, jk_server_conf_t * conf,
                    server_rec * s);

static int JK_METHOD ws_write(jk_ws_service_t *s, const void *b, unsigned l);

static void JK_METHOD ws_add_log_items(jk_ws_service_t *s,
                                       const char *const *log_names,
                                       const char *const *log_values,
                                       unsigned num_of_log_items);

static void * JK_METHOD ws_next_vhost(void *d);

static void JK_METHOD ws_vhost_to_text(void *d, char *buf, size_t len);

static jk_uri_worker_map_t * JK_METHOD ws_vhost_to_uw_map(void *d);

/* ========================================================================= */
/* JK Service step callbacks                                                 */
/* ========================================================================= */

static int JK_METHOD ws_start_response(jk_ws_service_t *s,
                                       int status,
                                       const char *reason,
                                       const char *const *header_names,
                                       const char *const *header_values,
                                       unsigned num_of_headers)
{
    unsigned h;
    apache_private_data_t *p = s->ws_private;
    request_rec *r = p->r;

    /* If there is no reason given (or an empty one),
     * we'll try to guess a good one.
     */
    if (!reason || *reason == '\0') {
        /* We ask Apache httpd about a good reason phrase. */
        reason = ap_get_status_line(status);
        /* Unfortunately it returns with a 500 reason phrase,
         * whenever it does not know about the given status code,
         * e.g. in the case of custom status codes.
         */
        if (status != 500 && !strncmp(reason, "500 ", 4)) {
            reason = "Unknown Reason";
        } else {
            /* Apache httpd returns a full status line,
             * but we only want a reason phrase, so skip
             * the prepended status code.
             */
            reason = reason + 4;
        }
    }
    r->status = status;
    r->status_line = apr_psprintf(r->pool, "%d %s", status, reason);

    for (h = 0; h < num_of_headers; h++) {
        if (!strcasecmp(header_names[h], "Content-type")) {
            char *tmp = apr_pstrdup(r->pool, header_values[h]);
            ap_content_type_tolower(tmp);
            /* It should be done like this in Apache 2.0 */
            /* This way, Apache 2.0 will be able to set the output filter */
            /* and it make jk useable with deflate using */
            /* AddOutputFilterByType DEFLATE text/html */
            ap_set_content_type(r, tmp);
        }
        else if (!strcasecmp(header_names[h], "Location")) {
#if defined(AS400) && !defined(AS400_UTF8)
            /* Fix escapes in Location Header URL */
            ap_fixup_escapes((char *)header_values[h],
                             strlen(header_values[h]), ap_hdrs_from_ascii);
#endif
            apr_table_set(r->headers_out, header_names[h], header_values[h]);
        }
        else if (!strcasecmp(header_names[h], "Content-Length")) {
            apr_table_set(r->headers_out, header_names[h], header_values[h]);
        }
        else if (!strcasecmp(header_names[h], "Transfer-Encoding")) {
            apr_table_set(r->headers_out, header_names[h], header_values[h]);
        }
        else if (!strcasecmp(header_names[h], "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(header_values[h]));
            ap_set_last_modified(r);
        }
        else {
            apr_table_add(r->headers_out, header_names[h], header_values[h]);
        }
    }

    /* this NOP function was removed in apache 2.0 alpha14 */
    /* ap_send_http_header(r); */
    s->response_started = JK_TRUE;

    return JK_TRUE;
}

/*
 * Read a chunk of the request body into a buffer.  Attempt to read len
 * bytes into the buffer.  Write the number of bytes actually read into
 * actually_read.
 *
 * Think of this function as a method of the apache1.3-specific subclass of
 * the jk_ws_service class.  Think of the *s param as a "this" or "self"
 * pointer.
 */
static int JK_METHOD ws_read(jk_ws_service_t *s,
                             void *b, unsigned len, unsigned *actually_read)
{
    if (s && s->ws_private && b && actually_read) {
        apache_private_data_t *p = s->ws_private;
        if (!p->read_body_started) {
            if (ap_should_client_block(p->r)) {
                p->read_body_started = JK_TRUE;
            }
        }

        if (p->read_body_started) {
#if defined(AS400) && !defined(AS400_UTF8)
            int long rv = OK;
            if (rv = ap_change_request_body_xlate(p->r, 65535, 65535)) {        /* turn off request body translation */
                ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_CRIT, 0, NULL,
                             "mod_jk: Error on ap_change_request_body_xlate, rc=%d",
                             rv);
                return JK_FALSE;
            }
#else
            long rv;
#endif

            if ((rv = ap_get_client_block(p->r, b, len)) < 0) {
                *actually_read = 0;
            }
            else {
                *actually_read = (unsigned)rv;
            }
            return JK_TRUE;
        }
    }
    return JK_FALSE;
}

static void JK_METHOD ws_flush(jk_ws_service_t *s)
{
#if ! (defined(AS400) && !defined(AS400_UTF8))
    if (s && s->ws_private) {
        apache_private_data_t *p = s->ws_private;
        ap_rflush(p->r);
    }
#endif
}

static void JK_METHOD ws_done(jk_ws_service_t *s)
{
#if ! (defined(AS400) && !defined(AS400_UTF8))
    if (s && s->ws_private) {
        apache_private_data_t *p = s->ws_private;
        ap_finalize_request_protocol(p->r);
    }
#endif
}

/*
 * Write a chunk of response data back to the browser.  If the headers
 * haven't yet been sent over, send over default header values (Status =
 * 200, basically).
 *
 * Write len bytes from buffer b.
 *
 * Think of this function as a method of the apache1.3-specific subclass of
 * the jk_ws_service class.  Think of the *s param as a "this" or "self"
 * pointer.
 */
/* Works with 4096, fails with 8192 */
#ifndef CHUNK_SIZE
#define CHUNK_SIZE 4096
#endif

static int JK_METHOD ws_write(jk_ws_service_t *s, const void *b, unsigned int l)
{
#if defined(AS400) && !defined(AS400_UTF8)
    int rc;
#endif

    if (s && s->ws_private && b) {
        apache_private_data_t *p = s->ws_private;

        if (l) {
            /* BUFF *bf = p->r->connection->client; */
            int r = 0;
            int ll = l;
            const char *bb = (const char *)b;

            if (!s->response_started) {
                if (main_log)
                    jk_log(main_log, JK_LOG_INFO,
                           "Write without start, starting with defaults");
                if (!s->start_response(s, 200, NULL, NULL, NULL, 0)) {
                    return JK_FALSE;
                }
            }
            if (p->r->header_only) {
#if ! (defined(AS400) && !defined(AS400_UTF8))
                ap_rflush(p->r);
#endif
                return JK_TRUE;
            }
#if defined(AS400) && !defined(AS400_UTF8)
            /* turn off response body translation */
            rc = ap_change_response_body_xlate(p->r, 65535, 65535);
            if (rc) {
                ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_CRIT, 0, NULL,
                             "mod_jk: Error on ap_change_response_body_xlate, rc=%d",
                             rc);
                return JK_FALSE;
            }
#endif

            while (ll > 0 && !p->r->connection->aborted) {
#if 0
                /* Apache 2 output filter does not write
                 * directly to the wire.
                 */
                int toSend = (ll > CHUNK_SIZE) ? CHUNK_SIZE : ll;
                r = ap_rwrite(bb, toSend, p->r);
#else
                r = ap_rwrite(bb, ll, p->r);
#endif
                if (JK_IS_DEBUG_LEVEL(main_log))
                    jk_log(main_log, JK_LOG_DEBUG,
                           "written %d out of %d", r, ll);

                if (r < 0)
                    return JK_FALSE;
                ll -= r;
                bb += r;
            }
            if (ll && p->r->connection->aborted) {
                /* Fail if there is something left to send and
                 * the connection was aborted by the client
                 */
                return JK_FALSE;
            }
        }

        return JK_TRUE;
    }
    return JK_FALSE;
}

static void JK_METHOD ws_add_log_items(jk_ws_service_t *s,
                                       const char *const *log_names,
                                       const char *const *log_values,
                                       unsigned num_of_log_items)
{
    unsigned h;
    apache_private_data_t *p = s->ws_private;
    request_rec *r = p->r;

    for (h = 0; h < num_of_log_items; h++) {
        if (log_names[h] && log_values[h]) {
            apr_table_setn(r->notes, log_names[h], log_values[h]);
        }
    }
}

static void * JK_METHOD ws_next_vhost(void *d)
{
    server_rec *s = (server_rec *)d;
    if (s == NULL)
        return main_server;
    return s->next;
}

static void JK_METHOD ws_vhost_to_text(void *d, char *buf, size_t len)
{
    server_rec *s = (server_rec *)d;
    size_t used = 0;

    if (s->server_hostname)
        used += strlen(s->server_hostname);
    if (!s->is_virtual) {
        if (s->port)
            used += strlen(":XXXXX");
    }
    else if (s->addrs) {
        used += strlen(" [");
        if (s->addrs->virthost)
            used += strlen(s->addrs->virthost);
        if (s->addrs->host_port)
            used += strlen(":XXXXX");
        used += strlen("]");
    }

    if (len < used && len > strlen("XXX")) {
        strcpy(buf, "XXX");
        return;
    }

    used = 0;

    if (s->server_hostname) {
        strcpy(buf + used, s->server_hostname);
        used += strlen(s->server_hostname);
    }
    if (!s->is_virtual) {
        if (s->port) {
            sprintf(buf + used, ":%hu", s->port);
            used = strlen(buf);
        }
    }
    else if (s->addrs) {
        strcpy(buf + used, " [");
        used += strlen(" [");
        if (s->addrs->virthost) {
            strcpy(buf + used, s->addrs->virthost);
            used += strlen(s->addrs->virthost);
        }
        if (s->addrs->host_port) {
            sprintf(buf + used, ":%hu", s->addrs->host_port);
            used = strlen(buf);
        }
        strcpy(buf + used, "]");
        used += strlen("]");
    }
}

static jk_uri_worker_map_t * JK_METHOD ws_vhost_to_uw_map(void *d)
{
    server_rec *s = (server_rec *)d;
    jk_server_conf_t *conf = NULL;
    if (s == NULL)
        return NULL;
    conf = (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                     &jk_module);
    return conf->uw_map;
}

/* ========================================================================= */
/* Utility functions                                                         */
/* ========================================================================= */

/* ========================================================================= */
/* Log something to Jk log file then exit */
static void jk_error_exit(const char *file,
                          int line,
                          int level,
                          const server_rec * s,
                          apr_pool_t * p, const char *fmt, ...)
{
    va_list ap;
    char *res;
    char *ch;

    va_start(ap, fmt);
    res = apr_pvsprintf(s->process->pool, fmt, ap);
    va_end(ap);
    /* Replace all format characters in the resulting message */
    /* because we feed the message to ap_log_error(). */
    ch = res;
    while (*ch) {
        if (*ch == '%') {
            *ch = '#';
        }
        ch++;
    }

#if (MODULE_MAGIC_NUMBER_MAJOR >= 20100606)
    ap_log_error(file, line, APLOG_MODULE_INDEX, level, 0, s, res);
#else
    ap_log_error(file, line, level, 0, s, res);
#endif
    if ( s ) {
#if (MODULE_MAGIC_NUMBER_MAJOR >= 20100606)
        ap_log_error(file, line, APLOG_MODULE_INDEX, level, 0, NULL, res);
#else
        ap_log_error(file, line, level, 0, NULL, res);
#endif
    }

    /* Exit process */
    exit(1);
}

static jk_uint64_t get_content_length(request_rec * r)
{
    if (r->clength > 0) {
        return (jk_uint64_t)r->clength;
    }
    else if (r->main == NULL || r->main == r) {
        char *lenp = (char *)apr_table_get(r->headers_in, "Content-Length");

        if (lenp) {
            jk_uint64_t rc = 0;
            if (sscanf(lenp, "%" JK_UINT64_T_FMT, &rc) > 0 && rc > 0) {
                return rc;
            }
        }
    }

    return 0;
}

/* Retrieve string value from env var, use default if env var does not exist. */
static const char *get_env_string(request_rec *r, const char *def,
                                  char *env, int null_for_empty)
{
    char *value = (char *)apr_table_get(r->subprocess_env, env);
    if (value)
        return null_for_empty ? NULL_FOR_EMPTY(value) : value;
    return null_for_empty ? NULL_FOR_EMPTY(def) : def;
}

/* Retrieve integer value from env var, use default if env var does not exist. */
static int get_env_int(request_rec *r, int def, char *env)
{
    char *value = (char *)apr_table_get(r->subprocess_env, env);
    if (value)
        return atoi(value);
    return def;
}

static int init_ws_service(apache_private_data_t * private_data,
                           jk_ws_service_t *s, jk_server_conf_t * conf)
{
    int size;
    request_rec *r = private_data->r;
    char *ssl_temp = NULL;
    const char *reply_timeout = NULL;
    rule_extension_t *e;

    /* Copy in function pointers (which are really methods) */
    s->start_response = ws_start_response;
    s->read = ws_read;
    s->write = ws_write;
    s->flush = ws_flush;
    s->done = ws_done;
    s->add_log_items = ws_add_log_items;
    s->next_vhost = ws_next_vhost;
    s->vhost_to_text = ws_vhost_to_text;
    s->vhost_to_uw_map = ws_vhost_to_uw_map;

    s->auth_type = get_env_string(r, r->ap_auth_type,
                                  conf->auth_type_indicator, 1);
    s->remote_user = get_env_string(r, r->user,
                                    conf->remote_user_indicator, 1);

    s->protocol = r->protocol;
    s->remote_host = (char *)ap_get_remote_host(r->connection,
                                                r->per_dir_config,
                                                REMOTE_HOST, NULL);
    s->remote_host = get_env_string(r, s->remote_host,
                                    conf->remote_host_indicator, 1);
    if (conf->options & JK_OPT_FWDLOCAL)
        s->remote_addr = r->connection->local_ip;
    else
        s->remote_addr = r->connection->remote_ip;
    s->remote_addr = get_env_string(r, s->remote_addr,
                                    conf->remote_addr_indicator, 1);
    if (conf->options & JK_OPT_FLUSHPACKETS)
        s->flush_packets = 1;
    if (conf->options & JK_OPT_FLUSHEADER)
        s->flush_header = 1;

    e = (rule_extension_t *)ap_get_module_config(r->request_config, &jk_module);
    if (e) {
        s->extension.reply_timeout = e->reply_timeout;
        s->extension.use_server_error_pages = e->use_server_error_pages;
        if (e->activation) {
            s->extension.activation = apr_palloc(r->pool, e->activation_size * sizeof(int));
            memcpy(s->extension.activation, e->activation, e->activation_size * sizeof(int));
        }
        if (e->fail_on_status_size > 0) {
            s->extension.fail_on_status_size = e->fail_on_status_size;
            s->extension.fail_on_status = apr_palloc(r->pool, e->fail_on_status_size * sizeof(int));
            memcpy(s->extension.fail_on_status, e->fail_on_status, e->fail_on_status_size * sizeof(int));
        }
    }
    reply_timeout = apr_table_get(r->subprocess_env, "JK_REPLY_TIMEOUT");
    if (reply_timeout) {
        int r = atoi(reply_timeout);
        if (r >= 0)
            s->extension.reply_timeout = r;
    }

    if (conf->options & JK_OPT_DISABLEREUSE)
        s->disable_reuse = 1;

    /* get server name */
    s->server_name = get_env_string(r, (char *)ap_get_server_name(r),
                                    conf->local_name_indicator, 0);

    /* get the real port (otherwise redirect failed) */
    /* XXX: use apache API for getting server port
     *
     * Pre 1.2.7 versions used:
     * s->server_port = r->connection->local_addr->port;
     */
    s->server_port = get_env_int(r, ap_get_server_port(r),
                                 conf->local_port_indicator);

#if ((AP_MODULE_MAGIC_AT_LEAST(20051115,4)) && !defined(API_COMPATIBILITY)) || (MODULE_MAGIC_NUMBER_MAJOR >= 20060905)
    s->server_software = (char *)ap_get_server_description();
#else
    s->server_software = (char *)ap_get_server_version();
#endif
    s->method = (char *)r->method;
    s->content_length = get_content_length(r);
    s->is_chunked = r->read_chunked;
    s->no_more_chunks = 0;
#if defined(AS400) && !defined(AS400_UTF8)
    /* Get the query string that is not translated to EBCDIC  */
    s->query_string = ap_get_original_query_string(r);
#else
    s->query_string = r->args;
#endif

    /*
     * The 2.2 servlet spec errata says the uri from
     * HttpServletRequest.getRequestURI() should remain encoded.
     * [http://java.sun.com/products/servlet/errata_042700.html]
     *
     * We use JkOptions to determine which method to be used
     *
     * ap_escape_uri is the latest recommanded but require
     *               some java decoding (in TC 3.3 rc2)
     *
     * unparsed_uri is used for strict compliance with spec and
     *              old Tomcat (3.2.3 for example)
     *
     * uri is use for compatibilty with mod_rewrite with old Tomcats
     */

    switch (conf->options & JK_OPT_FWDURIMASK) {

    case JK_OPT_FWDURICOMPATUNPARSED:
        s->req_uri = r->unparsed_uri;
        if (s->req_uri != NULL) {
            char *query_str = strchr(s->req_uri, '?');
            if (query_str != NULL) {
                *query_str = 0;
            }
        }

        break;

    case JK_OPT_FWDURICOMPAT:
        s->req_uri = r->uri;
        break;

    case JK_OPT_FWDURIPROXY:
        size = 3 * strlen(r->uri) + 1;
        s->req_uri = apr_palloc(r->pool, size);
        jk_canonenc(r->uri, s->req_uri, size);
        break;

    case JK_OPT_FWDURIESCAPED:
        s->req_uri = ap_escape_uri(r->pool, r->uri);
        break;

    default:
        return JK_FALSE;
    }

    if (conf->ssl_enable || conf->envvars) {
        ap_add_common_vars(r);

        if (conf->ssl_enable) {
            ssl_temp =
                (char *)apr_table_get(r->subprocess_env,
                                      conf->https_indicator);
            if (ssl_temp && !strcasecmp(ssl_temp, "on")) {
                s->is_ssl = JK_TRUE;
                s->ssl_cert =
                    (char *)apr_table_get(r->subprocess_env,
                                          conf->certs_indicator);

                if (conf->options & JK_OPT_FWDCERTCHAIN) {
                    const apr_array_header_t *t = apr_table_elts(r->subprocess_env);
                    if (t && t->nelts) {
                        int i;
                        const apr_table_entry_t *elts = (const apr_table_entry_t *) t->elts;
                        apr_array_header_t *certs = apr_array_make(r->pool, 1, sizeof(char *));
                        *(const char **)apr_array_push(certs) = s->ssl_cert;
                        for (i = 0; i < t->nelts; i++) {
                            if (!elts[i].key)
                                continue;
                            if (!strncasecmp(elts[i].key, conf->certchain_prefix,
                                             strlen(conf->certchain_prefix)))
                                *(const char **)apr_array_push(certs) = elts[i].val;
                        }
                        s->ssl_cert = apr_array_pstrcat(r->pool, certs, '\0');
                    }
                }

                if (s->ssl_cert) {
                    s->ssl_cert_len = strlen(s->ssl_cert);
                    if (JK_IS_DEBUG_LEVEL(conf->log)) {
                        jk_log(conf->log, JK_LOG_DEBUG,
                               "SSL client certificate (%d bytes): %s",
                               s->ssl_cert_len, s->ssl_cert);
                    }
                }
                /* Servlet 2.3 API */
                s->ssl_cipher =
                    (char *)apr_table_get(r->subprocess_env,
                                          conf->cipher_indicator);
                s->ssl_session =
                    (char *)apr_table_get(r->subprocess_env,
                                          conf->session_indicator);

                if (conf->options & JK_OPT_FWDKEYSIZE) {
                    /* Servlet 2.3 API */
                    ssl_temp = (char *)apr_table_get(r->subprocess_env,
                                                     conf->
                                                     key_size_indicator);
                    if (ssl_temp)
                        s->ssl_key_size = atoi(ssl_temp);
                }


            }
        }

        if (conf->envvars) {
            const apr_array_header_t *t = conf->envvar_items;
            if (t && t->nelts) {
                int i;
                int j = 0;
                envvar_item *elts = (envvar_item *) t->elts;
                s->attributes_names = apr_palloc(r->pool,
                                                 sizeof(char *) * t->nelts);
                s->attributes_values = apr_palloc(r->pool,
                                                  sizeof(char *) * t->nelts);

                for (i = 0; i < t->nelts; i++) {
                    s->attributes_names[i - j] = elts[i].name;
                    s->attributes_values[i - j] =
                        (char *)apr_table_get(r->subprocess_env, elts[i].name);
                    if (!s->attributes_values[i - j]) {
                        if (elts[i].has_default) {
                            s->attributes_values[i - j] = elts[i].value;
                        }
                        else {
                            s->attributes_values[i - j] = "";
                            s->attributes_names[i - j] = "";
                            j++;
                        }
                    }
                }

                s->num_attributes = t->nelts - j;
            }
        }
    }

    if (r->headers_in && apr_table_elts(r->headers_in)) {
        int need_content_length_header = (!s->is_chunked
                                          && s->content_length ==
                                          0) ? JK_TRUE : JK_FALSE;
        const apr_array_header_t *t = apr_table_elts(r->headers_in);
        if (t && t->nelts) {
            int i;
            apr_table_entry_t *elts = (apr_table_entry_t *) t->elts;
            s->num_headers = t->nelts;
            /* allocate an extra header slot in case we need to add a content-length header */
            s->headers_names =
                apr_palloc(r->pool, sizeof(char *) * (t->nelts + 1));
            s->headers_values =
                apr_palloc(r->pool, sizeof(char *) * (t->nelts + 1));
            if (!s->headers_names || !s->headers_values)
                return JK_FALSE;
            for (i = 0; i < t->nelts; i++) {
                char *hname = apr_pstrdup(r->pool, elts[i].key);
                s->headers_values[i] = apr_pstrdup(r->pool, elts[i].val);
                s->headers_names[i] = hname;
                if (need_content_length_header &&
                    !strcasecmp(s->headers_names[i], "content-length")) {
                    need_content_length_header = JK_FALSE;
                }
            }
            /* Add a content-length = 0 header if needed.
             * Ajp13 assumes an absent content-length header means an unknown,
             * but non-zero length body.
             */
            if (need_content_length_header) {
                s->headers_names[s->num_headers] = "content-length";
                s->headers_values[s->num_headers] = "0";
                s->num_headers++;
            }
        }
        /* Add a content-length = 0 header if needed. */
        else if (need_content_length_header) {
            s->headers_names = apr_palloc(r->pool, sizeof(char *));
            s->headers_values = apr_palloc(r->pool, sizeof(char *));
            if (!s->headers_names || !s->headers_values)
                return JK_FALSE;
            s->headers_names[0] = "content-length";
            s->headers_values[0] = "0";
            s->num_headers++;
        }
    }
    s->uw_map = conf->uw_map;

    /* Dump all connection param so we can trace what's going to
     * the remote tomcat
     */
    if (JK_IS_DEBUG_LEVEL(conf->log)) {
        jk_log(conf->log, JK_LOG_DEBUG,
               "Service protocol=%s method=%s ssl=%s host=%s addr=%s name=%s port=%d auth=%s user=%s laddr=%s raddr=%s uri=%s",
               STRNULL_FOR_NULL(s->protocol),
               STRNULL_FOR_NULL(s->method),
               s->is_ssl ? "true" : "false",
               STRNULL_FOR_NULL(s->remote_host),
               STRNULL_FOR_NULL(s->remote_addr),
               STRNULL_FOR_NULL(s->server_name),
               s->server_port,
               STRNULL_FOR_NULL(s->auth_type),
               STRNULL_FOR_NULL(s->remote_user),
               STRNULL_FOR_NULL(r->connection->local_ip),
               STRNULL_FOR_NULL(r->connection->remote_ip),
               STRNULL_FOR_NULL(s->req_uri));
    }

    return JK_TRUE;
}

/*
 * The JK module command processors
 *
 * The below are all installed so that Apache calls them while it is
 * processing its config files.  This allows configuration info to be
 * copied into a jk_server_conf_t object, which is then used for request
 * filtering/processing.
 *
 * See jk_cmds definition below for explanations of these options.
 */

/*
 * JkMountCopy directive handling
 *
 * JkMountCopy On/Off/All
 */

static const char *jk_set_mountcopy(cmd_parms * cmd,
                                    void *dummy, const char *mount_copy)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    if (! strcasecmp(mount_copy, "all")) {
        const char *err_string = ap_check_cmd_context(cmd, GLOBAL_ONLY);
        if (err_string != NULL) {
            return err_string;
        }
        jk_mount_copy_all = JK_TRUE;
    }
    else if (strcasecmp(mount_copy, "on") && strcasecmp(mount_copy, "off")) {
        return "JkMountCopy must be All, On or Off";
    }
    else {
        conf->mountcopy = strcasecmp(mount_copy, "off") ? JK_TRUE : JK_FALSE;
    }

    return NULL;
}

/*
 * JkMount directive handling
 *
 * JkMount URI(context) worker
 */

static const char *jk_mount_context(cmd_parms * cmd,
                                    void *dummy,
                                    const char *context,
                                    const char *worker)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);
    const char *c, *w;

    if (worker != NULL && cmd->path == NULL ) {
        c = context;
        w = worker;
    }
    else if (worker == NULL && cmd->path != NULL) {
        c = cmd->path;
        w = context;
    }
    else {
        if (worker == NULL)
            return "JkMount needs a path when not defined in a location";
        else
            return "JkMount can not have a path when defined in a location";
    }

    if (c[0] != '/')
        return "JkMount context should start with /";

    if (!conf->uri_to_context) {
        if (!jk_map_alloc(&(conf->uri_to_context))) {
            return "JkMount Memory error";
        }
    }
    /*
     * Add the new worker to the alias map.
     */
    jk_map_put(conf->uri_to_context, c, w, NULL);
    return NULL;
}

/*
 * JkUnMount directive handling
 *
 * JkUnMount URI(context) worker
 */

static const char *jk_unmount_context(cmd_parms * cmd,
                                      void *dummy,
                                      const char *context,
                                      const char *worker)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);
    char *uri;
    const char *c, *w;

    if (worker != NULL && cmd->path == NULL ) {
        c = context;
        w = worker;
    }
    else if (worker == NULL && cmd->path != NULL) {
        c = cmd->path;
        w = context;
    }
    else {
        if (worker == NULL)
            return "JkUnMount needs a path when not defined in a location";
        else
            return "JkUnMount can not have a path when defined in a location";
    }

    if (c[0] != '/')
        return "JkUnMount context should start with /";

    uri = apr_pstrcat(cmd->temp_pool, "!", c, NULL);

    if (!conf->uri_to_context) {
        if (!jk_map_alloc(&(conf->uri_to_context))) {
            return "JkUnMount Memory error";
        }
    }
    /*
     * Add the new worker to the alias map.
     */
    jk_map_put(conf->uri_to_context, uri, w, NULL);
    return NULL;
}


/*
 * JkWorkersFile Directive Handling
 *
 * JkWorkersFile file
 */

static const char *jk_set_worker_file(cmd_parms * cmd,
                                      void *dummy, const char *worker_file)
{
    const char *err_string = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err_string != NULL) {
        return err_string;
    }

    if (jk_worker_file != NULL)
        return "JkWorkersFile only allowed once";

    /* we need an absolute path (ap_server_root_relative does the ap_pstrdup) */
    jk_worker_file = ap_server_root_relative(cmd->pool, worker_file);

    if (jk_worker_file == NULL)
        return "JkWorkersFile file name invalid";

    if (jk_file_exists(jk_worker_file) != JK_TRUE)
        return "JkWorkersFile: Can't find the workers file specified";

    return NULL;
}

/*
 * JkMountFile Directive Handling
 *
 * JkMountFile file
 */

static const char *jk_set_mount_file(cmd_parms * cmd,
                                     void *dummy, const char *mount_file)
{
    server_rec *s = cmd->server;

    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    /* we need an absolute path (ap_server_root_relative does the ap_pstrdup) */
    conf->mount_file = ap_server_root_relative(cmd->pool, mount_file);

    if (conf->mount_file == NULL)
        return "JkMountFile file name invalid";

    if (jk_file_exists(conf->mount_file) != JK_TRUE)
        return "JkMountFile: Can't find the mount file specified";

    if (!conf->uri_to_context) {
        if (!jk_map_alloc(&(conf->uri_to_context))) {
            return "JkMountFile Memory error";
        }
    }

    return NULL;
}

/*
 * JkMountFileReload Directive Handling
 *
 * JkMountFileReload seconds
 */

static const char *jk_set_mount_file_reload(cmd_parms * cmd,
                                            void *dummy, const char *mount_file_reload)
{
    server_rec *s = cmd->server;
    int interval;

    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    interval = atoi(mount_file_reload);
    if (interval < 0) {
        interval = 0;
    }

    conf->mount_file_reload = interval;

    return NULL;
}

/*
 * JkWatchdogInterval Directive Handling
 *
 * JkWatchdogInterval seconds
 */

static const char *jk_set_watchdog_interval(cmd_parms * cmd,
                                            void *dummy, const char *watchdog_interval)
{
    const char *err_string = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err_string != NULL) {
        return err_string;
    }

#if APR_HAS_THREADS
    jk_watchdog_interval = atoi(watchdog_interval);
    if (jk_watchdog_interval < 0) {
        jk_watchdog_interval = 0;
    }
    return NULL;
#else
    return "JkWatchdogInterval: APR was compiled without threading support. Cannot create watchdog thread";
#endif
}

/*
 * JkLogFile Directive Handling
 *
 * JkLogFile file
 */

static const char *jk_set_log_file(cmd_parms * cmd,
                                   void *dummy, const char *log_file)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    /* we need an absolute path */
    if (*log_file != '|')
        conf->log_file = ap_server_root_relative(cmd->pool, log_file);
    else
        conf->log_file = apr_pstrdup(cmd->pool, log_file);

    if (conf->log_file == NULL)
        return "JkLogFile file name invalid";

    return NULL;
}

/*
 * JkShmFile Directive Handling
 *
 * JkShmFile file
 */

static const char *jk_set_shm_file(cmd_parms * cmd,
                                   void *dummy, const char *shm_file)
{
    const char *err_string = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err_string != NULL) {
        return err_string;
    }

    /* we need an absolute path */
    jk_shm_file = ap_server_root_relative(cmd->pool, shm_file);
    if (jk_shm_file == NULL)
        return "JkShmFile file name invalid";

    return NULL;
}

/*
 * JkShmSize Directive Handling
 *
 * JkShmSize size in kilobytes
 */

static const char *jk_set_shm_size(cmd_parms * cmd,
                                   void *dummy, const char *shm_size)
{
    int sz = 0;
    const char *err_string = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err_string != NULL) {
        return err_string;
    }

    sz = atoi(shm_size) * 1024;
    if (sz < JK_SHM_DEF_SIZE)
        sz = JK_SHM_DEF_SIZE;
    else
        sz = JK_SHM_ALIGN(sz);
    jk_shm_size = (size_t)sz;
    if (jk_shm_size)
        jk_shm_size_set = 1;
    return NULL;
}

/*
 * JkLogLevel Directive Handling
 *
 * JkLogLevel debug/info/error/emerg
 */

static const char *jk_set_log_level(cmd_parms * cmd,
                                    void *dummy, const char *log_level)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->log_level = jk_parse_log_level(log_level);

    return NULL;
}

/*
 * JkLogStampFormat Directive Handling
 *
 * JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
 */

static const char *jk_set_log_fmt(cmd_parms * cmd,
                                  void *dummy, const char *log_format)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->stamp_format_string = apr_pstrdup(cmd->pool, log_format);

    return NULL;
}


/*
 * JkAutoAlias Directive Handling
 *
 * JkAutoAlias application directory
 */

static const char *jk_set_auto_alias(cmd_parms * cmd,
                                     void *dummy, const char *directory)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->alias_dir = apr_pstrdup(cmd->pool, directory);

    if (conf->alias_dir == NULL)
        return "JkAutoAlias directory invalid";

    return NULL;
}

/*
 * JkStripSession directive handling
 *
 * JkStripSession On/Off [session path identifier]
 */

static const char *jk_set_strip_session(cmd_parms * cmd, void *dummy,
                                        const char *flag, const char *name)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    if (strcasecmp(flag, "on") && strcasecmp(flag, "off")) {
        return "JkStripSession must be On or Off";
    }
    else {
        conf->strip_session = strcasecmp(flag, "off") ? JK_TRUE : JK_FALSE;
    }

    /* Check for optional path value */
    if (name)
        conf->strip_session_name = apr_pstrdup(cmd->pool, name);
    else
        conf->strip_session_name = apr_pstrdup(cmd->pool, JK_PATH_SESSION_IDENTIFIER);

    return NULL;
}

/*****************************************************************
 *
 * Actually logging.
 */

typedef const char *(*item_key_func) (request_rec *, char *);

typedef struct
{
    item_key_func func;
    char *arg;
} request_log_format_item;

static const char *process_item(request_rec * r,
                                request_log_format_item * item)
{
    const char *cp;

    cp = (*item->func) (r, item->arg);
    return cp ? cp : "-";
}

static void request_log_transaction(request_rec * r, jk_server_conf_t * conf)
{
    request_log_format_item *items;
    char *str, *s;
    int i;
    int len = 0;
    const char **strs;
    int *strl;
    apr_array_header_t *format = conf->format;

    strs = apr_palloc(r->pool, sizeof(char *) * (format->nelts));
    strl = apr_palloc(r->pool, sizeof(int) * (format->nelts));
    items = (request_log_format_item *) format->elts;
    for (i = 0; i < format->nelts; ++i) {
        strs[i] = process_item(r, &items[i]);
    }
    for (i = 0; i < format->nelts; ++i) {
        len += strl[i] = strlen(strs[i]);
    }
    str = apr_palloc(r->pool, len + 1);
    for (i = 0, s = str; i < format->nelts; ++i) {
        memcpy(s, strs[i], strl[i]);
        s += strl[i];
    }
    *s = 0;

    jk_log(conf->log, JK_LOG_REQUEST, "%s", str);

}

/*****************************************************************
 *
 * Parsing the log format string
 */

static char *format_integer(apr_pool_t * p, int i)
{
    return apr_psprintf(p, "%d", i);
}

static char *pfmt(apr_pool_t * p, int i)
{
    if (i <= 0) {
        return "-";
    }
    else {
        return format_integer(p, i);
    }
}

static const char *constant_item(request_rec * dummy, char *stuff)
{
    return stuff;
}

static const char *log_worker_name(request_rec * r, char *a)
{
    return apr_table_get(r->notes, JK_NOTE_WORKER_NAME);
}

static const char *log_worker_route(request_rec * r, char *a)
{
    return apr_table_get(r->notes, JK_NOTE_WORKER_ROUTE);
}


static const char *log_request_duration(request_rec * r, char *a)
{
    return apr_table_get(r->notes, JK_NOTE_REQUEST_DURATION);
}

static const char *log_request_line(request_rec * r, char *a)
{
    /* NOTE: If the original request contained a password, we
     * re-write the request line here to contain XXXXXX instead:
     * (note the truncation before the protocol string for HTTP/0.9 requests)
     * (note also that r->the_request contains the unmodified request)
     */
    return (r->parsed_uri.password) ? apr_pstrcat(r->pool, r->method, " ",
                                                  apr_uri_unparse(r->pool,
                                                                  &r->
                                                                  parsed_uri,
                                                                  0),
                                                  r->
                                                  assbackwards ? NULL : " ",
                                                  r->protocol, NULL)
        : r->the_request;
}

/* These next two routines use the canonical name:port so that log
 * parsers don't need to duplicate all the vhost parsing crud.
 */
static const char *log_virtual_host(request_rec * r, char *a)
{
    return r->server->server_hostname;
}

static const char *log_server_port(request_rec * r, char *a)
{
    return apr_psprintf(r->pool, "%u",
                        r->server->port ? r->server->
                        port : ap_default_port(r));
}

/* This respects the setting of UseCanonicalName so that
 * the dynamic mass virtual hosting trick works better.
 */
static const char *log_server_name(request_rec * r, char *a)
{
    return ap_get_server_name(r);
}

static const char *log_request_uri(request_rec * r, char *a)
{
    return r->uri;
}
static const char *log_request_method(request_rec * r, char *a)
{
    return r->method;
}

static const char *log_request_protocol(request_rec * r, char *a)
{
    return r->protocol;
}
static const char *log_request_query(request_rec * r, char *a)
{
    return (r->args != NULL) ? apr_pstrcat(r->pool, "?", r->args, NULL)
        : "";
}
static const char *log_status(request_rec * r, char *a)
{
    return pfmt(r->pool, r->status);
}

static const char *clf_log_bytes_sent(request_rec * r, char *a)
{
    if (!r->sent_bodyct) {
        return "-";
    }
    else {
        return apr_off_t_toa(r->pool, r->bytes_sent);
    }
}

static const char *log_bytes_sent(request_rec * r, char *a)
{
    if (!r->sent_bodyct) {
        return "0";
    }
    else {
        return apr_psprintf(r->pool, "%" APR_OFF_T_FMT, r->bytes_sent);
    }
}

static struct log_item_list
{
    char ch;
    item_key_func func;
} log_item_keys[] = {

    {
    'T', log_request_duration}, {
    'r', log_request_line}, {
    'U', log_request_uri}, {
    's', log_status}, {
    'b', clf_log_bytes_sent}, {
    'B', log_bytes_sent}, {
    'V', log_server_name}, {
    'v', log_virtual_host}, {
    'p', log_server_port}, {
    'H', log_request_protocol}, {
    'm', log_request_method}, {
    'q', log_request_query}, {
    'w', log_worker_name}, {
    'R', log_worker_route}, {
    '\0'}
};

static struct log_item_list *find_log_func(char k)
{
    int i;

    for (i = 0; log_item_keys[i].ch; ++i)
        if (k == log_item_keys[i].ch) {
            return &log_item_keys[i];
        }

    return NULL;
}

static char *parse_request_log_misc_string(apr_pool_t * p,
                                           request_log_format_item * it,
                                           const char **sa)
{
    const char *s;
    char *d;

    it->func = constant_item;

    s = *sa;
    while (*s && *s != '%') {
        s++;
    }
    /*
     * This might allocate a few chars extra if there's a backslash
     * escape in the format string.
     */
    it->arg = apr_palloc(p, s - *sa + 1);

    d = it->arg;
    s = *sa;
    while (*s && *s != '%') {
        if (*s != '\\') {
            *d++ = *s++;
        }
        else {
            s++;
            switch (*s) {
            case '\\':
                *d++ = '\\';
                s++;
                break;
            case 'n':
                *d++ = '\n';
                s++;
                break;
            case 't':
                *d++ = '\t';
                s++;
                break;
            default:
                /* copy verbatim */
                *d++ = '\\';
                /*
                 * Allow the loop to deal with this *s in the normal
                 * fashion so that it handles end of string etc.
                 * properly.
                 */
                break;
            }
        }
    }
    *d = '\0';

    *sa = s;
    return NULL;
}

static char *parse_request_log_item(apr_pool_t * p,
                                    request_log_format_item * it,
                                    const char **sa)
{
    const char *s = *sa;
    struct log_item_list *l;

    if (*s != '%') {
        return parse_request_log_misc_string(p, it, sa);
    }

    ++s;
    it->arg = "";               /* For safety's sake... */

    l = find_log_func(*s++);
    if (!l) {
        char dummy[2];

        dummy[0] = s[-1];
        dummy[1] = '\0';
        return apr_pstrcat(p, "Unrecognized JkRequestLogFormat directive %",
                           dummy, NULL);
    }
    it->func = l->func;
    *sa = s;
    return NULL;
}

static apr_array_header_t *parse_request_log_string(apr_pool_t * p,
                                                    const char *s,
                                                    const char **err)
{
    apr_array_header_t *a =
        apr_array_make(p, 0, sizeof(request_log_format_item));
    char *res;

    while (*s) {
        if ((res =
             parse_request_log_item(p,
                                    (request_log_format_item *)
                                    apr_array_push(a), &s))) {
            *err = res;
            return NULL;
        }
    }

    return a;
}

/*
 * JkRequestLogFormat Directive Handling
 *
 * JkRequestLogFormat format string
 *
 * %b - Bytes sent, excluding HTTP headers. In CLF format
 * %B - Bytes sent, excluding HTTP headers.
 * %H - The request protocol
 * %m - The request method
 * %p - The canonical Port of the server serving the request
 * %q - The query string (prepended with a ? if a query string exists,
 *      otherwise an empty string)
 * %r - First line of request
 * %s - request HTTP status code
 * %T - Requset duration, elapsed time to handle request in seconds '.' micro seconds
 * %U - The URL path requested, not including any query string.
 * %v - The canonical ServerName of the server serving the request.
 * %V - The server name according to the UseCanonicalName setting.
 * %w - Tomcat worker name
 */

static const char *jk_set_request_log_format(cmd_parms * cmd,
                                             void *dummy, const char *format)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->format_string = apr_pstrdup(cmd->pool, format);

    return NULL;
}


/*
 * JkWorkerIndicator Directive Handling
 *
 * JkWorkerIndicator JkWorker
 */

static const char *jk_set_worker_indicator(cmd_parms * cmd,
                                           void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->worker_indicator = apr_pstrdup(cmd->pool, indicator);

    return NULL;
}

/*
 * Directives Handling for setting various environment names
 * used to overwrite the following request information:
 * - remote_addr
 * - remote_host
 * - remote_user
 * - auth_type
 * - server_name
 * - server_port
 */
static const char *jk_set_remote_addr_indicator(cmd_parms * cmd,
                                                void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config, &jk_module);
    conf->remote_addr_indicator = apr_pstrdup(cmd->pool, indicator);
    return NULL;
}

static const char *jk_set_remote_host_indicator(cmd_parms * cmd,
                                                void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config, &jk_module);
    conf->remote_host_indicator = apr_pstrdup(cmd->pool, indicator);
    return NULL;
}

static const char *jk_set_remote_user_indicator(cmd_parms * cmd,
                                                void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config, &jk_module);
    conf->remote_user_indicator = apr_pstrdup(cmd->pool, indicator);
    return NULL;
}

static const char *jk_set_auth_type_indicator(cmd_parms * cmd,
                                              void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config, &jk_module);
    conf->auth_type_indicator = apr_pstrdup(cmd->pool, indicator);
    return NULL;
}

static const char *jk_set_local_name_indicator(cmd_parms * cmd,
                                               void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config, &jk_module);
    conf->local_name_indicator = apr_pstrdup(cmd->pool, indicator);
    return NULL;
}

static const char *jk_set_local_port_indicator(cmd_parms * cmd,
                                               void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config, &jk_module);
    conf->local_port_indicator = apr_pstrdup(cmd->pool, indicator);
    return NULL;
}

/*
 * JkExtractSSL Directive Handling
 *
 * JkExtractSSL On/Off
 */

static const char *jk_set_enable_ssl(cmd_parms * cmd, void *dummy, int flag)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    /* Set up our value */
    conf->ssl_enable = flag ? JK_TRUE : JK_FALSE;

    return NULL;
}

/*
 * JkHTTPSIndicator Directive Handling
 *
 * JkHTTPSIndicator HTTPS
 */

static const char *jk_set_https_indicator(cmd_parms * cmd,
                                          void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->https_indicator = apr_pstrdup(cmd->pool, indicator);

    return NULL;
}

/*
 * JkCERTSIndicator Directive Handling
 *
 * JkCERTSIndicator SSL_CLIENT_CERT
 */

static const char *jk_set_certs_indicator(cmd_parms * cmd,
                                          void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->certs_indicator = apr_pstrdup(cmd->pool, indicator);

    return NULL;
}

/*
 * JkCIPHERIndicator Directive Handling
 *
 * JkCIPHERIndicator SSL_CIPHER
 */

static const char *jk_set_cipher_indicator(cmd_parms * cmd,
                                           void *dummy, const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->cipher_indicator = apr_pstrdup(cmd->pool, indicator);

    return NULL;
}

/*
 * JkCERTCHAINPrefix Directive Handling
 *
 * JkCERTCHAINPrefix SSL_CLIENT_CERT_CHAIN_
 */

static const char *jk_set_certchain_prefix(cmd_parms * cmd,
                                           void *dummy, const char *prefix)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->certchain_prefix = apr_pstrdup(cmd->pool, prefix);

    return NULL;
}

/*
 * JkSESSIONIndicator Directive Handling
 *
 * JkSESSIONIndicator SSL_SESSION_ID
 */

static const char *jk_set_session_indicator(cmd_parms * cmd,
                                            void *dummy,
                                            const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->session_indicator = apr_pstrdup(cmd->pool, indicator);

    return NULL;
}

/*
 * JkKEYSIZEIndicator Directive Handling
 *
 * JkKEYSIZEIndicator SSL_CIPHER_USEKEYSIZE
 */

static const char *jk_set_key_size_indicator(cmd_parms * cmd,
                                             void *dummy,
                                             const char *indicator)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->key_size_indicator = apr_pstrdup(cmd->pool, indicator);

    return NULL;
}

/*
 * JkOptions Directive Handling
 *
 *
 * +ForwardSSLKeySize        => Forward SSL Key Size, to follow 2.3 specs but may broke old TC 3.2
 * -ForwardSSLKeySize        => Don't Forward SSL Key Size, will make mod_jk works with all TC release
 *  ForwardURICompat         => Forward URI normally, less spec compliant but mod_rewrite compatible (old TC)
 *  ForwardURICompatUnparsed => Forward URI as unparsed, spec compliant but broke mod_rewrite (old TC)
 *  ForwardURIEscaped        => Forward URI escaped and Tomcat (3.3 rc2) stuff will do the decoding part
 *  ForwardDirectories       => Forward all directory requests with no index files to Tomcat
 * +ForwardSSLCertChain      => Forward SSL Cert Chain
 * -ForwardSSLCertChain      => Don't Forward SSL Cert Chain (default)
 */

static const char *jk_set_options(cmd_parms * cmd, void *dummy,
                                  const char *line)
{
    int opt = 0;
    int mask = 0;
    char action;
    char *w;

    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    while (line[0] != 0) {
        w = ap_getword_conf(cmd->pool, &line);
        action = 0;

        if (*w == '+' || *w == '-') {
            action = *(w++);
        }

        mask = 0;

        if (action == '-' && !strncasecmp(w, "ForwardURI", strlen("ForwardURI")))
            return apr_pstrcat(cmd->pool, "JkOptions: Illegal option '-", w,
                               "': ForwardURI* options can not be disabled", NULL);

        if (!strcasecmp(w, "ForwardURICompat")) {
            opt = JK_OPT_FWDURICOMPAT;
            mask = JK_OPT_FWDURIMASK;
        }
        else if (!strcasecmp(w, "ForwardURICompatUnparsed")) {
            opt = JK_OPT_FWDURICOMPATUNPARSED;
            mask = JK_OPT_FWDURIMASK;
        }
        else if (!strcasecmp(w, "ForwardURIEscaped")) {
            opt = JK_OPT_FWDURIESCAPED;
            mask = JK_OPT_FWDURIMASK;
        }
        else if (!strcasecmp(w, "ForwardURIProxy")) {
            opt = JK_OPT_FWDURIPROXY;
            mask = JK_OPT_FWDURIMASK;
        }
        else if (!strcasecmp(w, "ForwardDirectories")) {
            opt = JK_OPT_FWDDIRS;
        }
        else if (!strcasecmp(w, "ForwardLocalAddress")) {
            opt = JK_OPT_FWDLOCAL;
        }
        else if (!strcasecmp(w, "FlushPackets")) {
            opt = JK_OPT_FLUSHPACKETS;
        }
        else if (!strcasecmp(w, "FlushHeader")) {
            opt = JK_OPT_FLUSHEADER;
        }
        else if (!strcasecmp(w, "DisableReuse")) {
            opt = JK_OPT_DISABLEREUSE;
        }
        else if (!strcasecmp(w, "ForwardSSLCertChain")) {
            opt = JK_OPT_FWDCERTCHAIN;
        }
        else if (!strcasecmp(w, "ForwardKeySize")) {
            opt = JK_OPT_FWDKEYSIZE;
        }
        else if (!strcasecmp(w, "RejectUnsafeURI")) {
            opt = JK_OPT_REJECTUNSAFE;
        }
        else
            return apr_pstrcat(cmd->pool, "JkOptions: Illegal option '", w,
                               "'", NULL);

        conf->options &= ~mask;

        if (action == '-') {
            conf->exclude_options |= opt;
        }
        else if (action == '+') {
            conf->options |= opt;
        }
        else {                  /* for now +Opt == Opt */
            conf->options |= opt;
        }
    }
    return NULL;
}

/*
 * JkEnvVar Directive Handling
 *
 * JkEnvVar MYOWNDIR
 */

static const char *jk_add_env_var(cmd_parms * cmd,
                                  void *dummy,
                                  const char *env_name,
                                  const char *default_value)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    conf->envvars_has_own = JK_TRUE;
    if (!conf->envvars) {
        conf->envvars      = apr_table_make(cmd->pool, 0);
        conf->envvars_def  = apr_table_make(cmd->pool, 0);
        conf->envvar_items = apr_array_make(cmd->pool, 0,
                                            sizeof(envvar_item));
    }

    /* env_name is mandatory, default_value is optional.
     * No value means send the attribute only, if the env var is set during runtime.
     */
    apr_table_setn(conf->envvars, env_name, default_value ? default_value : "");
    apr_table_setn(conf->envvars_def, env_name, default_value ? "1" : "0");

    return NULL;
}

/*
 * JkWorkerProperty Directive Handling
 *
 * JkWorkerProperty name=value
 */

static const char *jk_set_worker_property(cmd_parms * cmd,
                                          void *dummy,
                                          const char *line)
{
    server_rec *s = cmd->server;
    jk_server_conf_t *conf =
        (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                  &jk_module);

    const char *err_string = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err_string != NULL) {
        return err_string;
    }

    if (!jk_worker_properties)
        jk_map_alloc(&jk_worker_properties);
    if (jk_map_read_property(jk_worker_properties, NULL, line,
                             JK_MAP_HANDLE_DUPLICATES, conf->log) == JK_FALSE)
        return apr_pstrcat(cmd->temp_pool, "Invalid JkWorkerProperty ", line, NULL);

    return NULL;
}

static const command_rec jk_cmds[] = {
    /*
     * JkWorkersFile specifies a full path to the location of the worker
     * properties file.
     *
     * This file defines the different workers used by apache to redirect
     * servlet requests.
     */
    AP_INIT_TAKE1("JkWorkersFile", jk_set_worker_file, NULL, RSRC_CONF,
                  "The name of a worker file for the Tomcat servlet containers"),

    /*
     * JkMountFile specifies a full path to the location of the
     * uriworker properties file.
     *
     * This file defines the different mapping for workers used by apache
     * to redirect servlet requests.
     */
    AP_INIT_TAKE1("JkMountFile", jk_set_mount_file, NULL, RSRC_CONF,
                  "The name of a mount file for the Tomcat servlet uri mapping"),

    /*
     * JkMountFileReload specifies the reload check interval for the
     * uriworker properties file.
     *
     * Default value is: JK_URIMAP_DEF_RELOAD
     */
    AP_INIT_TAKE1("JkMountFileReload", jk_set_mount_file_reload, NULL, RSRC_CONF,
                  "The reload check interval of the mount file"),

    /*
     * JkWatchdogInterval specifies the maintain interval for the
     * watchdog thread.
     *
     * Default value is: 0 meaning watchdog thread will not be created
     */
    AP_INIT_TAKE1("JkWatchdogInterval", jk_set_watchdog_interval, NULL, RSRC_CONF,
                  "The maintain interval of the watchdog thread"),

    /*
     * JkMount mounts a url prefix to a worker (the worker need to be
     * defined in the worker properties file.
     */
    AP_INIT_TAKE12("JkMount", jk_mount_context, NULL, RSRC_CONF|ACCESS_CONF,
                   "A mount point from a context to a Tomcat worker"),

    /*
     * JkUnMount unmounts a url prefix to a worker (the worker need to be
     * defined in the worker properties file.
     */
    AP_INIT_TAKE12("JkUnMount", jk_unmount_context, NULL, RSRC_CONF|ACCESS_CONF,
                   "A no mount point from a context to a Tomcat worker"),

    /*
     * JkMountCopy specifies if mod_jk should copy the mount points
     * from the main server to the virtual servers.
     */
    AP_INIT_TAKE1("JkMountCopy", jk_set_mountcopy, NULL, RSRC_CONF,
                  "Should the base server mounts be copied to the virtual server"),

    /*
     * JkStripSession specifies if mod_jk should strip the ;jsessionid
     * from the unmapped urls
     */
    AP_INIT_TAKE12("JkStripSession", jk_set_strip_session, NULL, RSRC_CONF,
                   "Should the server strip the jsessionid from unmapped URLs"),

    /*
     * JkLogFile & JkLogLevel specifies to where should the plugin log
     * its information and how much.
     * JkLogStampFormat specify the time-stamp to be used on log
     */
    AP_INIT_TAKE1("JkLogFile", jk_set_log_file, NULL, RSRC_CONF,
                  "Full path to the Tomcat module log file"),

    AP_INIT_TAKE1("JkShmFile", jk_set_shm_file, NULL, RSRC_CONF,
                  "Full path to the Tomcat module shared memory file"),

    AP_INIT_TAKE1("JkShmSize", jk_set_shm_size, NULL, RSRC_CONF,
                  "Size of the shared memory file in KBytes"),

    AP_INIT_TAKE1("JkLogLevel", jk_set_log_level, NULL, RSRC_CONF,
                  "The Tomcat module log level, can be debug, "
                  "info, error or emerg"),
    AP_INIT_TAKE1("JkLogStampFormat", jk_set_log_fmt, NULL, RSRC_CONF,
                  "The Tomcat module log format, follow strftime syntax"),
    AP_INIT_TAKE1("JkRequestLogFormat", jk_set_request_log_format, NULL,
                  RSRC_CONF,
                  "The mod_jk module request log format string"),

    /*
     * Automatically Alias webapp context directories into the Apache
     * document space.
     */
    AP_INIT_TAKE1("JkAutoAlias", jk_set_auto_alias, NULL, RSRC_CONF,
                  "The mod_jk module automatic context apache alias directory"),

    /*
     * Enable worker name to be set in an environment variable.
     * This way one can use LocationMatch together with mod_env,
     * mod_setenvif and mod_rewrite to set the target worker.
     * Use this in combination with SetHandler jakarta-servlet to
     * make mod_jk the handler for the request.
     *
     */
    AP_INIT_TAKE1("JkWorkerIndicator", jk_set_worker_indicator, NULL, RSRC_CONF,
                  "Name of the Apache environment that contains the worker name"),

    /*
     * Environment variables used to overwrite the following
     * request information which gets forwarded:
     * - remote_addr
     * - remote_host
     * - remote_user
     * - auth_type
     * - server_name
     * - server_port
     */
    AP_INIT_TAKE1("JkRemoteAddrIndicator", jk_set_remote_addr_indicator, NULL, RSRC_CONF,
                  "Name of the Apache environment that contains the remote address"),
    AP_INIT_TAKE1("JkRemoteHostIndicator", jk_set_remote_host_indicator, NULL, RSRC_CONF,
                  "Name of the Apache environment that contains the remote host name"),
    AP_INIT_TAKE1("JkRemoteUserIndicator", jk_set_remote_user_indicator, NULL, RSRC_CONF,
                  "Name of the Apache environment that contains the remote user name"),
    AP_INIT_TAKE1("JkAuthTypeIndicator", jk_set_auth_type_indicator, NULL, RSRC_CONF,
                  "Name of the Apache environment that contains the type of authentication"),
    AP_INIT_TAKE1("JkLocalNameIndicator", jk_set_local_name_indicator, NULL, RSRC_CONF,
                  "Name of the Apache environment that contains the local name"),
    AP_INIT_TAKE1("JkLocalPortIndicator", jk_set_local_port_indicator, NULL, RSRC_CONF,
                  "Name of the Apache environment that contains the local port"),

    /*
     * Apache has multiple SSL modules (for example apache_ssl, stronghold
     * IHS ...). Each of these can have a different SSL environment names
     * The following properties let the administrator specify the envoiroment
     * variables names.
     *
     * HTTPS - indication for SSL
     * CERTS - Base64-Der-encoded client certificates.
     * CIPHER - A string specifing the ciphers suite in use.
     * KEYSIZE - Size of Key used in dialogue (#bits are secure)
     * SESSION - A string specifing the current SSL session.
     */
    AP_INIT_TAKE1("JkHTTPSIndicator", jk_set_https_indicator, NULL, RSRC_CONF,
                  "Name of the Apache environment that contains SSL indication"),
    AP_INIT_TAKE1("JkCERTSIndicator", jk_set_certs_indicator, NULL, RSRC_CONF,
                  "Name of the Apache environment that contains SSL client certificates"),
    AP_INIT_TAKE1("JkCIPHERIndicator", jk_set_cipher_indicator, NULL,
                  RSRC_CONF,
                  "Name of the Apache environment that contains SSL client cipher"),
    AP_INIT_TAKE1("JkSESSIONIndicator", jk_set_session_indicator, NULL,
                  RSRC_CONF,
                  "Name of the Apache environment that contains SSL session"),
    AP_INIT_TAKE1("JkKEYSIZEIndicator", jk_set_key_size_indicator, NULL,
                  RSRC_CONF,
                  "Name of the Apache environment that contains SSL key size in use"),
    AP_INIT_TAKE1("JkCERTCHAINPrefix", jk_set_certchain_prefix, NULL, RSRC_CONF,
                  "Name of the Apache environment (prefix) that contains SSL client chain certificates"),
    AP_INIT_FLAG("JkExtractSSL", jk_set_enable_ssl, NULL, RSRC_CONF,
                 "Turns on SSL processing and information gathering by mod_jk"),

    /*
     * Options to tune mod_jk configuration
     * for now we understand :
     * +ForwardSSLKeySize        => Forward SSL Key Size, to follow 2.3 specs but may broke old TC 3.2
     * -ForwardSSLKeySize        => Don't Forward SSL Key Size, will make mod_jk works with all TC release
     *  ForwardURICompat         => Forward URI normally, less spec compliant but mod_rewrite compatible (old TC)
     *  ForwardURICompatUnparsed => Forward URI as unparsed, spec compliant but broke mod_rewrite (old TC)
     *  ForwardURIEscaped        => Forward URI escaped and Tomcat (3.3 rc2) stuff will do the decoding part
     * +ForwardSSLCertChain      => Forward SSL certificate chain
     * -ForwardSSLCertChain      => Don't forward SSL certificate chain
     */
    AP_INIT_RAW_ARGS("JkOptions", jk_set_options, NULL, RSRC_CONF,
                     "Set one of more options to configure the mod_jk module"),

    /*
     * JkEnvVar let user defines envs var passed from WebServer to
     * Servlet Engine
     */
    AP_INIT_TAKE12("JkEnvVar", jk_add_env_var, NULL, RSRC_CONF,
                   "Adds a name of environment variable and an optional value "
                   "that should be sent to servlet-engine"),

    AP_INIT_RAW_ARGS("JkWorkerProperty", jk_set_worker_property,
                     NULL, RSRC_CONF,
                     "Set workers.properties formated directive"),

    {NULL}
};

/* ========================================================================= */
/* The JK module handlers                                                    */
/* ========================================================================= */

/** Util - cleanup shmem.
 */
static apr_status_t jk_cleanup_shmem(void *data)
{
    /* Force the watchdog thread exit */
    if (jk_watchdog_interval > 0) {
        jk_watchdog_interval = 0;
        while (jk_watchdog_running)
            apr_sleep(apr_time_from_sec(1));
    }
    jk_shm_close();
    return APR_SUCCESS;
}

/** Main service method, called to forward a request to tomcat
 */
static int jk_handler(request_rec * r)
{
    const char *worker_name;
    jk_server_conf_t *xconf;
    int rc, dmt = 1;

    /* We do DIR_MAGIC_TYPE here to make sure TC gets all requests, even
     * if they are directory requests, in case there are no static files
     * visible to Apache and/or DirectoryIndex was not used. This is only
     * used when JkOptions has ForwardDirectories set. */
    /* Not for me, try next handler */
    if (strcmp(r->handler, JK_HANDLER)
        && (dmt = strcmp(r->handler, DIR_MAGIC_TYPE)))
        return DECLINED;

    xconf = (jk_server_conf_t *) ap_get_module_config(r->server->module_config,
                                                      &jk_module);
    JK_TRACE_ENTER(xconf->log);
    if (apr_table_get(r->subprocess_env, "no-jk")) {
        if (JK_IS_DEBUG_LEVEL(xconf->log))
            jk_log(xconf->log, JK_LOG_DEBUG,
                   "Into handler no-jk env var detected for uri=%s, declined",
                   r->uri);

        JK_TRACE_EXIT(xconf->log);
        return DECLINED;
    }

    /* Was the option to forward directories to Tomcat set? */
    if (!dmt && !(xconf->options & JK_OPT_FWDDIRS)) {
        JK_TRACE_EXIT(xconf->log);
        return DECLINED;
    }
    worker_name = apr_table_get(r->notes, JK_NOTE_WORKER_NAME);

    /* Set up r->read_chunked flags for chunked encoding, if present */
    if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != APR_SUCCESS) {
        JK_TRACE_EXIT(xconf->log);
        return rc;
    }

    if (worker_name == NULL) {
        /* we may be here because of a manual directive ( that overrides
           translate and
           sets the handler directly ). We still need to know the worker.
         */
        worker_name = apr_table_get(r->subprocess_env, xconf->worker_indicator);
        if (worker_name) {
          /* The JkWorkerIndicator environment variable has
           * been used to explicitely set the worker without JkMount.
           * This is useful in combination with LocationMatch or mod_rewrite.
           */
            if (JK_IS_DEBUG_LEVEL(xconf->log))
                jk_log(xconf->log, JK_LOG_DEBUG,
                       "Retrieved worker (%s) from env %s for %s",
                       worker_name, xconf->worker_indicator, r->uri);
        }
        else if (worker_env.num_of_workers == 1) {
          /** We have a single worker ( the common case ).
              ( lb is a bit special, it should count as a single worker but
              I'm not sure how ). We also have a manual config directive that
              explicitely give control to us. */
            worker_name = worker_env.worker_list[0];
            if (JK_IS_DEBUG_LEVEL(xconf->log))
                jk_log(xconf->log, JK_LOG_DEBUG,
                       "Single worker (%s) configuration for %s",
                       worker_name, r->uri);
        }
        else {
            if (!xconf->uw_map) {
                if (JK_IS_DEBUG_LEVEL(xconf->log))
                    jk_log(xconf->log, JK_LOG_DEBUG,
                           "missing uri map for %s:%s",
                           xconf->s->server_hostname ? xconf->s->server_hostname : "_default_",
                           r->uri);
            }
            else {
                rule_extension_t *e;
                worker_name = map_uri_to_worker_ext(xconf->uw_map, r->uri,
                                                    NULL, &e, NULL, xconf->log);
                ap_set_module_config(r->request_config, &jk_module, e);
            }

            if (worker_name == NULL && worker_env.num_of_workers) {
                worker_name = worker_env.worker_list[0];
                if (JK_IS_DEBUG_LEVEL(xconf->log))
                    jk_log(xconf->log, JK_LOG_DEBUG,
                           "Using first worker (%s) from %d workers for %s",
                           worker_name, worker_env.num_of_workers, r->uri);
            }
        }
        if (worker_name)
            apr_table_setn(r->notes, JK_NOTE_WORKER_NAME, worker_name);
    }

    if (JK_IS_DEBUG_LEVEL(xconf->log))
       jk_log(xconf->log, JK_LOG_DEBUG, "Into handler %s worker=%s"
              " r->proxyreq=%d",
              r->handler, STRNULL_FOR_NULL(worker_name), r->proxyreq);

    /* If this is a proxy request, we'll notify an error */
    if (r->proxyreq) {
        jk_log(xconf->log, JK_LOG_INFO, "Proxy request for worker=%s"
              " is not allowed",
              STRNULL_FOR_NULL(worker_name));
        JK_TRACE_EXIT(xconf->log);
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    if (worker_name) {
        jk_worker_t *worker = wc_get_worker_for_name(worker_name, xconf->log);

        /* If the remote client has aborted, just ignore the request */
        if (r->connection->aborted) {
            jk_log(xconf->log, JK_LOG_INFO, "Client connection aborted for"
                   " worker=%s",
                   worker_name);
            JK_TRACE_EXIT(xconf->log);
            return OK;
        }

        if (worker) {
            long micro, seconds;
            char *duration = NULL;
            apr_time_t rd;
            apr_time_t request_begin = 0;
            int is_error = HTTP_INTERNAL_SERVER_ERROR;
            int rc = JK_FALSE;
            apache_private_data_t private_data;
            jk_ws_service_t s;
            jk_pool_atom_t buf[SMALL_POOL_SIZE];
            jk_open_pool(&private_data.p, buf, sizeof(buf));

            private_data.read_body_started = JK_FALSE;
            private_data.r = r;

            /* Maintain will be done by watchdog thread */
            if (!jk_watchdog_interval)
                wc_maintain(xconf->log);
            jk_init_ws_service(&s);
            s.ws_private = &private_data;
            s.pool = &private_data.p;
            apr_table_setn(r->notes, JK_NOTE_WORKER_TYPE,
                           wc_get_name_for_type(worker->type, xconf->log));

            request_begin = apr_time_now();

            if (init_ws_service(&private_data, &s, xconf)) {
                jk_endpoint_t *end = NULL;

                /* Use per/thread pool ( or "context" ) to reuse the
                   endpoint. It's a bit faster, but I don't know
                   how to deal with load balancing - but it's usefull for JNI
                 */

                /* worker->get_endpoint might fail if we are out of memory so check */
                /* and handle it */
                if (worker->get_endpoint(worker, &end, xconf->log)) {
                    rc = end->service(end, &s, xconf->log,
                                      &is_error);
                    end->done(&end, xconf->log);
                    if (s.content_read < s.content_length ||
                        (s.is_chunked && !s.no_more_chunks)) {
                        /*
                         * If the servlet engine didn't consume all of the
                         * request data, consume and discard all further
                         * characters left to read from client
                         */
                        char *buff = apr_palloc(r->pool, 2048);
                        int consumed = 0;
                        if (buff != NULL) {
                            int rd;
                            while ((rd =
                                    ap_get_client_block(r, buff, 2048)) > 0) {
                                s.content_read += rd;
                                consumed += rd;
                            }
                        }
                        if (JK_IS_DEBUG_LEVEL(xconf->log)) {
                           jk_log(xconf->log, JK_LOG_DEBUG,
                                  "Consumed %d bytes of remaining request data for worker=%s",
                                  consumed, STRNULL_FOR_NULL(worker_name));
                        }
                    }
                }
                else {            /* this means we couldn't get an endpoint */
                    jk_log(xconf->log, JK_LOG_ERROR, "Could not get endpoint"
                           " for worker=%s",
                           worker_name);
                    rc = 0;       /* just to make sure that we know we've failed */
                }
            }
            else {
                jk_log(xconf->log, JK_LOG_ERROR, "Could not init service"
                       " for worker=%s",
                       worker_name);
                jk_close_pool(&private_data.p);
                JK_TRACE_EXIT(xconf->log);
                return HTTP_INTERNAL_SERVER_ERROR;
            }
            rd = apr_time_now() - request_begin;
            seconds = (long)apr_time_sec(rd);
            micro = (long)(rd - apr_time_from_sec(seconds));

            duration = apr_psprintf(r->pool, "%.1ld.%.6ld", seconds, micro);
            apr_table_setn(r->notes, JK_NOTE_REQUEST_DURATION, duration);
            if (s.route && *s.route)
                apr_table_setn(r->notes, JK_NOTE_WORKER_ROUTE, s.route);

            if (xconf->format != NULL) {
                request_log_transaction(r, xconf);
            }

            jk_close_pool(&private_data.p);

            if (rc > 0) {
                if (s.extension.use_server_error_pages &&
                    s.http_response_status >= s.extension.use_server_error_pages) {
                    if (JK_IS_DEBUG_LEVEL(xconf->log))
                        jk_log(xconf->log, JK_LOG_DEBUG, "Forwarding status=%d"
                               " for worker=%s",
                               s.http_response_status, worker_name);
                    JK_TRACE_EXIT(xconf->log);
                    return s.http_response_status;
                }
                /* If tomcat returned no body and the status is not OK,
                   let apache handle the error code */

                if (!r->sent_bodyct && r->status >= HTTP_BAD_REQUEST) {
                    jk_log(xconf->log, JK_LOG_INFO, "No body with status=%d"
                           " for worker=%s",
                           r->status, worker_name);
                    JK_TRACE_EXIT(xconf->log);
                    return r->status;
                }
                if (JK_IS_DEBUG_LEVEL(xconf->log))
                    jk_log(xconf->log, JK_LOG_DEBUG, "Service finished"
                           " with status=%d for worker=%s",
                           r->status, worker_name);
                JK_TRACE_EXIT(xconf->log);
                return OK;      /* NOT r->status, even if it has changed. */
            }
            else if (rc == JK_CLIENT_ERROR) {
                if (is_error != HTTP_REQUEST_ENTITY_TOO_LARGE)
                    r->connection->aborted = 1;
                jk_log(xconf->log, JK_LOG_INFO, "Aborting connection"
                       " for worker=%s",
                       worker_name);
                JK_TRACE_EXIT(xconf->log);
                return is_error;
            }
            else {
                jk_log(xconf->log, JK_LOG_INFO, "Service error=%d"
                       " for worker=%s",
                       rc, worker_name);
                JK_TRACE_EXIT(xconf->log);
                return is_error;
            }
        }
        else {
            jk_log(xconf->log, JK_LOG_INFO, "Could not find a worker"
                   " for worker name=%s",
                   worker_name);
            JK_TRACE_EXIT(xconf->log);
            return HTTP_INTERNAL_SERVER_ERROR;
        }
    }

    JK_TRACE_EXIT(xconf->log);
    return DECLINED;
}

/** Standard apache hook, cleanup jk
 */
static apr_status_t jk_apr_pool_cleanup(void *data)
{
    server_rec *s = data;

    if (jk_worker_properties) {
        jk_map_free(&jk_worker_properties);
        jk_worker_properties = NULL;
        jk_worker_file = NULL;
        jk_mount_copy_all = JK_FALSE;
    }

    while (NULL != s) {
        jk_server_conf_t *conf =
            (jk_server_conf_t *) ap_get_module_config(s->module_config,
                                                      &jk_module);

        if (conf && conf->was_initialized == JK_TRUE) {
            /* On pool cleanup pass NULL for the jk_logger to
               prevent segmentation faults on Windows because
               we can't guarantee what order pools get cleaned
               up between APR implementations. */
            wc_close(NULL);
            if (conf->uri_to_context) {
                jk_map_free(&conf->uri_to_context);
                /* We cannot have allocated uw_map
                 * unless we've allocated uri_to_context
                 */
                if (conf->uw_map)
                    uri_worker_map_free(&conf->uw_map, NULL);
            }
            conf->was_initialized = JK_FALSE;
        }
        s = s->next;
    }
    return APR_SUCCESS;
}

/** Create default jk_config. XXX This is mostly server-independent,
    all servers are using something similar - should go to common.
 */
static void *create_jk_config(apr_pool_t * p, server_rec * s)
{
    jk_server_conf_t *c =
        (jk_server_conf_t *) apr_pcalloc(p, sizeof(jk_server_conf_t));

    c->was_initialized = JK_FALSE;

    if (s->is_virtual) {
        c->mountcopy = JK_UNSET;
        c->mount_file_reload = JK_UNSET;
        c->log_level = JK_UNSET;
        c->ssl_enable = JK_UNSET;
        c->strip_session = JK_UNSET;
    } else {
        if (!jk_map_alloc(&(c->uri_to_context))) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "Memory error");
        }
        c->mountcopy = JK_FALSE;
        c->mount_file_reload = JK_URIMAP_DEF_RELOAD;
        c->log_level = JK_LOG_DEF_LEVEL;
        c->options = JK_OPT_DEFAULT;
        c->worker_indicator = JK_ENV_WORKER_NAME;

        /*
         * Configurable environment variables to overwrite
         * request information using meta data send by a
         * proxy in front of us.
         */
        c->remote_addr_indicator = JK_ENV_REMOTE_ADDR;
        c->remote_host_indicator = JK_ENV_REMOTE_HOST;
        c->remote_user_indicator = JK_ENV_REMOTE_USER;
        c->auth_type_indicator = JK_ENV_AUTH_TYPE;
        c->local_name_indicator = JK_ENV_LOCAL_NAME;
        c->local_port_indicator = JK_ENV_LOCAL_PORT;

        /*
         * By default we will try to gather SSL info.
         * Disable this functionality through JkExtractSSL
         */
        c->ssl_enable = JK_TRUE;
        /*
         * The defaults ssl indicators match those in mod_ssl (seems
         * to be in more use).
         */
        c->https_indicator = JK_ENV_HTTPS;
        c->certs_indicator = JK_ENV_CERTS;
        c->cipher_indicator = JK_ENV_CIPHER;
        c->certchain_prefix = JK_ENV_CERTCHAIN_PREFIX;
        c->session_indicator = JK_ENV_SESSION;
        c->key_size_indicator = JK_ENV_KEY_SIZE;
        c->strip_session = JK_FALSE;
    }
    c->envvars_has_own = JK_FALSE;

    c->s = s;
    apr_pool_cleanup_register(p, s, jk_apr_pool_cleanup, apr_pool_cleanup_null);
    return c;
}


/*
 * Utility - copy items from apr table src to dst,
 * for keys that exist in src but not in dst.
 */
static void merge_apr_table(apr_table_t *src, apr_table_t *dst)
{
    int i;
    const apr_array_header_t *arr;
    const apr_table_entry_t *elts;

    arr = apr_table_elts(src);
    elts = (const apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; ++i) {
        if (!apr_table_get(dst, elts[i].key)) {
            apr_table_setn(dst, elts[i].key, elts[i].val);
        }
    }
}


/** Standard apache callback, merge jk options specified in <Directory>
    context or <Host>.
 */
static void *merge_jk_config(apr_pool_t * p, void *basev, void *overridesv)
{
    jk_server_conf_t *base = (jk_server_conf_t *) basev;
    jk_server_conf_t *overrides = (jk_server_conf_t *) overridesv;

    if (!overrides->log_file)
        overrides->log_file = base->log_file;
    if (overrides->log_level == JK_UNSET)
        overrides->log_level = base->log_level;

    if (!overrides->stamp_format_string)
        overrides->stamp_format_string = base->stamp_format_string;
    if (!overrides->format_string)
        overrides->format_string = base->format_string;

    if (!overrides->worker_indicator)
        overrides->worker_indicator = base->worker_indicator;

    if (!overrides->remote_addr_indicator)
        overrides->remote_addr_indicator = base->remote_addr_indicator;
    if (!overrides->remote_host_indicator)
        overrides->remote_host_indicator = base->remote_host_indicator;
    if (!overrides->remote_user_indicator)
        overrides->remote_user_indicator = base->remote_user_indicator;
    if (!overrides->auth_type_indicator)
        overrides->auth_type_indicator = base->auth_type_indicator;
    if (!overrides->local_name_indicator)
        overrides->local_name_indicator = base->local_name_indicator;
    if (!overrides->local_port_indicator)
        overrides->local_port_indicator = base->local_port_indicator;

    if (overrides->ssl_enable == JK_UNSET)
        overrides->ssl_enable = base->ssl_enable;
    if (!overrides->https_indicator)
        overrides->https_indicator = base->https_indicator;
    if (!overrides->certs_indicator)
        overrides->certs_indicator = base->certs_indicator;
    if (!overrides->cipher_indicator)
        overrides->cipher_indicator = base->cipher_indicator;
    if (!overrides->certchain_prefix)
        overrides->certchain_prefix = base->certchain_prefix;
    if (!overrides->session_indicator)
        overrides->session_indicator = base->session_indicator;
    if (!overrides->key_size_indicator)
        overrides->key_size_indicator = base->key_size_indicator;

/* Don't simply accumulate bits in the JK_OPT_FWDURIMASK region, */
/* because those are multi-bit values. */
    if (overrides->options & JK_OPT_FWDURIMASK)
        overrides->options |= (base->options & ~base->exclude_options) & ~JK_OPT_FWDURIMASK;
    else
        overrides->options |= (base->options & ~base->exclude_options);

    if (base->envvars) {
        if (overrides->envvars && overrides->envvars_has_own) {
/* merge_apr_table() preserves existing entries in overrides table */
            merge_apr_table(base->envvars, overrides->envvars);
            merge_apr_table(base->envvars_def, overrides->envvars_def);
        }
        else {
            overrides->envvars = base->envvars;
            overrides->envvars_def = base->envvars_def;
            overrides->envvar_items = base->envvar_items;
        }
    }

    if (overrides->mountcopy == JK_UNSET && jk_mount_copy_all == JK_TRUE) {
        overrides->mountcopy = JK_TRUE;
    }
    if (overrides->uri_to_context && overrides->mountcopy == JK_TRUE) {
/* jk_map_copy() preserves existing entries in overrides map */
        if (jk_map_copy(base->uri_to_context, overrides->uri_to_context) == JK_FALSE) {
                jk_error_exit(JKLOG_MARK, APLOG_EMERG, overrides->s, p, "Memory error");
        }
        if (!overrides->mount_file)
            overrides->mount_file = base->mount_file;
    }
    if (overrides->mountcopy == JK_TRUE) {
        if (!overrides->alias_dir)
            overrides->alias_dir = base->alias_dir;
    }
    if (overrides->mount_file_reload == JK_UNSET)
        overrides->mount_file_reload = base->mount_file_reload;
    if (overrides->strip_session == JK_UNSET) {
        overrides->strip_session = base->strip_session;
        overrides->strip_session_name = base->strip_session_name;
    }
    return overrides;
}

static int JK_METHOD jk_log_to_file(jk_logger_t *l, int level,
                                    int used, char *what)
{
    if (l &&
        (l->level <= level || level == JK_LOG_REQUEST_LEVEL) &&
        l->logger_private && what && used > 0) {
        jk_file_logger_t *p = l->logger_private;
        if (p->jklogfp) {
            apr_status_t rv;
            apr_size_t wrote;
#if defined(WIN32)
            what[used++] = '\r';
#endif
            what[used++] = '\n';
            wrote = used;
            rv = apr_global_mutex_lock(jk_log_lock);
            if (rv != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
                             "apr_global_mutex_lock(jk_log_lock) failed");
                /* XXX: Maybe this should be fatal? */
            }
            rv = apr_file_write(p->jklogfp, what, &wrote);
            if (rv != APR_SUCCESS) {
                char error[256];
                apr_strerror(rv, error, 254);
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                             "mod_jk: jk_log_to_file %s failed: %s",
                             what, error);
            }
            rv = apr_global_mutex_unlock(jk_log_lock);
            if (rv != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
                             "apr_global_mutex_unlock(jk_log_lock) failed");
                /* XXX: Maybe this should be fatal? */
            }
        }

        return JK_TRUE;
    }

    return JK_FALSE;
}

/*
** +-------------------------------------------------------+
** |                                                       |
** |              jk logfile support                       |
** |                                                       |
** +-------------------------------------------------------+
*/

static apr_status_t jklog_cleanup(void *d)
{
    /* hgomez@20070425 */
    /* Clean up pointer content */
    if (d != NULL)
        *(jk_logger_t **)d = NULL;

    return APR_SUCCESS;
}

static int open_jklog(server_rec * s, apr_pool_t * p)
{
    jk_server_conf_t *conf;
    const char *fname;
    apr_status_t rc;
    apr_file_t *jklogfp;
    piped_log *pl;
    jk_logger_t *jkl;
    jk_file_logger_t *flp;
    int jklog_flags = (APR_WRITE | APR_APPEND | APR_CREATE);
    apr_fileperms_t jklog_mode =
        (APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD);

    conf = ap_get_module_config(s->module_config, &jk_module);

    if (conf->log_file == NULL) {
        conf->log_file = ap_server_root_relative(p, JK_LOG_DEF_FILE);
        if (conf->log_file)
            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
                         "No JkLogFile defined in httpd.conf. "
                         "Using default %s", conf->log_file);
    }
    if (*(conf->log_file) == '\0') {
        ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s,
                     "mod_jk: Invalid JkLogFile EMPTY");
        conf->log = main_log;
        return 0;
    }

    jklogfp = apr_hash_get(jk_log_fps, conf->log_file, APR_HASH_KEY_STRING);
    if (!jklogfp) {
        if (*conf->log_file == '|') {
            if ((pl = ap_open_piped_log(p, conf->log_file + 1)) == NULL) {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
                             "mod_jk: could not open reliable pipe "
                             "to jk log %s", conf->log_file + 1);
                return -1;
            }
            jklogfp = (void *)ap_piped_log_write_fd(pl);
        }
        else {
            fname = ap_server_root_relative(p, conf->log_file);
            if (!fname) {
                ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s,
                             "mod_jk: Invalid JkLog " "path %s", conf->log_file);
                return -1;
            }
            if ((rc = apr_file_open(&jklogfp, fname,
                                    jklog_flags, jklog_mode, p))
                != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
                             "mod_jk: could not open JkLog " "file %s", fname);
                return -1;
            }
        }
        apr_file_inherit_set(jklogfp);
        apr_hash_set(jk_log_fps, conf->log_file, APR_HASH_KEY_STRING, jklogfp);
    }
    conf->jklogfp = jklogfp;
    jkl = (jk_logger_t *)apr_palloc(p, sizeof(jk_logger_t));
    flp = (jk_file_logger_t *) apr_palloc(p, sizeof(jk_file_logger_t));
    if (jkl && flp) {
        jkl->log = jk_log_to_file;
        jkl->level = conf->log_level;
        jkl->logger_private = flp;
        flp->jklogfp = conf->jklogfp;
        conf->log = jkl;
        jk_set_time_fmt(conf->log, conf->stamp_format_string);
        if (main_log == NULL) {
            main_log = conf->log;

            /* hgomez@20070425 */
            /* Shouldn't we clean both conf->log and main_log ?                   */
            /* Also should we pass pointer (ie: main_log) or handle (*main_log) ? */
            apr_pool_cleanup_register(p, &main_log,
                                      jklog_cleanup,
                                      apr_pool_cleanup_null);
        }

        return 0;
    }

    return -1;
}

#if APR_HAS_THREADS

static void * APR_THREAD_FUNC jk_watchdog_func(apr_thread_t *thd, void *data)
{
    int i;
    jk_server_conf_t *conf = (jk_server_conf_t *)data;

    if (JK_IS_DEBUG_LEVEL(conf->log))
        jk_log(conf->log, JK_LOG_DEBUG,
               "Watchdog thread initialized with %d second interval",
               jk_watchdog_interval);
    for (;;) {
        for (i = 0; i < (jk_watchdog_interval * 10); i++) {
            /* apr_sleep() uses microseconds */
            apr_sleep((apr_time_t)(100000));
            if (!jk_watchdog_interval)
                break;
        }
        if (!jk_watchdog_interval)
            break;
        if (JK_IS_DEBUG_LEVEL(conf->log))
           jk_log(conf->log, JK_LOG_DEBUG,
                  "Watchdog thread running");
        jk_watchdog_running = 1;
        wc_maintain(conf->log);
        if (!jk_watchdog_interval)
            break;
    }
    jk_watchdog_running = 0;
    return NULL;
}

#endif

/** Standard apache callback, initialize jk.
 */
static void jk_child_init(apr_pool_t * pconf, server_rec * s)
{
    jk_server_conf_t *conf;
    apr_status_t rv;
    int rc;
    apr_thread_t *wdt;

    conf = ap_get_module_config(s->module_config, &jk_module);

    rv = apr_global_mutex_child_init(&jk_log_lock, NULL, pconf);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
                     "mod_jk: could not init JK log lock in child");
    }

    JK_TRACE_ENTER(conf->log);

    if (jk_watchdog_interval) {
#if APR_HAS_THREADS
        rv = apr_thread_create(&wdt, NULL, jk_watchdog_func, conf, pconf);
        if (rv != APR_SUCCESS) {
            jk_log(conf->log, JK_LOG_ERROR,
                   "Could not init watchdog thread, error=%d", rv);
            jk_watchdog_interval = 0;
        }
        apr_thread_detach(wdt);
#endif
    }

    if ((rc = jk_shm_attach(jk_shm_file, jk_shm_size, conf->log)) == 0) {
        apr_pool_cleanup_register(pconf, conf->log, jk_cleanup_shmem,
                                  apr_pool_cleanup_null);
    }
    else
        jk_log(conf->log, JK_LOG_ERROR, "Attaching shm:%s errno=%d",
               jk_shm_name(), rc);

    if (JK_IS_DEBUG_LEVEL(conf->log))
        jk_log(conf->log, JK_LOG_DEBUG, "Initialized %s", JK_FULL_EXPOSED_VERSION);
    JK_TRACE_EXIT(conf->log);
}

/** Initialize jk, using worker.properties.
    We also use apache commands ( JkWorker, etc), but this use is
    deprecated, as we'll try to concentrate all config in
    workers.properties, urimap.properties, and ajp14 autoconf.

    Apache config will only be used for manual override, using
    SetHandler and normal apache directives ( but minimal jk-specific
    stuff )
*/
static int init_jk(apr_pool_t * pconf, jk_server_conf_t * conf,
                    server_rec * s)
{
    int rc;
    int is_threaded;
    int mpm_threads = 1;

    if (!jk_worker_properties)
        jk_map_alloc(&jk_worker_properties);
    jk_map_put(jk_worker_properties, "ServerRoot", ap_server_root, NULL);

    /* Set default connection cache size for multi-threaded MPMs */
    if (ap_mpm_query(AP_MPMQ_IS_THREADED, &is_threaded) == APR_SUCCESS &&
        is_threaded != AP_MPMQ_NOT_SUPPORTED) {
        if (ap_mpm_query(AP_MPMQ_MAX_THREADS, &mpm_threads) != APR_SUCCESS)
            mpm_threads = 1;
    }
    if (mpm_threads > 1) {
#if _MT_CODE
        /* _MT_CODE  */
        if (JK_IS_DEBUG_LEVEL(conf->log)) {
#if !defined(WIN32) && !defined(NETWARE)
#if USE_FLOCK_LK
            jk_log(conf->log, JK_LOG_DEBUG,
                   "Using flock() for locking.");
#else
            jk_log(conf->log, JK_LOG_DEBUG,
                   "Using fcntl() for locking.");
#endif /* USE_FLOCK_LK */
#else  /* WIN32 */
            jk_log(conf->log, JK_LOG_DEBUG,
                   "Not using locking.");
#endif
        }
#else
        /* !_MT_CODE */
        ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
                     "Cannot run prefork mod_jk on threaded server.");
        return JK_FALSE;
#endif
    }
    if (JK_IS_DEBUG_LEVEL(conf->log))
        jk_log(conf->log, JK_LOG_DEBUG,
               "Setting default connection pool max size to %d",
               mpm_threads);
    jk_set_worker_def_cache_size(mpm_threads);

    if ((jk_worker_file != NULL) &&
        !jk_map_read_properties(jk_worker_properties, NULL, jk_worker_file, NULL,
                                JK_MAP_HANDLE_DUPLICATES, conf->log)) {
        ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
                     "Error in reading worker properties from '%s'",
                     jk_worker_file);
        return JK_FALSE;
    }

    if (jk_map_resolve_references(jk_worker_properties, "worker.",
                                  1, 1, conf->log) == JK_FALSE) {
        ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
                     "Error in resolving configuration references");
        return JK_FALSE;
    }

#if !defined(WIN32) && !defined(NETWARE)
    if (!jk_shm_file) {
        jk_shm_file = ap_server_root_relative(pconf, JK_SHM_DEF_FILE);
        if (jk_shm_file)
            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
                         "No JkShmFile defined in httpd.conf. "
                         "Using default %s", jk_shm_file);
    }
#endif
    if (jk_shm_size == 0)
        jk_shm_size = jk_shm_calculate_size(jk_worker_properties, conf->log);
    else if (jk_shm_size_set) {
        jk_log(conf->log, JK_LOG_WARNING,
               "The optimal shared memory size can now be determined automatically.");
        jk_log(conf->log, JK_LOG_WARNING,
               "You can remove the JkShmSize directive if you want to use the optimal size.");
    }
    if ((rc = jk_shm_open(jk_shm_file, jk_shm_size, conf->log)) == 0) {
        apr_pool_cleanup_register(pconf, conf->log,
                                  jk_cleanup_shmem,
                                  apr_pool_cleanup_null);
    }
    else
        jk_log(conf->log, JK_LOG_ERROR,
               "Initializing shm:%s errno=%d. Load balancing workers will not function properly.",
               jk_shm_name(), rc);

    /* we add the URI->WORKER MAP since workers using AJP14
       will feed it */
    worker_env.uri_to_worker = conf->uw_map;
    worker_env.virtual = "*";   /* for now */
#if ((AP_MODULE_MAGIC_AT_LEAST(20051115,4)) && !defined(API_COMPATIBILITY)) || (MODULE_MAGIC_NUMBER_MAJOR >= 20060905)
    worker_env.server_name = (char *)ap_get_server_description();
#else
    worker_env.server_name = (char *)ap_get_server_version();
#endif
    worker_env.pool = pconf;

    if (wc_open(jk_worker_properties, &worker_env, conf->log)) {
        ap_add_version_component(pconf, JK_EXPOSED_VERSION);
        jk_log(conf->log, JK_LOG_INFO,
               "%s initialized",
               JK_FULL_EXPOSED_VERSION);
    }
    else {
        ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
                     "Error in creating the workers."
                     " Please consult your mod_jk log file '%s'.", conf->log_file);
        return JK_FALSE;
    }
    return JK_TRUE;
}

static int jk_post_config(apr_pool_t * pconf,
                          apr_pool_t * plog,
                          apr_pool_t * ptemp, server_rec * s)
{
    apr_status_t rv;
    jk_server_conf_t *conf;
    server_rec *srv = s;
    const char *err_string = NULL;
    void *data = NULL;

    apr_pool_userdata_get(&data, JK_LOG_LOCK_KEY, s->process->pool);
    if (data == NULL) {
        /* create the jk log lockfiles in the parent */
        if ((rv = apr_global_mutex_create(&jk_log_lock, NULL,
                                          APR_LOCK_DEFAULT,
                                          s->process->pool)) != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
                         "mod_jk: could not create jk_log_lock");
            return HTTP_INTERNAL_SERVER_ERROR;
        }

#if JK_NEED_SET_MUTEX_PERMS
#if (MODULE_MAGIC_NUMBER_MAJOR >= 20090208)
        rv = ap_unixd_set_global_mutex_perms(jk_log_lock);
#else
        rv = unixd_set_global_mutex_perms(jk_log_lock);
#endif
        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
                         "mod_jk: Could not set permissions on "
                         "jk_log_lock; check User and Group directives");
            return HTTP_INTERNAL_SERVER_ERROR;
        }
#endif
        apr_pool_userdata_set((const void *)jk_log_lock, JK_LOG_LOCK_KEY,
                              apr_pool_cleanup_null, s->process->pool);
    } else {
        jk_log_lock = (apr_global_mutex_t *)data;
    }

    main_server = s;
    jk_log_fps = apr_hash_make(pconf);

    if (!s->is_virtual) {
        conf = (jk_server_conf_t *)ap_get_module_config(s->module_config,
                                                        &jk_module);
        if (conf->was_initialized == JK_FALSE) {
            /* step through the servers and open each jk logfile
             * and do additional post config initialization.
             */
            for (; srv; srv = srv->next) {
                jk_server_conf_t *sconf = (jk_server_conf_t *)ap_get_module_config(srv->module_config,
                                                                                   &jk_module);

/*
 * If a virtual server contains no JK directive, httpd shares
 * the config structure. But we don't want to share some settings
 * by default, especially the JkMount rules.
 * Therefore we check, if this config structure really belongs to this
 * vhost, otherwise we create a new one and merge.
 */
                if (sconf && sconf->s != srv) {
                    jk_server_conf_t *srvconf = (jk_server_conf_t *)create_jk_config(pconf, srv);
                    sconf = (jk_server_conf_t *)merge_jk_config(pconf, sconf, srvconf);
                    ap_set_module_config(srv->module_config, &jk_module, sconf);

                }

                if (sconf && sconf->was_initialized == JK_FALSE) {
                    sconf->was_initialized = JK_TRUE;
                    if (open_jklog(srv, pconf))
                        return HTTP_INTERNAL_SERVER_ERROR;
                    sconf->options &= ~sconf->exclude_options;
                    if (sconf->uri_to_context) {
                        if (!uri_worker_map_alloc(&(sconf->uw_map),
                                                  sconf->uri_to_context, sconf->log))
                            jk_error_exit(JKLOG_MARK, APLOG_EMERG, srv,
                                          srv->process->pool, "Memory error");
                        if (sconf->options & JK_OPT_REJECTUNSAFE)
                            sconf->uw_map->reject_unsafe = 1;
                        else
                            sconf->uw_map->reject_unsafe = 0;
                        if (sconf->mount_file) {
                            sconf->uw_map->fname = sconf->mount_file;
                            sconf->uw_map->reload = sconf->mount_file_reload;
                            uri_worker_map_switch(sconf->uw_map, sconf->log);
                            uri_worker_map_load(sconf->uw_map, sconf->log);
                        }
                    }
                    else {
                        if (sconf->mountcopy == JK_TRUE) {
                            sconf->uw_map = conf->uw_map;
                        }
                    }
                    if (sconf->format_string) {
                        sconf->format =
                            parse_request_log_string(pconf, sconf->format_string, &err_string);
                        if (sconf->format == NULL)
                            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
                                         "JkRequestLogFormat format array NULL");
                    }
                    if (sconf->envvars && sconf->envvars_has_own) {
                        int i;
                        const apr_array_header_t *arr;
                        const apr_table_entry_t *elts;
                        envvar_item *item;
                        const char *envvar_def;

                        arr = apr_table_elts(sconf->envvars);
                        if (arr) {
                            elts = (const apr_table_entry_t *)arr->elts;
                            for (i = 0; i < arr->nelts; ++i) {
                                item = (envvar_item *)apr_array_push(sconf->envvar_items);
                                if (!item)
                                    return HTTP_INTERNAL_SERVER_ERROR;
                                item->name = elts[i].key;
                                envvar_def = apr_table_get(sconf->envvars_def, elts[i].key);
                                if (envvar_def && !strcmp("1", envvar_def) ) {
                                    item->value = elts[i].val;
                                    item->has_default = 1;
                                }
                                else {
                                    item->value = "";
                                    item->has_default = 0;
                                }
                            }
                        }
                    }
                }
            }
            conf->was_initialized = JK_TRUE;
            if (init_jk(pconf, conf, s) == JK_FALSE)
                return HTTP_INTERNAL_SERVER_ERROR;
            if (conf->uw_map) {
                uri_worker_map_ext(conf->uw_map, conf->log);
                uri_worker_map_switch(conf->uw_map, conf->log);
            }
            for (srv = s; srv; srv = srv->next) {
                jk_server_conf_t *sconf = (jk_server_conf_t *)ap_get_module_config(srv->module_config,
                                                                                   &jk_module);
                if (conf->uw_map != sconf->uw_map && sconf->uw_map) {
                    uri_worker_map_ext(sconf->uw_map, sconf->log);
                    uri_worker_map_switch(sconf->uw_map, sconf->log);
                }
            }

        }
    }

    return OK;
}

/** Use the internal mod_jk mappings to find if this is a request for
 *    tomcat and what worker to use.
 */
static int jk_translate(request_rec * r)
{
    ap_set_module_config(r->request_config, &jk_module, NULL);

    if (!r->proxyreq) {
        jk_server_conf_t *conf =
            (jk_server_conf_t *) ap_get_module_config(r->server->
                                                      module_config,
                                                      &jk_module);

        if (conf) {
            const char *worker;
            if ((r->handler != NULL) && (!strcmp(r->handler, JK_HANDLER))) {
                /* Somebody already set the handler, probably manual config
                 * or "native" configuration, no need for extra overhead
                 */
                if (JK_IS_DEBUG_LEVEL(conf->log))
                    jk_log(conf->log, JK_LOG_DEBUG,
                           "Manually mapped, no need to call uri_to_worker");
                return DECLINED;
            }

            if (apr_table_get(r->subprocess_env, "no-jk")) {
                if (JK_IS_DEBUG_LEVEL(conf->log))
                    jk_log(conf->log, JK_LOG_DEBUG,
                           "Into translate no-jk env var detected for uri=%s, declined",
                           r->uri);

                return DECLINED;
            }

            /* Special case to make sure that apache can serve a directory
               listing if there are no matches for the DirectoryIndex and
               Tomcat webapps are mapped into apache using JkAutoAlias. */
            if (r->main != NULL && r->main->handler != NULL &&
                (conf->alias_dir != NULL) &&
                !strcmp(r->main->handler, DIR_MAGIC_TYPE)) {

                /* Append the request uri to the JkAutoAlias directory and
                   determine if the file exists. */
                char *clean_uri;
                apr_finfo_t finfo;
                finfo.filetype = APR_NOFILE;
                clean_uri = apr_pstrdup(r->pool, r->uri);
                ap_no2slash(clean_uri);
                /* Map uri to a context static file */
                if (strlen(clean_uri) > 1) {
                    char *context_path = NULL;

                    context_path = apr_pstrcat(r->pool, conf->alias_dir,
                                               ap_os_escape_path(r->pool,
                                                                 clean_uri,
                                                                 1), NULL);
                    if (context_path != NULL) {
                        apr_stat(&finfo, context_path, APR_FINFO_TYPE,
                                 r->pool);
                    }
                }
                if (finfo.filetype != APR_REG) {
                    if (JK_IS_DEBUG_LEVEL(conf->log))
                        jk_log(conf->log, JK_LOG_DEBUG,
                               "JkAutoAlias, no DirectoryIndex file for URI %s",
                               r->uri);
                    return DECLINED;
                }
            }
            if (!conf->uw_map) {
                if (JK_IS_DEBUG_LEVEL(conf->log))
                    jk_log(conf->log, JK_LOG_DEBUG,
                           "missing uri map for %s:%s",
                           conf->s->server_hostname ? conf->s->server_hostname : "_default_",
                           r->uri);
                return DECLINED;
            }
            else {
                rule_extension_t *e;
                worker = map_uri_to_worker_ext(conf->uw_map, r->uri,
                                               NULL, &e, NULL, conf->log);
                ap_set_module_config(r->request_config, &jk_module, e);
            }

            if (worker) {
                r->handler = apr_pstrdup(r->pool, JK_HANDLER);
                apr_table_setn(r->notes, JK_NOTE_WORKER_NAME, worker);

                /* This could be a sub-request, possibly from mod_dir */
                /* Also add the the HANDLER to the main request */
                if (r->main) {
                    r->main->handler = apr_pstrdup(r->main->pool, JK_HANDLER);
                    apr_table_setn(r->main->notes, JK_NOTE_WORKER_NAME, worker);
                }

                return OK;
            }
            else if (conf->alias_dir != NULL) {
                char *clean_uri = apr_pstrdup(r->pool, r->uri);
                ap_no2slash(clean_uri);
                /* Automatically map uri to a context static file */
                if (JK_IS_DEBUG_LEVEL(conf->log))
                    jk_log(conf->log, JK_LOG_DEBUG,
                           "check alias_dir: %s",
                           conf->alias_dir);
                if (strlen(clean_uri) > 1) {
                    /* Get the context directory name */
                    char *context_dir = NULL;
                    char *context_path = NULL;
                    char *child_dir = NULL;
                    char *index = clean_uri;
                    char *suffix = strchr(index + 1, '/');
                    if (suffix != NULL) {
                        int size = suffix - index;
                        context_dir = apr_pstrndup(r->pool, index, size);
                        /* Get the context child directory name */
                        index = index + size + 1;
                        suffix = strchr(index, '/');
                        if (suffix != NULL) {
                            size = suffix - index;
                            child_dir = apr_pstrndup(r->pool, index, size);
                        }
                        else {
                            child_dir = index;
                        }
                        /* Deny access to WEB-INF and META-INF directories */
                        if (child_dir != NULL) {
                            if (JK_IS_DEBUG_LEVEL(conf->log))
                                jk_log(conf->log, JK_LOG_DEBUG,
                                       "AutoAlias child_dir: %s",
                                       child_dir);
                            if (!strcasecmp(child_dir, "WEB-INF")
                                || !strcasecmp(child_dir, "META-INF")) {
                                if (JK_IS_DEBUG_LEVEL(conf->log))
                                    jk_log(conf->log, JK_LOG_DEBUG,
                                           "AutoAlias HTTP_NOT_FOUND for URI: %s",
                                           r->uri);
                                return HTTP_NOT_FOUND;
                            }
                        }
                    }
                    else {
                        context_dir = apr_pstrdup(r->pool, index);
                    }

                    context_path = apr_pstrcat(r->pool, conf->alias_dir,
                                               ap_os_escape_path(r->pool,
                                                                 context_dir,
                                                                 1), NULL);
                    if (context_path != NULL) {
                        apr_finfo_t finfo;
                        finfo.filetype = APR_NOFILE;
                        apr_stat(&finfo, context_path, APR_FINFO_TYPE,
                                 r->pool);
                        if (finfo.filetype == APR_DIR) {
                            char *escurl =
                                ap_os_escape_path(r->pool, clean_uri, 1);
                            char *ret =
                                apr_pstrcat(r->pool, conf->alias_dir, escurl,
                                            NULL);
                            /* Add code to verify real path ap_os_canonical_name */
                            if (ret != NULL) {
                                if (JK_IS_DEBUG_LEVEL(conf->log))
                                    jk_log(conf->log, JK_LOG_DEBUG,
                                           "AutoAlias OK for file: %s",
                                           ret);
                                r->filename = ret;
                                return OK;
                            }
                        }
                        else {
                            /* Deny access to war files in web app directory */
                            int size = strlen(context_dir);
                            if (size > 4
                                && !strcasecmp(context_dir + (size - 4),
                                               ".war")) {
                                if (JK_IS_DEBUG_LEVEL(conf->log))
                                    jk_log(conf->log, JK_LOG_DEBUG,
                                           "AutoAlias HTTP_FORBIDDEN for URI: %s",
                                           r->uri);
                                return HTTP_FORBIDDEN;
                            }
                        }
                    }
                }
            }
            else {
                if (JK_IS_DEBUG_LEVEL(conf->log))
                    jk_log(conf->log, JK_LOG_DEBUG,
                           "no match for %s found",
                           r->uri);
            }
        }
    }

    return DECLINED;
}

/* bypass the directory_walk and file_walk for non-file requests */
static int jk_map_to_storage(request_rec * r)
{

    if (!r->proxyreq && !apr_table_get(r->notes, JK_NOTE_WORKER_NAME)) {
        jk_server_conf_t *conf =
            (jk_server_conf_t *) ap_get_module_config(r->server->
                                                      module_config,
                                                      &jk_module);

        if (conf) {
            const char *worker;
            if ((r->handler != NULL) && (!strcmp(r->handler, JK_HANDLER))) {
                /* Somebody already set the handler, probably manual config
                 * or "native" configuration, no need for extra overhead
                 */
                if (JK_IS_DEBUG_LEVEL(conf->log))
                    jk_log(conf->log, JK_LOG_DEBUG,
                           "Manually mapped, no need to call uri_to_worker");
                return DECLINED;
            }

            if (apr_table_get(r->subprocess_env, "no-jk")) {
                if (JK_IS_DEBUG_LEVEL(conf->log))
                    jk_log(conf->log, JK_LOG_DEBUG,
                           "Into map_to_storage no-jk env var detected for uri=%s, declined",
                           r->uri);

                return DECLINED;
            }
            if (!conf->uw_map) {
                if (JK_IS_DEBUG_LEVEL(conf->log))
                    jk_log(conf->log, JK_LOG_DEBUG,
                           "missing uri map for %s:%s",
                           conf->s->server_hostname ? conf->s->server_hostname : "_default_",
                           r->uri);
                return DECLINED;
            }
            else {
                rule_extension_t *e;
                worker = map_uri_to_worker_ext(conf->uw_map, r->uri,
                                               NULL, &e, NULL, conf->log);
                ap_set_module_config(r->request_config, &jk_module, e);
            }

            if (worker) {
                r->handler = apr_pstrdup(r->pool, JK_HANDLER);
                apr_table_setn(r->notes, JK_NOTE_WORKER_NAME, worker);

                /* This could be a sub-request, possibly from mod_dir */
                if (r->main)
                    apr_table_setn(r->main->notes, JK_NOTE_WORKER_NAME, worker);

            }
            else {
                if (JK_IS_DEBUG_LEVEL(conf->log))
                    jk_log(conf->log, JK_LOG_DEBUG,
                           "no match for %s found",
                           r->uri);
                if (conf->strip_session == JK_TRUE &&
                    conf->strip_session_name) {
                    char *jsessionid;
                    if (r->uri) {
                        jsessionid = strstr(r->uri, conf->strip_session_name);
                        if (jsessionid) {
                            if (JK_IS_DEBUG_LEVEL(conf->log))
                                jk_log(conf->log, JK_LOG_DEBUG,
                                       "removing session identifier [%s] for non servlet url [%s]",
                                       jsessionid, r->uri);
                            *jsessionid = '\0';
                        }
                    }
                    if (r->filename) {
                        jsessionid = strstr(r->filename, conf->strip_session_name);
                        if (jsessionid)
                            *jsessionid = '\0';
                    }
                    return DECLINED;
                }
            }
        }
    }

    if (apr_table_get(r->notes, JK_NOTE_WORKER_NAME)) {

        /* First find just the name of the file, no directory */
        r->filename = (char *)apr_filepath_name_get(r->uri);

        /* Only if sub-request for a directory, most likely from mod_dir */
        if (r->main && r->main->filename &&
            (!apr_filepath_name_get(r->main->filename) ||
             !strlen(apr_filepath_name_get(r->main->filename)))) {

            /* The filename from the main request will be set to what should
             * be picked up, aliases included. Tomcat will need to know about
             * those aliases or things won't work for them. Normal files should
             * be fine. */

            /* Need absolute path to stat */
            if (apr_filepath_merge(&r->filename,
                                   r->main->filename, r->filename,
                                   APR_FILEPATH_SECUREROOT |
                                   APR_FILEPATH_TRUENAME, r->pool)
                != APR_SUCCESS) {
                return DECLINED;        /* We should never get here, very bad */
            }

            /* Stat the file so that mod_dir knows it's there */
            apr_stat(&r->finfo, r->filename, APR_FINFO_TYPE, r->pool);
        }

        return OK;
    }
    return DECLINED;
}

static void jk_register_hooks(apr_pool_t * p)
{
    ap_hook_handler(jk_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_config(jk_post_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_child_init(jk_child_init, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_translate_name(jk_translate, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_map_to_storage(jk_map_to_storage, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA jk_module = {
    STANDARD20_MODULE_STUFF,
    NULL,                       /* dir config creater */
    NULL,                       /* dir merger --- default is to override */
    create_jk_config,           /* server config */
    merge_jk_config,            /* merge server config */
    jk_cmds,                    /* command ap_table_t */
    jk_register_hooks           /* register hooks */
};
