From 9401f816dd0d9d550fe98a8507224bde51c4b847 Mon Sep 17 00:00:00 2001 From: hongbotian Date: Mon, 30 Nov 2015 02:41:33 -0500 Subject: upload tomcat JIRA: BOTTLENECK-7 Change-Id: I875d474869efd76ca203c30b60ebc0c3ee606d0e Signed-off-by: hongbotian --- .../native/iis/jk_isapi_plugin.c | 3495 ++++++++++++++++++++ 1 file changed, 3495 insertions(+) create mode 100644 rubbos/app/tomcat-connectors-1.2.32-src/native/iis/jk_isapi_plugin.c (limited to 'rubbos/app/tomcat-connectors-1.2.32-src/native/iis/jk_isapi_plugin.c') diff --git a/rubbos/app/tomcat-connectors-1.2.32-src/native/iis/jk_isapi_plugin.c b/rubbos/app/tomcat-connectors-1.2.32-src/native/iis/jk_isapi_plugin.c new file mode 100644 index 00000000..7c9f67be --- /dev/null +++ b/rubbos/app/tomcat-connectors-1.2.32-src/native/iis/jk_isapi_plugin.c @@ -0,0 +1,3495 @@ +/* + * 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: ISAPI plugin for IIS/PWS * + * Author: Gal Shachor * + * Author: Larry Isaacs * + * Author: Ignacio J. Ortega * + * Author: Mladen Turk * + * Version: $Revision: 1129429 $ * + ***************************************************************************/ + +// This define is needed to include wincrypt,h, needed to get client certificates +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif +#include +#include +#include +#include + +#include "jk_global.h" +#include "jk_url.h" +#include "jk_util.h" +#include "jk_map.h" +#include "jk_pool.h" +#include "jk_service.h" +#include "jk_worker.h" +#include "jk_uri_worker_map.h" +#include "jk_shm.h" +#include "jk_ajp13.h" +#include "pcre.h" + +#ifndef POSIX_MALLOC_THRESHOLD +#define POSIX_MALLOC_THRESHOLD (10) +#endif + +#include + +#define VERSION_STRING "Jakarta/ISAPI/" JK_EXPOSED_VERSION +#define FULL_VERSION_STRING "Jakarta/ISAPI/" JK_FULL_EXPOSED_VERSION +#define SHM_DEF_NAME "JKISAPISHMEM" +#define DEFAULT_WORKER_NAME ("ajp13") + +/* + * This is default value found inside httpd.conf + * for MaxClients + */ +#define DEFAULT_WORKER_THREADS 250 + +/* + * We use special headers to pass values from the filter to the + * extension. These values are: + * + * 1. The real URI before redirection took place + * 2. The name of the worker to be used. + * 3. The contents of the Translate header, if any + * + */ +#define URI_HEADER_NAME_BASE ("TOMCATURI") +#define QUERY_HEADER_NAME_BASE ("TOMCATQUERY") +#define WORKER_HEADER_NAME_BASE ("TOMCATWORKER") +#define WORKER_HEADER_INDEX_BASE ("TOMCATWORKERIDX") +#define TOMCAT_TRANSLATE_HEADER_NAME_BASE ("TOMCATTRANSLATE") +#ifndef USE_CGI_HEADERS +#define CONTENT_LENGTH ("CONTENT-LENGTH:") +#else +#define CONTENT_LENGTH ("CONTENT_LENGTH:") +#endif + +/* The HTTP_ form of the header for use in ExtensionProc */ +#define HTTP_HEADER_PREFIX "HTTP_" +#ifdef USE_CGI_HEADERS +#define HTTP_HEADER_PREFIX_LEN 5 +#endif + +/* The template used to construct our unique headers + * from the base name and module instance + */ +#define HEADER_TEMPLATE "%s%p:" +#define HTTP_HEADER_TEMPLATE HTTP_HEADER_PREFIX "%s%p" + +static char URI_HEADER_NAME[MAX_PATH]; +static char QUERY_HEADER_NAME[MAX_PATH]; +static char WORKER_HEADER_NAME[MAX_PATH]; +static char TOMCAT_TRANSLATE_HEADER_NAME[MAX_PATH]; +static char WORKER_HEADER_INDEX[MAX_PATH]; + +/* The variants of the special headers after IIS adds + * "HTTP_" to the front of them + */ +static char HTTP_URI_HEADER_NAME[MAX_PATH]; +static char HTTP_QUERY_HEADER_NAME[MAX_PATH]; +static char HTTP_WORKER_HEADER_NAME[MAX_PATH]; +static char HTTP_WORKER_HEADER_INDEX[MAX_PATH]; + +#define REGISTRY_LOCATION ("Software\\Apache Software Foundation\\Jakarta Isapi Redirector\\1.0") +#define W3SVC_REGISTRY_KEY ("SYSTEM\\CurrentControlSet\\Services\\W3SVC\\Parameters") +#define EXTENSION_URI_TAG ("extension_uri") + +#define URI_SELECT_TAG ("uri_select") +#define URI_SELECT_PARSED_VERB ("parsed") +#define URI_SELECT_UNPARSED_VERB ("unparsed") +#define URI_SELECT_ESCAPED_VERB ("escaped") +#define URI_SELECT_PROXY_VERB ("proxy") +#define URI_REWRITE_TAG ("rewrite_rule_file") +#define SHM_SIZE_TAG ("shm_size") +#define WORKER_MOUNT_RELOAD_TAG ("worker_mount_reload") +#define STRIP_SESSION_TAG ("strip_session") +#ifndef AUTOMATIC_AUTH_NOTIFICATION +#define AUTH_COMPLETE_TAG ("auth_complete") +#endif +#define REJECT_UNSAFE_TAG ("reject_unsafe") +#define WATCHDOG_INTERVAL_TAG ("watchdog_interval") +#define ENABLE_CHUNKED_ENCODING_TAG ("enable_chunked_encoding") +#define ERROR_PAGE_TAG ("error_page") + +#define LOG_ROTATION_TIME_TAG ("log_rotationtime") +#define LOG_FILESIZE_TAG ("log_filesize") + +/* HTTP standard headers */ +#define TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE ("Transfer-Encoding: chunked") +#define TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE_LEN (26) +#define TRANSFER_ENCODING_HEADER_NAME ("Transfer-Encoding") +#define TRANSFER_ENCODING_HEADER_NAME_LEN (17) +#define TRANSFER_ENCODING_IDENTITY_VALUE ("identity") +#define TRANSFER_ENCODING_CHUNKED_VALUE ("chunked") +#define TRANSFER_ENCODING_CHUNKED_VALUE_LEN (7) + +#define CONTENT_LENGTH_HEADER_NAME ("Content-Length") +#define CONTENT_LENGTH_HEADER_NAME_LEN (14) + +#define CONNECTION_HEADER_NAME ("Connection") +#define CONNECTION_CLOSE_VALUE ("Close") + +#define TRANSLATE_HEADER ("Translate:") +#define TRANSLATE_HEADER_NAME ("Translate") +#define TRANSLATE_HEADER_NAME_LC ("translate") + +/* HTTP protocol CRLF */ +#define CRLF ("\r\n") +#define CRLF_LEN (2) + +/* Transfer-Encoding: chunked content trailer */ +#define CHUNKED_ENCODING_TRAILER ("0\r\n\r\n") +#define CHUNKED_ENCODING_TRAILER_LEN (5) + +/* Hex of chunk length (one char per byte) + CRLF + terminator. */ +#define CHUNK_HEADER_BUFFER_SIZE (sizeof(unsigned int)*2+CRLF_LEN+1) + +#define BAD_REQUEST -1 +#define BAD_PATH -2 +#define MAX_SERVERNAME 128 +#define MAX_INSTANCEID 32 +#define MAX_PACKET_SIZE 65536 + +char HTML_ERROR_HEAD[] = "\n" + "\n" + "\n\n" + "\n" + "\n"; + +#define HTML_ERROR_BODY_FMT "%s!\n\n\n

%s!

\n

\n%s\n

\n" + +char HTML_ERROR_TAIL[] = "

\n
 
 
 
 \n" + FULL_VERSION_STRING "\n" + "
 \n" + "


\n" + "

\n" + "Copyright © 1999-2011 Apache Software Foundation
\n" + "All Rights Reserved\n" + "

\n\n\n"; + +static struct error_reasons { + int status; + const char *reason; + const char *title; + const char *description; +} error_reasons[] = { + { 100, + "Continue", + NULL, + NULL + }, + { 101, + "Switching Protocols", + NULL, + NULL + }, + { 200, + "OK", + NULL, + NULL + }, + { 201, + "Created", + NULL, + NULL + }, + { 202, + "Accepted", + NULL, + NULL + }, + { 203, + "Non-Authoritative Information", + NULL, + NULL + }, + { 204, + "No Content", + NULL, + NULL + }, + { 205, + "Reset Content", + NULL, + NULL + }, + { 206, + "Partial Content", + NULL, + NULL + }, + { 300, + "Multiple Choices", + NULL, + NULL + }, + { 301, + "Moved Permanently", + NULL, + NULL + }, + { 302, + "Moved Temporarily", + NULL, + NULL + }, + { 303, + "See Other", + NULL, + NULL + }, + { 304, + "Not Modified", + NULL, + NULL + }, + { 305, + "Use Proxy", + NULL, + NULL + }, + { 400, + "Bad Request", + "Bad Request", + "Your browser (or proxy) sent a request that " + "this server could not understand." + }, + { 401, + "Unauthorized", + "Access is denied due to invalid credentials", + "You do not have permission to view this directory or " + "page using the credentials that you supplied." + }, + { 402, + "Payment Required", + NULL, + NULL + }, + { 403, + "Forbidden", + "Access is denied", + "You do not have permission to view this directory or page " + "using the credentials that you supplied." + }, + { 404, + "Not Found", + "The requested URL was not found on this server", + "If you entered the URL manually please check your" + "spelling and try again." + }, + { 405, + "Method Not Allowed", + "HTTP method used to access this page is not allowed", + "The page you are looking for cannot be displayed because an " + "invalid method (HTTP method) was used to attempt access." + }, + { 406, + "Not Acceptable", + "Client browser does not accept the MIME type of the requested page", + "The page you are looking for cannot be opened by your browser " + "because it has a file name extension that your browser " + "does not accept." + }, + { 407, + "Proxy Authentication Required", + NULL, + "The client must first authenticate itself with the proxy" + }, + { 408, + "Request Timeout", + NULL, + "The client did not produce a request within the time " + "that the server was prepared to wait." + }, + { 409, + "Conflict", + NULL, + "The request could not be completed due to a conflict with " + "the current state of the resource." + }, + { 410, + "Gone", + NULL, + "The requested resource is no longer available at the " + "server and no forwarding address is known." + }, + { 411, + "Length Required", + NULL, + "The server refuses to accept the request without a " + "defined Content-Length." + }, + { 412, + "Precondition Failed", + NULL, + "The precondition given in one or more of the request " + "header fields evaluated to false when it was tested on the server." + }, + { 413, + "Request Entity Too Large", + NULL, + "The HTTP method does not allow the data transmitted, " + "or the data volume exceeds the capacity limit." + }, + { 414, + "Request-URI Too Long", + "Submitted URI too large", + "The length of the requested URL exceeds the capacity limit " + "for this server. The request cannot be processed." + }, + { 415, + "Unsupported Media Type", + NULL, + "The server is refusing to service the request because the " + "entity of the request is in a format not supported by the " + "requested resource for the requested method." + }, + { 500, + "Internal Server Error", + NULL, + "The server encountered an internal error and was " + "unable to complete your request." + }, + { 501, + "Not Implemented", + NULL, + "The server does not support the functionality required " + "to fulfill the request." + }, + { 502, + "Bad Gateway", + NULL, + "There is a problem with the page you are looking for, " + "and it cannot be displayed. When the Web server (while " + "acting as a gateway or proxy) contacted the upstream content " + "server, it received an invalid response from the content server." + }, + { 503, + "Service Unavailable", + "Service Temporary Unavailable", + "The server is temporarily unable to service your " + "request due to maintenance downtime or capacity problems. " + "Please try again later." + }, + { 504, + "Gateway Timeout", + NULL, + "The server, while acting as a gateway or proxy, " + "did not receive a timely response from the upstream server" + }, + { 505, + "HTTP Version Not Supported", + NULL, + "The server does not support, or refuses to support, the " + "HTTP protocol version that was used in the request message." + }, + { 0, + NULL, + NULL, + NULL + } +}; + + + +#define STRNULL_FOR_NULL(x) ((x) ? (x) : "(null)") +#ifdef USE_CGI_HEADERS +#define JK_TOLOWER(x) ((char)tolower((BYTE)(x))) +#endif + +#define GET_SERVER_VARIABLE_VALUE(name, place) \ + do { \ + (place) = NULL; \ + huge_buf_sz = MAX_PACKET_SIZE; \ + if (get_server_value(private_data->lpEcb, \ + (name), \ + huge_buf, \ + huge_buf_sz)) { \ + (place) = jk_pool_strdup(&private_data->p, \ + huge_buf); \ + } } while(0) + +#define GET_SERVER_VARIABLE_VALUE_INT(name, place, def) \ + do { \ + huge_buf_sz = MAX_PACKET_SIZE; \ + if (get_server_value(private_data->lpEcb, \ + (name), \ + huge_buf, \ + huge_buf_sz)) { \ + (place) = atoi(huge_buf); \ + if (((place) == 0) && (errno == EINVAL || \ + errno == ERANGE)) { \ + (place) = def; \ + } \ + } else { \ + (place) = def; \ + } } while(0) + +static char dll_file_path[MAX_PATH]; +static char ini_file_name[MAX_PATH]; +static int using_ini_file = JK_FALSE; +static JK_CRIT_SEC init_cs; +static int is_inited = JK_FALSE; +static int is_mapread = JK_FALSE; + +static jk_uri_worker_map_t *uw_map = NULL; +static jk_map_t *workers_map = NULL; +static jk_map_t *rewrite_map = NULL; +static jk_map_t *rregexp_map = NULL; +static jk_map_t *jk_environment_map = NULL; + +static jk_logger_t *logger = NULL; +static JK_CRIT_SEC log_cs; +static char *SERVER_NAME = "SERVER_NAME"; +static char *SERVER_SOFTWARE = "SERVER_SOFTWARE"; +static char *INSTANCE_ID = "INSTANCE_ID"; +static char *CONTENT_TYPE = "Content-Type:text/html\r\n\r\n"; + +static char extension_uri[INTERNET_MAX_URL_LENGTH] = + "/jakarta/isapi_redirect.dll"; +static char log_file[MAX_PATH * 2]; +static char log_file_effective[MAX_PATH * 2]; +static int log_level = JK_LOG_DEF_LEVEL; +static long log_rotationtime = 0; +static time_t log_next_rotate_time = 0; +static ULONGLONG log_filesize = 0; + +static char worker_file[MAX_PATH * 2]; +static char worker_mount_file[MAX_PATH * 2] = {0}; +static int worker_mount_reload = JK_URIMAP_DEF_RELOAD; +static char rewrite_rule_file[MAX_PATH * 2] = {0}; +static size_t shm_config_size = 0; +static int strip_session = 0; +#ifndef AUTOMATIC_AUTH_NOTIFICATION +static int use_auth_notification_flags = 1; +#endif +static int chunked_encoding_enabled = JK_FALSE; +static int reject_unsafe = 0; +static int watchdog_interval = 0; +static HANDLE watchdog_handle = NULL; +static char error_page_buf[INTERNET_MAX_URL_LENGTH] = {0}; +static char *error_page = NULL; + +#define URI_SELECT_OPT_PARSED 0 +#define URI_SELECT_OPT_UNPARSED 1 +#define URI_SELECT_OPT_ESCAPED 2 +#define URI_SELECT_OPT_PROXY 3 + +static int uri_select_option = URI_SELECT_OPT_PROXY; + +static jk_worker_env_t worker_env; + +typedef struct isapi_private_data_t isapi_private_data_t; +struct isapi_private_data_t +{ + jk_pool_t p; + + unsigned int bytes_read_so_far; + int chunk_content; /* Whether we're responding with Transfer-Encoding: chunked content */ + LPEXTENSION_CONTROL_BLOCK lpEcb; +}; + +typedef struct isapi_log_data_t isapi_log_data_t; +struct isapi_log_data_t { + char uri[INTERNET_MAX_URL_LENGTH]; + char query[INTERNET_MAX_URL_LENGTH]; + int request_matched; /* Whether this request (within a multi-request connection) + was handled and needs the log values adjusted */ +}; + +typedef struct iis_info_t iis_info_t; +struct iis_info_t { + int major; /* The major version */ + int minor; /* The minor version */ + DWORD filter_notify_event; /* The primary filter SF_NOTIFY_* event */ +}; + +static iis_info_t iis_info; + +static int JK_METHOD start_response(jk_ws_service_t *s, + int status, + const char *reason, + const char *const *header_names, + const char *const *header_values, + unsigned int num_of_headers); + +static int JK_METHOD iis_read(jk_ws_service_t *s, + void *b, unsigned int l, unsigned int *a); + +static int JK_METHOD iis_write(jk_ws_service_t *s, const void *b, unsigned int l); + +static int JK_METHOD iis_done(jk_ws_service_t *s); + +static int init_ws_service(isapi_private_data_t * private_data, + jk_ws_service_t *s, char **worker_name); + +static int init_jk(char *serverName); + + +static int JK_METHOD iis_log_to_file(jk_logger_t *l, int level, + int used, char *what); + +static BOOL initialize_extension(void); + +static int read_registry_init_data(void); + +static int get_config_parameter(LPVOID src, const char *tag, + char *val, DWORD sz); + +static int get_config_bool(LPVOID src, const char *tag, int def); + +static int get_config_int(LPVOID src, const char *tag, int def); + +static int get_registry_config_parameter(HKEY hkey, + const char *tag, char *b, DWORD sz); + +static int get_registry_config_number(HKEY hkey, const char *tag, + int *val); + + +static int get_server_value(LPEXTENSION_CONTROL_BLOCK lpEcb, + char *name, + char *buf, DWORD bufsz); + +static int base64_encode_cert_len(int len); + +static int base64_encode_cert(char *encoded, + const char *string, int len); + +static int get_iis_info(iis_info_t *info); + +static int isapi_write_client(isapi_private_data_t *p, const char *buf, unsigned int write_length); + +static char x2c(const char *what) +{ + register char digit; + + digit = + ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0')); + digit *= 16; + digit += + (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0')); + return (digit); +} + +static int unescape_url(char *url) +{ + register int x, y, badesc, badpath; + + badesc = 0; + badpath = 0; + for (x = 0, y = 0; url[y]; ++x, ++y) { + if (url[y] != '%') + url[x] = url[y]; + else { + if (!isxdigit(url[y + 1]) || !isxdigit(url[y + 2])) { + badesc = 1; + url[x] = '%'; + } + else { + url[x] = x2c(&url[y + 1]); + y += 2; + if (url[x] == '/' || url[x] == '\0') + badpath = 1; + } + } + } + url[x] = '\0'; + if (badesc) + return BAD_REQUEST; + else if (badpath) + return BAD_PATH; + else + return 0; +} + +static void getparents(char *name) +{ + int l, w; + + /* Four paseses, as per RFC 1808 */ + /* a) remove ./ path segments */ + + for (l = 0, w = 0; name[l] != '\0';) { + if (name[l] == '.' && name[l + 1] == '/' + && (l == 0 || name[l - 1] == '/')) + l += 2; + else + name[w++] = name[l++]; + } + + /* b) remove trailing . path, segment */ + if (w == 1 && name[0] == '.') + w--; + else if (w > 1 && name[w - 1] == '.' && name[w - 2] == '/') + w--; + name[w] = '\0'; + + /* c) remove all xx/../ segments. (including leading ../ and /../) */ + l = 0; + + while (name[l] != '\0') { + if (name[l] == '.' && name[l + 1] == '.' && name[l + 2] == '/' && + (l == 0 || name[l - 1] == '/')) { + register int m = l + 3, n; + + l = l - 2; + if (l >= 0) { + while (l >= 0 && name[l] != '/') + l--; + l++; + } + else + l = 0; + n = l; + while ((name[n] = name[m]) != '\0') { + n++; + m++; + } + } + else + ++l; + } + + /* d) remove trailing xx/.. segment. */ + if (l == 2 && name[0] == '.' && name[1] == '.') + name[0] = '\0'; + else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.' + && name[l - 3] == '/') { + l = l - 4; + if (l >= 0) { + while (l >= 0 && name[l] != '/') + l--; + l++; + } + else + l = 0; + name[l] = '\0'; + } +} + +/* Apache code to escape a URL */ + +#define T_OS_ESCAPE_PATH (4) + +static const BYTE test_char_table[256] = { + 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 0, 7, 6, 1, 6, 1, 1, 9, 9, 1, 0, 8, 0, 0, 10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 15, 15, 8, 15, 15, + 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 7, 0, + 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 7, 15, 1, 14, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 +}; + +#define TEST_CHAR(c, f) (test_char_table[(unsigned int)(c)] & (f)) + +static const char c2x_table[] = "0123456789abcdef"; + +static BYTE *c2x(unsigned int what, BYTE *where) +{ + *where++ = '%'; + *where++ = c2x_table[what >> 4]; + *where++ = c2x_table[what & 0xf]; + return where; +} + +static const char *status_reason(int status) +{ + struct error_reasons *r; + + r = error_reasons; + while (r->status <= status) { + if (r->status == status) + return r->reason; + else + r++; + } + return "No Reason"; +} + +static const char *status_title(int status) +{ + struct error_reasons *r; + + r = error_reasons; + while (r->status <= status) { + if (r->status == status) { + if (r->title) + return r->title; + else + return r->reason; + } + else + r++; + } + return "Unknown Error"; +} + +static const char *status_description(int status) +{ + struct error_reasons *r; + + r = error_reasons; + while (r->status <= status) { + if (r->status == status) { + if (r->description) + return r->description; + else + return r->reason; + } + else + r++; + } + return "Unknown Error"; +} + +static int escape_url(const char *path, char *dest, int destsize) +{ + const BYTE *s = (const BYTE *)path; + BYTE *d = (BYTE *)dest; + BYTE *e = d + destsize - 1; + BYTE *ee = d + destsize - 3; + + while (*s) { + if (TEST_CHAR(*s, T_OS_ESCAPE_PATH)) { + if (d >= ee) + return JK_FALSE; + d = c2x(*s, d); + } + else { + if (d >= e) + return JK_FALSE; + *d++ = *s; + } + ++s; + } + *d = '\0'; + return JK_TRUE; +} + +/* + * Find the first occurrence of find in s. + */ +static char *stristr(const char *s, const char *find) +{ + char c, sc; + size_t len; + + if ((c = tolower((unsigned char)(*find++))) != 0) { + len = strlen(find); + do { + do { + if ((sc = tolower((unsigned char)(*s++))) == 0) + return (NULL); + } while (sc != c); + } while (strnicmp(s, find, len) != 0); + s--; + } + return ((char *)s); +} + +static int uri_is_web_inf(const char *uri) +{ + if (stristr(uri, "/web-inf")) { + return JK_TRUE; + } + if (stristr(uri, "/meta-inf")) { + return JK_TRUE; + } + + return JK_FALSE; +} + +static void write_error_response(PHTTP_FILTER_CONTEXT pfc, int err) +{ + char status[MAX_PATH]; + char body[8192] = ""; + DWORD len; + + /* reject !!! */ + pfc->AddResponseHeaders(pfc, CONTENT_TYPE, 0); + StringCbPrintf(status, MAX_PATH, "%d %s", err, status_reason(err)); + pfc->ServerSupportFunction(pfc, + SF_REQ_SEND_RESPONSE_HEADER, + status, 0, 0); + len = (DWORD)(sizeof(HTML_ERROR_HEAD) - 1); + pfc->WriteClient(pfc, HTML_ERROR_HEAD, &len, + HSE_IO_SYNC); + StringCbPrintf(body, sizeof(body), HTML_ERROR_BODY_FMT, + status_reason(err), status_title(err), + status_description(err)); + len = (DWORD)(strlen(body)); + pfc->WriteClient(pfc, body, &len, + HSE_IO_SYNC); + len = (DWORD)(sizeof(HTML_ERROR_TAIL) - 1); + pfc->WriteClient(pfc, HTML_ERROR_TAIL, &len, + HSE_IO_SYNC); +} + +static void write_error_message(LPEXTENSION_CONTROL_BLOCK lpEcb, int err) +{ + DWORD len; + char status[MAX_PATH]; + char body[8192] = ""; + + if (error_page) { + char error_page_url[INTERNET_MAX_URL_LENGTH] = ""; + int len_of_error_page; + StringCbPrintf(error_page_url, INTERNET_MAX_URL_LENGTH, + (LPCSTR)error_page, err); + len_of_error_page = (int)strlen(error_page_url); + if (!lpEcb->ServerSupportFunction(lpEcb->ConnID, + HSE_REQ_SEND_URL_REDIRECT_RESP, + error_page_url, + (LPDWORD)&len_of_error_page, + (LPDWORD)NULL)) { + lpEcb->dwHttpStatusCode = err; + } + else { + return; + } + } + lpEcb->dwHttpStatusCode = err; + + StringCbPrintf(status, MAX_PATH, "%d %s", err, status_reason(err)); + lpEcb->ServerSupportFunction(lpEcb->ConnID, + HSE_REQ_SEND_RESPONSE_HEADER, + status, + 0, + (LPDWORD)CONTENT_TYPE); + /* First write the HEAD */ + len = (DWORD)(sizeof(HTML_ERROR_HEAD) - 1); + lpEcb->WriteClient(lpEcb->ConnID, + HTML_ERROR_HEAD, &len, + HSE_IO_SYNC); + StringCbPrintf(body, sizeof(body), HTML_ERROR_BODY_FMT, + status_reason(err), status_title(err), + status_description(err)); + len = (DWORD)(strlen(body)); + lpEcb->WriteClient(lpEcb->ConnID, + body, &len, + HSE_IO_SYNC); + len = (DWORD)(sizeof(HTML_ERROR_TAIL) - 1); + lpEcb->WriteClient(lpEcb->ConnID, + HTML_ERROR_TAIL, &len, + HSE_IO_SYNC); + +} + + +static int JK_METHOD start_response(jk_ws_service_t *s, + int status, + const char *reason, + const char *const *header_names, + const char *const *header_values, + unsigned int num_of_headers) +{ + JK_TRACE_ENTER(logger); + if (status < 100 || status > 1000) { + jk_log(logger, JK_LOG_ERROR, + "invalid status %d", + status); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + if (s && s->ws_private) { + int rv = JK_TRUE; + isapi_private_data_t *p = s->ws_private; + + /* If we use proxy error pages, still pass + * through context headers needed for special status codes. + */ + if (s->extension.use_server_error_pages && + status >= s->extension.use_server_error_pages) { + if (status == JK_HTTP_UNAUTHORIZED) { + int found = JK_FALSE; + unsigned int h; + for (h = 0; h < num_of_headers; h++) { + if (!strcasecmp(header_names[h], "WWW-Authenticate")) { + /* + * TODO: we need to save a copy of header_values[h] + * for later reuse in write_error_message() + * which is called later on form HttpExtensionProc + * because of use_server_error_pages. + */ + found = JK_TRUE; + } + } + if (found == JK_FALSE) { + jk_log(logger, JK_LOG_INFO, + "origin server sent 401 without" + " WWW-Authenticate header"); + } + } + return JK_TRUE; + } + + if (!s->response_started) { + char *status_str = NULL; + char *headers_str = NULL; + BOOL keep_alive = FALSE; /* Whether the downstream or us can supply content length */ + BOOL rc; + size_t i, len_of_headers = 0; + + s->response_started = JK_TRUE; + + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Starting response for URI '%s' (protocol %s)", + s->req_uri, s->protocol); + } + + /* + * Create the status line + */ + if (!reason) { + reason = status_reason(status); + } + status_str = (char *)malloc((6 + strlen(reason))); + StringCbPrintf(status_str, 6 + strlen(reason), "%d %s", status, reason); + + if (chunked_encoding_enabled) { + /* Check if we've got an HTTP/1.1 response */ + if (!strcasecmp(s->protocol, "HTTP/1.1")) { + keep_alive = TRUE; + /* Chunking only when HTTP/1.1 client and enabled */ + p->chunk_content = JK_TRUE; + } + } + + /* + * Create response headers string + */ + + /* Calculate length of headers block */ + for (i = 0; i < num_of_headers; i++) { + len_of_headers += strlen(header_names[i]); + len_of_headers += strlen(header_values[i]); + len_of_headers += 4; /* extra for colon, space and crlf */ + } + + /* + * Exclude status codes that MUST NOT include message bodies + */ + if ((status == 204) || (status == 205) || (status == 304)) { + p->chunk_content = JK_FALSE; + /* Keep alive is still possible */ + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Response status %d implies no message body", status ); + } + if (p->chunk_content) { + for (i = 0; i < num_of_headers; i++) { + /* Check the downstream response to see whether + * it's appropriate to chunk the response content + * and whether it supports keeping the connection open. + + * This implements the rules for HTTP/1.1 message length determination + * with the exception of multipart/byteranges media types. + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 + */ + if (!strcasecmp(CONTENT_LENGTH_HEADER_NAME, header_names[i])) { + p->chunk_content = JK_FALSE; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Response specifies Content-Length" ); + } + else if (!strcasecmp(CONNECTION_HEADER_NAME, header_names[i]) + && !strcasecmp(CONNECTION_CLOSE_VALUE, header_values[i])) { + keep_alive = FALSE; + p->chunk_content = JK_FALSE; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Response specifies Connection: Close" ); + } + else if (!strcasecmp(TRANSFER_ENCODING_HEADER_NAME, header_names[i]) + && !strcasecmp(TRANSFER_ENCODING_IDENTITY_VALUE, header_values[i])) { + /* HTTP states that this must include 'chunked' as the last value. + * 'identity' is the same as absence of the header */ + p->chunk_content = JK_FALSE; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Response specifies Transfer-Encoding" ); + } + } + + /* Provide room in the buffer for the Transfer-Encoding header if we use it. */ + len_of_headers += TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE_LEN + 2; + } + + /* Allocate and init the headers string */ + len_of_headers += 3; /* crlf and terminating null char */ + headers_str = (char *)malloc(len_of_headers); + headers_str[0] = '\0'; + + /* Copy headers into headers block for sending */ + for (i = 0; i < num_of_headers; i++) { + StringCbCat(headers_str, len_of_headers, header_names[i]); + StringCbCat(headers_str, len_of_headers, ": "); + StringCbCat(headers_str, len_of_headers, header_values[i]); + StringCbCat(headers_str, len_of_headers, CRLF); + } + + if (p->chunk_content) { + /* Configure the response if chunked encoding is used */ + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Using Transfer-Encoding: chunked"); + + /** We will supply the transfer-encoding to allow IIS to keep the connection open */ + keep_alive = TRUE; + + /* Indicate to the client that the content will be chunked + - We've already reserved space for this */ + StringCbCat(headers_str, len_of_headers, TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE); + StringCbCat(headers_str, len_of_headers, CRLF); + } + + /* Terminate the headers */ + StringCbCat(headers_str, len_of_headers, CRLF); + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "%ssing Keep-Alive", (keep_alive ? "U" : "Not u")); + + if (keep_alive) { + HSE_SEND_HEADER_EX_INFO hi; + + /* Fill in the response */ + hi.pszStatus = status_str; + hi.pszHeader = headers_str; + hi.cchStatus = (DWORD)strlen(status_str); + hi.cchHeader = (DWORD)strlen(headers_str); + + /* + * Using the extended form of the API means we have to get this right, + * i.e. IIS won't keep connections open if there's a Content-Length and close them if there isn't. + */ + hi.fKeepConn = keep_alive; + + /* Send the response to the client */ + rc = p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_SEND_RESPONSE_HEADER_EX, + &hi, + NULL, NULL); + } + else { + DWORD status_str_len = (DWORD)strlen(status_str); + /* Old style response - forces Connection: close if Tomcat response doesn't + specify necessary details to allow keep alive */ + rc = p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_SEND_RESPONSE_HEADER, + status_str, + &status_str_len, + (LPDWORD)headers_str); + } + + if (!rc) { + jk_log(logger, JK_LOG_ERROR, + "HSE_REQ_SEND_RESPONSE_HEADER%s failed with error=%d (0x%08x)", + (keep_alive ? "_EX" : ""), GetLastError(), GetLastError()); + rv = JK_FALSE; + } + if (headers_str) + free(headers_str); + if (status_str) + free(status_str); + } + JK_TRACE_EXIT(logger); + return rv; + } + + JK_LOG_NULL_PARAMS(logger); + JK_TRACE_EXIT(logger); + return JK_FALSE; +} + +static int JK_METHOD iis_read(jk_ws_service_t *s, + void *b, unsigned int l, unsigned int *a) +{ + JK_TRACE_ENTER(logger); + + if (s && s->ws_private && b && a) { + isapi_private_data_t *p = s->ws_private; + + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Preparing to read %d bytes. " + "ECB reports %d bytes total, with %d available.", + l, p->lpEcb->cbTotalBytes, p->lpEcb->cbAvailable); + } + + *a = 0; + if (l) { + char *buf = b; + DWORD already_read = p->lpEcb->cbAvailable - p->bytes_read_so_far; + + if (already_read >= l) { + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Already read %d bytes - supplying %d bytes from buffer", + already_read, l); + } + memcpy(buf, p->lpEcb->lpbData + p->bytes_read_so_far, l); + p->bytes_read_so_far += l; + *a = l; + } + else { + /* + * Try to copy what we already have + */ + if (already_read > 0) { + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Supplying %d bytes from buffer", + already_read); + } + memcpy(buf, p->lpEcb->lpbData + p->bytes_read_so_far, + already_read); + buf += already_read; + l -= already_read; + p->bytes_read_so_far = p->lpEcb->cbAvailable; + + *a = already_read; + } + + /* + * Now try to read from the client ... + */ + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Attempting to read %d bytes from client", l); + } + if (p->lpEcb->ReadClient(p->lpEcb->ConnID, buf, (LPDWORD)&l)) { + /* ReadClient will succeed with dwSize == 0 for last chunk + if request chunk encoded */ + *a += l; + } + else { + jk_log(logger, JK_LOG_ERROR, + "ReadClient failed with %d (0x%08x)", GetLastError(), GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + } + JK_TRACE_EXIT(logger); + return JK_TRUE; + } + + JK_LOG_NULL_PARAMS(logger); + JK_TRACE_EXIT(logger); + return JK_FALSE; +} + +/* + * Writes a buffer to the ISAPI response. + */ +static int isapi_write_client(isapi_private_data_t *p, const char *buf, unsigned int write_length) +{ + unsigned int written = 0; + DWORD try_to_write = 0; + + JK_TRACE_ENTER(logger); + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Writing %d bytes of data to client", write_length); + + while (written < write_length) { + try_to_write = write_length - written; + if (!p->lpEcb->WriteClient(p->lpEcb->ConnID, + (LPVOID)(buf + written), &try_to_write, HSE_IO_SYNC)) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient failed with %d (0x%08x)", GetLastError(), GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + written += try_to_write; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Wrote %d bytes of data successfully", try_to_write); + } + JK_TRACE_EXIT(logger); + return JK_TRUE; +} + +/* + * Write content to the response. + * If chunked encoding has been enabled and the client supports it + *(and it's appropriate for the response), then this will write a + * single "Transfer-Encoding: chunked" chunk + */ +static int JK_METHOD iis_write(jk_ws_service_t *s, const void *b, unsigned int l) +{ + JK_TRACE_ENTER(logger); + + if (!l) { + JK_TRACE_EXIT(logger); + return JK_TRUE; + } + + if (s && s->ws_private && b) { + isapi_private_data_t *p = s->ws_private; + const char *buf = (const char *)b; + + if (!p) { + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + if (!s->response_started) { + start_response(s, 200, NULL, NULL, NULL, 0); + } + + if (p->chunk_content == JK_FALSE) { + if (isapi_write_client(p, buf, l) == JK_FALSE) { + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + else { + char chunk_header[CHUNK_HEADER_BUFFER_SIZE]; + + /* Construct chunk header : HEX CRLF*/ + StringCbPrintf(chunk_header, CHUNK_HEADER_BUFFER_SIZE, "%X%s", l, CRLF); + + if (iis_info.major >= 6) { + HSE_RESPONSE_VECTOR response_vector; + HSE_VECTOR_ELEMENT response_elements[3]; + + response_elements[0].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER; + response_elements[0].pvContext = chunk_header; + response_elements[0].cbOffset = 0; + response_elements[0].cbSize = strlen(chunk_header); + + response_elements[1].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER; + response_elements[1].pvContext = (PVOID)buf; + response_elements[1].cbOffset = 0; + response_elements[1].cbSize = l; + + response_elements[2].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER; + response_elements[2].pvContext = CRLF; + response_elements[2].cbOffset = 0; + response_elements[2].cbSize = CRLF_LEN; + + response_vector.dwFlags = HSE_IO_SYNC; + response_vector.pszStatus = NULL; + response_vector.pszHeaders = NULL; + response_vector.nElementCount = 3; + response_vector.lpElementArray = response_elements; + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "Using vector write for chunk encoded %d byte chunk", l); + + if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_VECTOR_SEND, + &response_vector, + (LPDWORD)NULL, + (LPDWORD)NULL)) { + jk_log(logger, JK_LOG_ERROR, + "Vector write of chunk encoded response failed with %d (0x%08x)", + GetLastError(), GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } else { + /* Write chunk header */ + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "Using chunked encoding - writing chunk header for %d byte chunk", l); + + if (!isapi_write_client(p, chunk_header, (unsigned int)strlen(chunk_header))) { + jk_log(logger, JK_LOG_ERROR, "WriteClient for chunk header failed"); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + /* Write chunk body (or simple body block) */ + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Writing %s of size %d", + (p->chunk_content ? "chunk body" : "simple response"), l); + } + if (!isapi_write_client(p, buf, l)) { + jk_log(logger, JK_LOG_ERROR, "WriteClient for response body chunk failed"); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + /* Write chunk trailer */ + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Using chunked encoding - writing chunk trailer"); + } + + if (!isapi_write_client(p, CRLF, CRLF_LEN)) { + jk_log(logger, JK_LOG_ERROR, "WriteClient for chunk trailer failed"); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + } + + JK_TRACE_EXIT(logger); + return JK_TRUE; + + } + + JK_LOG_NULL_PARAMS(logger); + JK_TRACE_EXIT(logger); + return JK_FALSE; +} + +/** + * In the case of a Transfer-Encoding: chunked response, this will write the terminator chunk. + */ +static int JK_METHOD iis_done(jk_ws_service_t *s) +{ + JK_TRACE_ENTER(logger); + + if (s && s->ws_private) { + isapi_private_data_t *p = s->ws_private; + + if (p->chunk_content == JK_FALSE) { + JK_TRACE_EXIT(logger); + return JK_TRUE; + } + + /* Write last chunk + terminator */ + if (iis_info.major >= 6) { + HSE_RESPONSE_VECTOR response_vector; + HSE_VECTOR_ELEMENT response_elements[1]; + + response_elements[0].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER; + response_elements[0].pvContext = CHUNKED_ENCODING_TRAILER; + response_elements[0].cbOffset = 0; + response_elements[0].cbSize = CHUNKED_ENCODING_TRAILER_LEN; + + /* HSE_IO_FINAL_SEND lets IIS process the response to the client before we return */ + response_vector.dwFlags = HSE_IO_SYNC | HSE_IO_FINAL_SEND; + response_vector.pszStatus = NULL; + response_vector.pszHeaders = NULL; + response_vector.nElementCount = 1; + response_vector.lpElementArray = response_elements; + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "Using vector write to terminate chunk encoded response."); + + if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_VECTOR_SEND, + &response_vector, + (LPDWORD)NULL, + (LPDWORD)NULL)) { + jk_log(logger, JK_LOG_ERROR, + "Vector termination of chunk encoded response failed with %d (0x%08x)", + GetLastError(), GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + else { + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Terminating chunk encoded response"); + + if (!isapi_write_client(p, CHUNKED_ENCODING_TRAILER, CHUNKED_ENCODING_TRAILER_LEN)) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for chunked response terminator failed with %d (0x%08x)", + GetLastError(), GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + + JK_TRACE_EXIT(logger); + return JK_TRUE; + } + + JK_LOG_NULL_PARAMS(logger); + JK_TRACE_EXIT(logger); + return JK_FALSE; +} + +BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) +{ + int rc; + BOOL rv = TRUE; + ULONG http_filter_revision = HTTP_FILTER_REVISION; + + pVer->dwFilterVersion = pVer->dwServerFilterVersion; + + if (pVer->dwFilterVersion > http_filter_revision) { + pVer->dwFilterVersion = http_filter_revision; + } + JK_ENTER_CS(&(init_cs), rc); + if (!is_inited) { + rv = initialize_extension(); + } + JK_LEAVE_CS(&(init_cs), rc); + pVer->dwFlags = SF_NOTIFY_ORDER_HIGH | + SF_NOTIFY_SECURE_PORT | + SF_NOTIFY_NONSECURE_PORT | + SF_NOTIFY_LOG | + iis_info.filter_notify_event; + + StringCbCopy(pVer->lpszFilterDesc, SF_MAX_FILTER_DESC_LEN, (VERSION_STRING)); + return rv; +} + + +#define AP_REG_ICASE 0x01 /** use a case-insensitive match */ +#define AP_REG_NEWLINE 0x02 /** don't match newlines against '.' etc */ +#define AP_REG_NOTBOL 0x04 /** ^ will not match against start-of-string */ +#define AP_REG_NOTEOL 0x08 /** $ will not match against end-of-string */ + +#define AP_REG_EXTENDED (0) /** unused */ +#define AP_REG_NOSUB (0) /** unused */ +/** The max number of regex captures that can be expanded by ap_pregsub */ +#define AP_MAX_REG_MATCH 10 + +/* Error values: */ +enum { + AP_REG_ASSERT = 1, /** internal error ? */ + AP_REG_ESPACE, /** failed to get memory */ + AP_REG_INVARG, /** invalid argument */ + AP_REG_NOMATCH /** match failed */ +}; + +/* The structure representing a compiled regular expression. */ +typedef struct { + void *re_pcre; + size_t re_nsub; + size_t re_erroffset; + const char *real; + const char *fake; +} ap_regex_t; + +/* The structure in which a captured offset is returned. */ +typedef struct { + int rm_so; + int rm_eo; +} ap_regmatch_t; + + +/* Table of error strings corresponding to POSIX error codes; must be + * kept in synch with include/ap_regex.h's AP_REG_E* definitions. */ + +static const char *const pstring[] = { + "", /* Dummy for value 0 */ + "internal error", /* AP_REG_ASSERT */ + "failed to get memory", /* AP_REG_ESPACE */ + "bad argument", /* AP_REG_INVARG */ + "match failed" /* AP_REG_NOMATCH */ +}; + +static size_t ap_regerror(int errcode, const ap_regex_t *preg, + char *errbuf, size_t errbuf_size) +{ + const char *message, *addmessage; + size_t length, addlength; + + message = (errcode >= (int)(sizeof(pstring)/sizeof(char *))) ? + "unknown error code" : pstring[errcode]; + length = strlen(message) + 1; + + addmessage = " at offset "; + addlength = (preg != NULL && (int)preg->re_erroffset != -1)? + strlen(addmessage) + 6 : 0; + + if (errbuf_size > 0) { + if (addlength > 0 && errbuf_size >= length + addlength) + StringCbPrintf(errbuf, sizeof(errbuf), "%s%s%-6d", + message, addmessage, + (int)preg->re_erroffset); + else { + strncpy(errbuf, message, errbuf_size - 1); + errbuf[errbuf_size-1] = 0; + } + } + + return length + addlength; +} + +/************************************************* + * Free store held by a regex * + *************************************************/ + +static void ap_regfree(ap_regex_t *preg) +{ + (pcre_free)(preg->re_pcre); +} + + + + +/************************************************* + * Compile a regular expression * + *************************************************/ + +/* +Arguments: + preg points to a structure for recording the compiled expression + pattern the pattern to compile + cflags compilation flags + +Returns: 0 on success + various non-zero codes on failure +*/ + +static int ap_regcomp(ap_regex_t *preg, const char *pattern, int cflags) +{ + const char *errorptr; + int erroffset; + int options = 0; + + if ((cflags & AP_REG_ICASE) != 0) options |= PCRE_CASELESS; + if ((cflags & AP_REG_NEWLINE) != 0) options |= PCRE_MULTILINE; + + preg->re_pcre = pcre_compile(pattern, options, &errorptr, &erroffset, NULL); + preg->re_erroffset = erroffset; + + if (preg->re_pcre == NULL) return AP_REG_INVARG; + + preg->re_nsub = pcre_info((const pcre *)preg->re_pcre, NULL, NULL); + return 0; +} + +/************************************************* + * Match a regular expression * + *************************************************/ + +/* Unfortunately, PCRE requires 3 ints of working space for each captured +substring, so we have to get and release working store instead of just using +the POSIX structures as was done in earlier releases when PCRE needed only 2 +ints. However, if the number of possible capturing brackets is small, use a +block of store on the stack, to reduce the use of malloc/free. The threshold is +in a macro that can be changed at configure time. */ + +static int ap_regexec(const ap_regex_t *preg, const char *string, + int nmatch, ap_regmatch_t pmatch[], + int eflags) +{ + int rc; + int options = 0; + int *ovector = NULL; + int small_ovector[POSIX_MALLOC_THRESHOLD * 3]; + int allocated_ovector = 0; + + if ((eflags & AP_REG_NOTBOL) != 0) options |= PCRE_NOTBOL; + if ((eflags & AP_REG_NOTEOL) != 0) options |= PCRE_NOTEOL; + + ((ap_regex_t *)preg)->re_erroffset = (size_t)(-1); /* Only has meaning after compile */ + + if (nmatch > 0) { + if (nmatch <= POSIX_MALLOC_THRESHOLD) { + ovector = &(small_ovector[0]); + } + else { + ovector = (int *)malloc(sizeof(int) * nmatch * 3); + if (ovector == NULL) + return AP_REG_ESPACE; + allocated_ovector = 1; + } + } + + rc = pcre_exec((const pcre *)preg->re_pcre, NULL, string, + (int)strlen(string), + 0, options, ovector, nmatch * 3); + + if (rc == 0) + rc = nmatch; /* All captured slots were filled in */ + if (rc >= 0) { + int i; + for (i = 0; i < rc; i++) { + pmatch[i].rm_so = ovector[i*2]; + pmatch[i].rm_eo = ovector[i*2+1]; + } + if (allocated_ovector) + free(ovector); + for (; i < nmatch; i++) + pmatch[i].rm_so = pmatch[i].rm_eo = -1; + return 0; + } + else { + if (allocated_ovector) + free(ovector); + switch(rc) { + case PCRE_ERROR_NOMATCH: return AP_REG_NOMATCH; + case PCRE_ERROR_NULL: return AP_REG_INVARG; + case PCRE_ERROR_BADOPTION: return AP_REG_INVARG; + case PCRE_ERROR_BADMAGIC: return AP_REG_INVARG; + case PCRE_ERROR_UNKNOWN_NODE: return AP_REG_ASSERT; + case PCRE_ERROR_NOMEMORY: return AP_REG_ESPACE; +#ifdef PCRE_ERROR_MATCHLIMIT + case PCRE_ERROR_MATCHLIMIT: return AP_REG_ESPACE; +#endif +#ifdef PCRE_ERROR_BADUTF8 + case PCRE_ERROR_BADUTF8: return AP_REG_INVARG; +#endif +#ifdef PCRE_ERROR_BADUTF8_OFFSET + case PCRE_ERROR_BADUTF8_OFFSET: return AP_REG_INVARG; +#endif + default: return AP_REG_ASSERT; + } + } +} + +/* This function substitutes for $0-$9, filling in regular expression + * submatches. Pass it the same nmatch and pmatch arguments that you + * passed ap_regexec(). pmatch should not be greater than the maximum number + * of subexpressions - i.e. one more than the re_nsub member of ap_regex_t. + * + * input should be the string with the $-expressions, source should be the + * string that was matched against. + * + * It returns the substituted string, or NULL on error. + * + * Parts of this code are based on Henry Spencer's regsub(), from his + * AT&T V8 regexp package. + */ + +static char *ap_pregsub(const char *input, + const char *source, size_t nmatch, + ap_regmatch_t pmatch[]) +{ + const char *src = input; + char *dest, *dst; + char c; + size_t no; + int len; + + if (!source) + return NULL; + if (!nmatch) + return strdup(src); + + /* First pass, find the size */ + len = 0; + + while ((c = *src++) != '\0') { + if (c == '&') + no = 0; + else if (c == '$' && isdigit((unsigned char)*src)) + no = *src++ - '0'; + else + no = 10; + + if (no > 9) { /* Ordinary character. */ + if (c == '\\' && (*src == '$' || *src == '&')) + c = *src++; + len++; + } + else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) { + len += pmatch[no].rm_eo - pmatch[no].rm_so; + } + + } + + dest = dst = calloc(1, len + 1); + + /* Now actually fill in the string */ + + src = input; + + while ((c = *src++) != '\0') { + if (c == '&') + no = 0; + else if (c == '$' && isdigit((unsigned char)*src)) + no = *src++ - '0'; + else + no = 10; + + if (no > 9) { /* Ordinary character. */ + if (c == '\\' && (*src == '$' || *src == '&')) + c = *src++; + *dst++ = c; + } + else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) { + len = pmatch[no].rm_eo - pmatch[no].rm_so; + memcpy(dst, source + pmatch[no].rm_so, len); + dst += len; + } + + } + *dst = '\0'; + return dest; +} + +static int simple_rewrite(char *uri) +{ + if (rewrite_map) { + int i; + char buf[INTERNET_MAX_URL_LENGTH]; + for (i = 0; i < jk_map_size(rewrite_map); i++) { + const char *src = jk_map_name_at(rewrite_map, i); + if (*src == '~') + continue; /* Skip regexp rewrites */ + if (strncmp(uri, src, strlen(src)) == 0) { + StringCbCopy(buf, INTERNET_MAX_URL_LENGTH, jk_map_value_at(rewrite_map, i)); + StringCbCat(buf, INTERNET_MAX_URL_LENGTH, uri + strlen(src)); + StringCbCopy(uri, INTERNET_MAX_URL_LENGTH, buf); + return 1; + } + } + } + return 0; +} + +static int rregex_rewrite(char *uri) +{ + ap_regmatch_t regm[AP_MAX_REG_MATCH]; + + if (rregexp_map) { + int i; + for (i = 0; i < jk_map_size(rregexp_map); i++) { + ap_regex_t *regexp = (ap_regex_t *)jk_map_value_at(rregexp_map, i); + if (!ap_regexec(regexp, uri, AP_MAX_REG_MATCH, regm, 0)) { + char *subs = ap_pregsub(regexp->fake, uri, + AP_MAX_REG_MATCH, regm); + if (subs) { + char buf[INTERNET_MAX_URL_LENGTH]; + size_t diffsz = strlen(subs) - (regm[0].rm_eo - regm[0].rm_so); + memcpy(&buf[0], uri, regm[0].rm_so); + StringCbCopy(&buf[regm[0].rm_so], INTERNET_MAX_URL_LENGTH - regm[0].rm_so, subs); + StringCbCat(&buf[0], INTERNET_MAX_URL_LENGTH, uri + regm[0].rm_eo); + StringCbCopy(uri, INTERNET_MAX_URL_LENGTH, &buf[0]); + free(subs); + return 1; + } + } + } + } + return 0; +} + +DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, + DWORD dwNotificationType, LPVOID pvNotification) +{ + int rc; + + /* Initialise jk */ + if (is_inited && !is_mapread) { + char serverName[MAX_SERVERNAME] = ""; + char instanceId[MAX_INSTANCEID] = ""; + DWORD dwLen = MAX_SERVERNAME - MAX_INSTANCEID - 1; + + if (pfc->GetServerVariable(pfc, SERVER_NAME, serverName, &dwLen)) { + if (dwLen > 0) { + serverName[dwLen - 1] = '\0'; + dwLen = MAX_INSTANCEID; + if (pfc->GetServerVariable(pfc, INSTANCE_ID, instanceId, &dwLen)) { + if (dwLen > 0) { + instanceId[dwLen - 1] = '\0'; + StringCbCat(serverName, MAX_SERVERNAME, "_"); + StringCbCat(serverName, MAX_SERVERNAME, instanceId); + } + } + } + JK_ENTER_CS(&(init_cs), rc); + if (!is_mapread && init_jk(serverName)) + is_mapread = JK_TRUE; + JK_LEAVE_CS(&(init_cs), rc); + } + /* If we can't read the map we become dormant */ + if (!is_mapread) + is_inited = JK_FALSE; + } + if (!is_inited) { + /* In case the initialization failed + * return error. This will make entire IIS + * unusable like with Apache servers + */ + SetLastError(ERROR_INVALID_FUNCTION); + return SF_STATUS_REQ_ERROR; + } + if (iis_info.filter_notify_event == dwNotificationType) { + char uri[INTERNET_MAX_URL_LENGTH]; + char snuri[INTERNET_MAX_URL_LENGTH] = "/"; + char Host[INTERNET_MAX_URL_LENGTH] = ""; + char Port[INTERNET_MAX_URL_LENGTH] = ""; + char Translate[INTERNET_MAX_URL_LENGTH]; + char squery[INTERNET_MAX_URL_LENGTH] = ""; + char swindex[MAX_INSTANCEID] = ""; + BOOL(WINAPI * GetHeader) + (struct _HTTP_FILTER_CONTEXT * pfc, LPSTR lpszName, + LPVOID lpvBuffer, LPDWORD lpdwSize); + BOOL(WINAPI * SetHeader) + (struct _HTTP_FILTER_CONTEXT * pfc, LPSTR lpszName, + LPSTR lpszValue); + BOOL(WINAPI * AddHeader) + (struct _HTTP_FILTER_CONTEXT * pfc, LPSTR lpszName, + LPSTR lpszValue); + char *query; + DWORD sz = sizeof(uri); + DWORD szHost = sizeof(Host); + DWORD szPort = sizeof(Port); + DWORD szTranslate = sizeof(Translate); + + if (iis_info.filter_notify_event == SF_NOTIFY_AUTH_COMPLETE) { + GetHeader = + ((PHTTP_FILTER_AUTH_COMPLETE_INFO) pvNotification)->GetHeader; + SetHeader = + ((PHTTP_FILTER_AUTH_COMPLETE_INFO) pvNotification)->SetHeader; + AddHeader = + ((PHTTP_FILTER_AUTH_COMPLETE_INFO) pvNotification)->AddHeader; + } + else { + GetHeader = + ((PHTTP_FILTER_PREPROC_HEADERS) pvNotification)->GetHeader; + SetHeader = + ((PHTTP_FILTER_PREPROC_HEADERS) pvNotification)->SetHeader; + AddHeader = + ((PHTTP_FILTER_PREPROC_HEADERS) pvNotification)->AddHeader; + } + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Filter started"); + + /* + * Just in case somebody set these headers in the request! + */ + SetHeader(pfc, URI_HEADER_NAME, NULL); + SetHeader(pfc, QUERY_HEADER_NAME, NULL); + SetHeader(pfc, WORKER_HEADER_NAME, NULL); + SetHeader(pfc, WORKER_HEADER_INDEX, NULL); + SetHeader(pfc, TOMCAT_TRANSLATE_HEADER_NAME, NULL); + + // Suppress logging of original uri/query when we don't map a URL + if (pfc->pFilterContext) { + isapi_log_data_t *ld = (isapi_log_data_t *)pfc->pFilterContext; + ld->request_matched = JK_FALSE; + } + + if (!GetHeader(pfc, "url", (LPVOID) uri, (LPDWORD) & sz)) { + jk_log(logger, JK_LOG_ERROR, + "error while getting the url"); + return SF_STATUS_REQ_ERROR; + } + + if (strlen(uri)) { + int rc; + const char *worker = NULL; + rule_extension_t *extensions; + int worker_index = -1; + query = strchr(uri, '?'); + if (query) { + *query++ = '\0'; + StringCbCopy(squery, INTERNET_MAX_URL_LENGTH, query); + } + + rc = unescape_url(uri); + if (rc == BAD_REQUEST) { + jk_log(logger, JK_LOG_ERROR, + "[%s] contains one or more invalid escape sequences.", + uri); + write_error_response(pfc, 400); + return SF_STATUS_REQ_FINISHED; + } + else if (rc == BAD_PATH) { + jk_log(logger, JK_LOG_EMERG, + "[%s] contains forbidden escape sequences.", + uri); + write_error_response(pfc, 404); + return SF_STATUS_REQ_FINISHED; + } + getparents(uri); + if (pfc-> + GetServerVariable(pfc, SERVER_NAME, (LPVOID) Host, + (LPDWORD) & szHost)) { + if (szHost > 0) { + Host[szHost - 1] = '\0'; + } + } + Port[0] = '\0'; + if (pfc-> + GetServerVariable(pfc, "SERVER_PORT", (LPVOID) Port, + (LPDWORD) & szPort)) { + if (szPort > 0) { + Port[szPort - 1] = '\0'; + } + } + szPort = atoi(Port); + if (szPort != 80 && szPort != 443 && szHost > 0) { + StringCbCat(Host, INTERNET_MAX_URL_LENGTH, ":"); + StringCbCat(Host, INTERNET_MAX_URL_LENGTH, Port); + } + if (szHost > 0) { + StringCbCat(snuri, INTERNET_MAX_URL_LENGTH, Host); + worker = map_uri_to_worker_ext(uw_map, uri, snuri, + &extensions, &worker_index, logger); + } + else { + worker = map_uri_to_worker_ext(uw_map, uri, NULL, + &extensions, &worker_index, logger); + } + /* + * Check if somebody is feading us with his own TOMCAT data headers. + * We reject such postings ! + */ + if (worker) { + char *forwardURI; + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "check if [%s] points to the web-inf directory", + uri); + + if (uri_is_web_inf(uri)) { + jk_log(logger, JK_LOG_EMERG, + "[%s] points to the web-inf or meta-inf directory. " + "Somebody tries to hack into the site!!!", + uri); + + write_error_response(pfc, 404); + return SF_STATUS_REQ_FINISHED; + } + + /* This is a servlet, should redirect ... */ + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "[%s] is a servlet url - should redirect to %s", + uri, worker); + + /* get URI we should forward */ + if (uri_select_option == URI_SELECT_OPT_UNPARSED) { + /* get original unparsed URI */ + GetHeader(pfc, "url", (LPVOID) uri, (LPDWORD) & sz); + /* restore terminator for uri portion */ + if (query) + *(query - 1) = '\0'; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "forwarding original URI [%s]", + uri); + forwardURI = uri; + } + else if (uri_select_option == URI_SELECT_OPT_ESCAPED) { + if (!escape_url(uri, snuri, INTERNET_MAX_URL_LENGTH)) { + jk_log(logger, JK_LOG_ERROR, + "[%s] re-encoding request exceeds maximum buffer size.", + uri); + write_error_response(pfc, 400); + return SF_STATUS_REQ_FINISHED; + } + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "fowarding escaped URI [%s]", + snuri); + forwardURI = snuri; + } + else if (uri_select_option == URI_SELECT_OPT_PROXY) { + if (!jk_canonenc(uri, snuri, INTERNET_MAX_URL_LENGTH)) { + jk_log(logger, JK_LOG_ERROR, + "[%s] re-encoding request exceeds maximum buffer size.", + uri); + write_error_response(pfc, 400); + return SF_STATUS_REQ_FINISHED; + } + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "fowarding escaped URI [%s]", + snuri); + forwardURI = snuri; + } + else { + forwardURI = uri; + } + /* Do a simple rewrite . + * Note that URI can be escaped, so thus the rule has + * to be in that case. + * + * TODO: Add more advanced regexp rewrite. + */ + if (JK_IS_DEBUG_LEVEL(logger)) { + char duri[INTERNET_MAX_URL_LENGTH]; + StringCbCopy(duri, INTERNET_MAX_URL_LENGTH, forwardURI); + if (simple_rewrite(forwardURI)) { + jk_log(logger, JK_LOG_DEBUG, + "rewritten URI [%s]->[%s]", + duri, forwardURI); + } + else if (rregex_rewrite(forwardURI)) { + jk_log(logger, JK_LOG_DEBUG, + "rewritten URI [%s]->[%s]", + duri, forwardURI); + } + } + else { + if (!simple_rewrite(forwardURI)) + rregex_rewrite(forwardURI); + } + + itoa(worker_index, swindex, 10); + if (!AddHeader(pfc, URI_HEADER_NAME, forwardURI) || + ((strlen(squery) > 0) + ? !AddHeader(pfc, QUERY_HEADER_NAME, squery) : FALSE) || + !AddHeader(pfc, WORKER_HEADER_NAME, (LPSTR)worker) || + !AddHeader(pfc, WORKER_HEADER_INDEX, swindex) || + !SetHeader(pfc, "url", extension_uri)) { + jk_log(logger, JK_LOG_ERROR, + "error while adding request headers"); + SetLastError(ERROR_INVALID_PARAMETER); + return SF_STATUS_REQ_ERROR; + } + + /* Move Translate: header to a temporary header so + * that the extension proc will be called. + * This allows the servlet to handle 'Translate: f'. + */ + if (GetHeader + (pfc, TRANSLATE_HEADER, (LPVOID) Translate, + (LPDWORD) & szTranslate) && Translate != NULL + && szTranslate > 0) { + if (!AddHeader + (pfc, TOMCAT_TRANSLATE_HEADER_NAME, Translate)) { + jk_log(logger, JK_LOG_ERROR, + "error while adding Tomcat-Translate headers"); + return SF_STATUS_REQ_ERROR; + } + SetHeader(pfc, "Translate:", NULL); + } + if (!pfc->pFilterContext) { + isapi_log_data_t *ld = (isapi_log_data_t *)pfc->AllocMem(pfc, sizeof(isapi_log_data_t), 0); + if (!ld) { + jk_log(logger, JK_LOG_ERROR, + "error while allocating memory"); + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return SF_STATUS_REQ_ERROR; + } + memset(ld, 0, sizeof(isapi_log_data_t)); + StringCbCopy(ld->uri, INTERNET_MAX_URL_LENGTH, forwardURI); + StringCbCopy(ld->query, INTERNET_MAX_URL_LENGTH, squery); + ld->request_matched = JK_TRUE; + pfc->pFilterContext = ld; + } else { + isapi_log_data_t *ld = (isapi_log_data_t *)pfc->pFilterContext; + memset(ld, 0, sizeof(isapi_log_data_t)); + StringCbCopy(ld->uri, INTERNET_MAX_URL_LENGTH, forwardURI); + StringCbCopy(ld->query, INTERNET_MAX_URL_LENGTH, squery); + ld->request_matched = JK_TRUE; + } + } + else { + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "[%s] is not a servlet url", uri); + if (strip_session) { + char *jsessionid = strstr(uri, JK_PATH_SESSION_IDENTIFIER); + if (jsessionid) { + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "removing session identifier [%s] for non servlet url [%s]", + jsessionid, uri); + *jsessionid = '\0'; + SetHeader(pfc, "url", uri); + } + } + } + } + } + else if (dwNotificationType == SF_NOTIFY_LOG) { + if (pfc->pFilterContext) { + isapi_log_data_t *ld = (isapi_log_data_t *)pfc->pFilterContext; + if (ld->request_matched) { + HTTP_FILTER_LOG *pl = (HTTP_FILTER_LOG *)pvNotification; + pl->pszTarget = ld->uri; + pl->pszParameters = ld->query; + } + } + } + return SF_STATUS_REQ_NEXT_NOTIFICATION; +} + + +BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO * pVer) +{ + int rc; + BOOL rv = TRUE; + + pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR); + + StringCbCopy(pVer->lpszExtensionDesc, HSE_MAX_EXT_DLL_NAME_LEN, (VERSION_STRING)); + + + JK_ENTER_CS(&(init_cs), rc); + if (!is_inited) { + rv = initialize_extension(); + } + JK_LEAVE_CS(&(init_cs), rc); + + return rv; +} + +DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpEcb) +{ + int rv; + DWORD rc = HSE_STATUS_ERROR; + + lpEcb->dwHttpStatusCode = HTTP_STATUS_SERVER_ERROR; + + JK_TRACE_ENTER(logger); + + /* Initialise jk */ + if (is_inited && !is_mapread) { + char serverName[MAX_SERVERNAME] = ""; + char instanceId[MAX_INSTANCEID] = ""; + + DWORD dwLen = MAX_SERVERNAME - MAX_INSTANCEID - 1; + if (lpEcb->GetServerVariable(lpEcb->ConnID, + SERVER_NAME, serverName, &dwLen)) { + if (dwLen > 0) { + serverName[dwLen - 1] = '\0'; + dwLen = MAX_INSTANCEID; + if (lpEcb->GetServerVariable(lpEcb->ConnID, + INSTANCE_ID, instanceId, &dwLen)) { + if (dwLen > 0) { + instanceId[dwLen - 1] = '\0'; + StringCbCat(serverName, MAX_SERVERNAME, "_"); + StringCbCat(serverName, MAX_SERVERNAME, instanceId); + } + } + } + JK_ENTER_CS(&(init_cs), rv); + if (!is_mapread && init_jk(serverName)) + is_mapread = JK_TRUE; + JK_LEAVE_CS(&(init_cs), rv); + } + if (!is_mapread) + is_inited = JK_FALSE; + } + + if (is_inited) { + isapi_private_data_t private_data; + jk_ws_service_t s; + jk_pool_atom_t buf[SMALL_POOL_SIZE]; + char *worker_name; + + if (!watchdog_interval) + wc_maintain(logger); + jk_init_ws_service(&s); + jk_open_pool(&private_data.p, buf, sizeof(buf)); + + private_data.bytes_read_so_far = 0; + private_data.lpEcb = lpEcb; + private_data.chunk_content = JK_FALSE; + + s.ws_private = &private_data; + s.pool = &private_data.p; + + if (init_ws_service(&private_data, &s, &worker_name)) { + jk_worker_t *worker = wc_get_worker_for_name(worker_name, logger); + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "%s a worker for name %s", + worker ? "got" : "could not get", worker_name); + + if (worker) { + jk_endpoint_t *e = NULL; + if (worker->get_endpoint(worker, &e, logger)) { + int is_error = JK_HTTP_SERVER_ERROR; + int result; + if ((result = e->service(e, &s, logger, &is_error)) > 0) { + if (s.extension.use_server_error_pages && + s.http_response_status >= s.extension.use_server_error_pages) { + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Forwarding status=%d" + " for worker=%s", + s.http_response_status, worker_name); + lpEcb->dwHttpStatusCode = s.http_response_status; + write_error_message(lpEcb, s.http_response_status); + } + else { + rc = HSE_STATUS_SUCCESS; + lpEcb->dwHttpStatusCode = s.http_response_status; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "service() returned OK"); + } + } + else { + if ((result == JK_CLIENT_ERROR) && (is_error == JK_HTTP_OK)) { + jk_log(logger, JK_LOG_INFO, + "service() failed because client aborted connection"); + } + else { + jk_log(logger, JK_LOG_ERROR, + "service() failed with http error %d", is_error); + } + lpEcb->dwHttpStatusCode = is_error; + write_error_message(lpEcb, is_error); + } + e->done(&e, logger); + } + else { + jk_log(logger, JK_LOG_ERROR, + "Failed to obtain an endpoint to service request - " + "your connection_pool_size is probably less than the threads in your web server!"); + } + } + else { + jk_log(logger, JK_LOG_ERROR, + "could not get a worker for name %s", + worker_name); + } + } + else { + jk_log(logger, JK_LOG_ERROR, + "failed to init service for request."); + } + jk_close_pool(&private_data.p); + } + else { + jk_log(logger, JK_LOG_ERROR, + "not initialized"); + } + + JK_TRACE_EXIT(logger); + return rc; +} + + + +BOOL WINAPI TerminateExtension(DWORD dwFlags) +{ + return TerminateFilter(dwFlags); +} + +BOOL WINAPI TerminateFilter(DWORD dwFlags) +{ + int rc; + + UNREFERENCED_PARAMETER(dwFlags); + + JK_ENTER_CS(&(init_cs), rc); + if (is_inited) { + jk_log(logger, JK_LOG_INFO, "%s stopping", (FULL_VERSION_STRING)); + is_inited = JK_FALSE; + watchdog_interval = 0; + if (watchdog_handle) { + WaitForSingleObject(watchdog_handle, INFINITE); + CloseHandle(watchdog_handle); + watchdog_handle = NULL; + } + if (is_mapread) { + uri_worker_map_free(&uw_map, logger); + is_mapread = JK_FALSE; + } + if (workers_map) { + jk_map_free(&workers_map); + } + if (rewrite_map) { + jk_map_free(&rewrite_map); + } + if (rregexp_map) { + int i; + for (i = 0; i < jk_map_size(rregexp_map); i++) { + ap_regex_t *regexp = (ap_regex_t *)jk_map_value_at(rregexp_map, i); + if (regexp) { + ap_regfree(regexp); + free(regexp); + } + } + jk_map_free(&rregexp_map); + } + wc_close(logger); + jk_shm_close(); + JK_ENTER_CS(&(log_cs), rc); + if (logger) { + jk_close_file_logger(&logger); + } + JK_LEAVE_CS(&(log_cs), rc); + } + JK_LEAVE_CS(&(init_cs), rc); + + return TRUE; +} + + +BOOL WINAPI DllMain(HINSTANCE hInst, // Instance Handle of the DLL + ULONG ulReason, // Reason why NT called this DLL + LPVOID lpReserved) // Reserved parameter for future use +{ + int rc; + BOOL fReturn = TRUE; + char fname[MAX_PATH]; + + UNREFERENCED_PARAMETER(lpReserved); + + switch (ulReason) { + case DLL_PROCESS_ATTACH: + if (GetModuleFileName(hInst, fname, sizeof(fname))) { + char *p = strrchr(fname, '.'); + if (p) { + *p = '\0'; + StringCbCopy(ini_file_name, MAX_PATH, fname); + StringCbCat(ini_file_name, MAX_PATH, ".properties"); + } + else { + /* Cannot obtain file name ? */ + fReturn = JK_FALSE; + } + if ((p = strrchr(fname, '\\'))) { + *(p++) = '\0'; + StringCbCopy(dll_file_path, MAX_PATH, fname); + jk_map_alloc(&jk_environment_map); + jk_map_add(jk_environment_map, "JKISAPI_PATH", dll_file_path); + jk_map_add(jk_environment_map, "JKISAPI_NAME", p); + } + else { + /* Cannot obtain file name ? */ + fReturn = JK_FALSE; + } + } + else { + fReturn = JK_FALSE; + } + /* Construct redirector headers to use for this redirector instance */ + StringCbPrintf(URI_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, URI_HEADER_NAME_BASE, hInst); + StringCbPrintf(QUERY_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, QUERY_HEADER_NAME_BASE, hInst); + StringCbPrintf(WORKER_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, WORKER_HEADER_NAME_BASE, hInst); + StringCbPrintf(WORKER_HEADER_INDEX, MAX_PATH, HEADER_TEMPLATE, WORKER_HEADER_INDEX_BASE, hInst); + StringCbPrintf(TOMCAT_TRANSLATE_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, TOMCAT_TRANSLATE_HEADER_NAME_BASE, hInst); + + /* Construct the HTTP_ headers that will be seen in ExtensionProc */ + StringCbPrintf(HTTP_URI_HEADER_NAME, MAX_PATH, HTTP_HEADER_TEMPLATE, URI_HEADER_NAME_BASE, hInst); + StringCbPrintf(HTTP_QUERY_HEADER_NAME, MAX_PATH, HTTP_HEADER_TEMPLATE, QUERY_HEADER_NAME_BASE, hInst); + StringCbPrintf(HTTP_WORKER_HEADER_NAME, MAX_PATH, HTTP_HEADER_TEMPLATE, WORKER_HEADER_NAME_BASE, hInst); + StringCbPrintf(HTTP_WORKER_HEADER_INDEX, MAX_PATH, HTTP_HEADER_TEMPLATE, WORKER_HEADER_INDEX_BASE, hInst); + + JK_INIT_CS(&init_cs, rc); + JK_INIT_CS(&log_cs, rc); + + break; + case DLL_PROCESS_DETACH: + __try { + TerminateFilter(HSE_TERM_MUST_UNLOAD); + } + __except(1) { + } + JK_DELETE_CS(&init_cs, rc); + JK_DELETE_CS(&log_cs, rc); + break; + + default: + break; + } + + return fReturn; +} + +static DWORD WINAPI watchdog_thread(void *param) +{ + int i; + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Watchdog thread initialized with %u second interval", + watchdog_interval); + } + while (watchdog_interval) { + for (i = 0; i < (watchdog_interval * 10); i++) { + if (!watchdog_interval) + break; + Sleep(100); + } + if (!watchdog_interval) + break; + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Watchdog thread running"); + } + if (worker_mount_file[0]) { + jk_shm_lock(); + uri_worker_map_update(uw_map, 0, logger); + jk_shm_unlock(); + } + wc_maintain(logger); + } + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Watchdog thread finished"); + } + return 0; +} + +/* + * Reinitializes the logger, formatting the log file name if rotation is enabled, + * and calculating the next rotation time if applicable. + */ +static int init_logger(int rotate, jk_logger_t **l) +{ + int rc = JK_TRUE; + int log_open = rotate; /* log is assumed open if a rotate is requested */ + char *log_file_name; + char log_file_name_buf[MAX_PATH*2]; + + /* If log rotation is enabled, format the log filename */ + if ((log_rotationtime > 0) || (log_filesize > 0)) { + time_t t; + t = time(NULL); + + if (log_rotationtime > 0) { + /* Align time to rotationtime intervals */ + t = (t / log_rotationtime) * log_rotationtime; + + /* Calculate rotate time */ + log_next_rotate_time = t + log_rotationtime; + } + + log_file_name = log_file_name_buf; + if (strchr(log_file, '%')) { + struct tm *tm_now; + + /* If there are %s in the log file name, treat it as a sprintf format */ + tm_now = localtime(&t); + strftime(log_file_name, sizeof(log_file_name_buf), log_file, tm_now); + } else { + /* Otherwise append the number of seconds to the base name */ + StringCbPrintf(log_file_name, sizeof(log_file_name_buf), "%s.%d", log_file, (long)t); + } + } else { + log_file_name = log_file; + } + + /* Close the current log file if required, and the effective log file name has changed */ + if (log_open && strncmp(log_file_name, log_file_effective, strlen(log_file_name)) != 0) { + FILE* lf = ((jk_file_logger_t* )logger->logger_private)->logfile; + fprintf(lf, "Log rotated to %s\r\n", log_file_name); + fflush(lf); + rc = jk_close_file_logger(&logger); + log_open = JK_FALSE; + } + + if (!log_open) { + if (jk_open_file_logger(&logger, log_file_name, log_level)) { + logger->log = iis_log_to_file; + + /* Remember the current log file name for the next potential rotate */ + StringCbCopy(log_file_effective, sizeof(log_file_effective), log_file_name); + rc = JK_TRUE; + } else { + logger = NULL; + rc = JK_FALSE; + } + } + + /* Update logger being used for this log call so it occurs on new file */ + (*l) = logger; + return rc; +} + +/* + * Checks whether the log needs to be rotated. Must be called while holding the log lock. + * The behaviour here is based on the Apache rotatelogs program. + * http://httpd.apache.org/docs/2.0/programs/rotatelogs.html + */ +static int JK_METHOD rotate_log_file(jk_logger_t **l) +{ + int rc = JK_TRUE; + int rotate = JK_FALSE; + + if (log_rotationtime > 0) { + time_t t = time(NULL); + + if (t >= log_next_rotate_time) { + rotate = JK_TRUE; + } + } else if (log_filesize > 0) { + LARGE_INTEGER filesize; + HANDLE h = (HANDLE)_get_osfhandle(fileno(((jk_file_logger_t *)(*l)->logger_private)->logfile)); + GetFileSizeEx(h, &filesize); + + if ((ULONGLONG)filesize.QuadPart >= log_filesize) { + rotate = JK_TRUE; + } + } + if (rotate) { + rc = init_logger(JK_TRUE, l); + } + return rc; +} + +/* + * Log messages to the log file, rotating the log when required. + */ +static int JK_METHOD iis_log_to_file(jk_logger_t *l, int level, + int used, char *what) +{ + int rc = JK_FALSE; + + if (l && + (l->level <= level || level == JK_LOG_REQUEST_LEVEL) && + l->logger_private && what && used > 0) { + jk_file_logger_t *p = l->logger_private; + rc = JK_TRUE; + + if (p->logfile) { + what[used++] = '\r'; + what[used++] = '\n'; + what[used] = '\0'; + + /* Perform logging within critical section to protect rotation */ + JK_ENTER_CS(&(log_cs), rc); + if (rc && rotate_log_file(&l)) { + /* The rotation process will reallocate the jk_logger_t structure, so refetch */ + FILE *rotated = ((jk_file_logger_t *)l->logger_private)->logfile; + fputs(what, rotated); + fflush(rotated); + JK_LEAVE_CS(&(log_cs), rc); + } + } + } + return rc; +} + +static int init_jk(char *serverName) +{ + char shm_name[MAX_PATH]; + int rc = JK_FALSE; + + init_logger(JK_FALSE, &logger); + /* TODO: Use System logging to notify the user that + * we cannot open the configured log file. + */ + + StringCbCopy(shm_name, MAX_PATH, SHM_DEF_NAME); + + jk_log(logger, JK_LOG_INFO, "Starting %s", (FULL_VERSION_STRING)); + + if (*serverName) { + size_t i; + StringCbCat(shm_name, MAX_PATH, "_"); + StringCbCat(shm_name, MAX_PATH, serverName); + for(i = 0; i < strlen(shm_name); i++) { + shm_name[i] = toupper(shm_name[i]); + if (!isalnum(shm_name[i])) + shm_name[i] = '_'; + } + } + + jk_set_worker_def_cache_size(DEFAULT_WORKER_THREADS); + + /* Logging the initialization type: registry or properties file in virtual dir + */ + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Detected IIS version %d.%d", iis_info.major, iis_info.minor); + if (using_ini_file) { + jk_log(logger, JK_LOG_DEBUG, "Using ini file %s.", ini_file_name); + } + else { + jk_log(logger, JK_LOG_DEBUG, "Using registry."); + } + + jk_log(logger, JK_LOG_DEBUG, "Using log file %s.", log_file); + jk_log(logger, JK_LOG_DEBUG, "Using log level %d.", log_level); + jk_log(logger, JK_LOG_DEBUG, "Using log rotation time %d seconds.", log_rotationtime); + jk_log(logger, JK_LOG_DEBUG, "Using log file size %d bytes.", log_filesize); + + jk_log(logger, JK_LOG_DEBUG, "Using extension uri %s.", extension_uri); + jk_log(logger, JK_LOG_DEBUG, "Using worker file %s.", worker_file); + jk_log(logger, JK_LOG_DEBUG, "Using worker mount file %s.", + worker_mount_file); + jk_log(logger, JK_LOG_DEBUG, "Using rewrite rule file %s.", + rewrite_rule_file); + jk_log(logger, JK_LOG_DEBUG, "Using uri select %d.", uri_select_option); + jk_log(logger, JK_LOG_DEBUG, "Using%s chunked encoding.", (chunked_encoding_enabled ? "" : " no")); + + jk_log(logger, JK_LOG_DEBUG, "Using notification event %s (0x%08x)", + (iis_info.filter_notify_event == SF_NOTIFY_AUTH_COMPLETE) ? + "SF_NOTIFY_AUTH_COMPLETE" : + ((iis_info.filter_notify_event == SF_NOTIFY_PREPROC_HEADERS) ? + "SF_NOTIFY_PREPROC_HEADERS" : "UNKNOWN"), + iis_info.filter_notify_event); + + if (error_page) { + jk_log(logger, JK_LOG_DEBUG, "Using error page '%s'.", error_page); + } + jk_log(logger, JK_LOG_DEBUG, "Using uri header %s.", URI_HEADER_NAME); + jk_log(logger, JK_LOG_DEBUG, "Using query header %s.", QUERY_HEADER_NAME); + jk_log(logger, JK_LOG_DEBUG, "Using worker header %s.", WORKER_HEADER_NAME); + jk_log(logger, JK_LOG_DEBUG, "Using worker index %s.", WORKER_HEADER_INDEX); + jk_log(logger, JK_LOG_DEBUG, "Using translate header %s.", TOMCAT_TRANSLATE_HEADER_NAME); + jk_log(logger, JK_LOG_DEBUG, "Using a default of %d connections per pool.", + DEFAULT_WORKER_THREADS); + } + + if ((log_rotationtime > 0) && (log_filesize > 0)) { + jk_log(logger, JK_LOG_WARNING, + "%s is defined in configuration, but will be ignored because %s is set. ", + LOG_FILESIZE_TAG, LOG_ROTATION_TIME_TAG); + } + + if (rewrite_rule_file[0] && jk_map_alloc(&rewrite_map)) { + if (jk_map_read_properties(rewrite_map, NULL, rewrite_rule_file, + NULL, JK_MAP_HANDLE_RAW, logger)) { + int i; + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Loaded rewrite rule file %s.", + rewrite_rule_file); + + } + jk_map_alloc(&rregexp_map); + for (i = 0; i < jk_map_size(rewrite_map); i++) { + const char *src = jk_map_name_at(rewrite_map, i); + if (*src == '~') { + ap_regex_t *regexp = malloc(sizeof(ap_regex_t)); + const char *val = jk_map_value_at(rewrite_map, i); + /* Skip leading tilde */ + regexp->real = src + 1; + regexp->fake = val; + if (!ap_regcomp(regexp, regexp->real, AP_REG_EXTENDED)) { + jk_map_add(rregexp_map, regexp->real, regexp); + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Added regular expression rule %s -> %s", + regexp->real, regexp->fake); + } + } + else { + jk_log(logger, JK_LOG_ERROR, + "Unable to compile regular expression %s", + regexp->real); + free(regexp); + } + } + } + } + else { + jk_map_free(&rewrite_map); + rewrite_map = NULL; + } + } + + if (uri_worker_map_alloc(&uw_map, NULL, logger)) { + rc = JK_FALSE; + if (reject_unsafe) + uw_map->reject_unsafe = 1; + else + uw_map->reject_unsafe = 0; + uw_map->reload = worker_mount_reload; + if (worker_mount_file[0]) { + uw_map->fname = worker_mount_file; + rc = uri_worker_map_load(uw_map, logger); + } + } + if (rc) { + rc = JK_FALSE; + if (jk_map_alloc(&workers_map)) { + if (jk_map_read_properties(workers_map, NULL, worker_file, NULL, + JK_MAP_HANDLE_DUPLICATES, logger)) { + int rv; + + /* we add the URI->WORKER MAP since workers using AJP14 will feed it */ + + if (jk_map_resolve_references(workers_map, "worker.", 1, 1, logger) == JK_FALSE) { + jk_log(logger, JK_LOG_ERROR, "Error in resolving configuration references"); + } + /* + * Create named shared memory for each server + */ + if (shm_config_size == 0) + shm_config_size = jk_shm_calculate_size(workers_map, logger); + else { + jk_log(logger, JK_LOG_WARNING, + "The optimal shared memory size can now be determined automatically."); + jk_log(logger, JK_LOG_WARNING, + "You can remove the shm_size attribute if you want to use the optimal size."); + } + if ((rv = jk_shm_open(shm_name, shm_config_size, logger)) != 0) { + /* TODO: Do not try to open the worker if we cannot create + * the shared memory segment. + */ + jk_log(logger, JK_LOG_ERROR, + "Initializing shm:%s errno=%d. Load balancing workers will not function properly.", + jk_shm_name(), rv); + } + worker_env.uri_to_worker = uw_map; + worker_env.server_name = serverName; + worker_env.pool = NULL; + + if (wc_open(workers_map, &worker_env, logger)) { + rc = JK_TRUE; + } + uri_worker_map_ext(uw_map, logger); + uri_worker_map_switch(uw_map, logger); + } + else + jk_log(logger, JK_LOG_EMERG, + "Unable to read worker file %s.", worker_file); + if (rc != JK_TRUE) { + jk_map_free(&workers_map); + workers_map = NULL; + } + } + } + if (rc) { + if (watchdog_interval) { + DWORD wi; + watchdog_handle = CreateThread(NULL, 0, watchdog_thread, + NULL, 0, &wi); + if (!watchdog_handle) { + jk_log(logger, JK_LOG_EMERG, "Error %d (0x%08x) creating Watchdog thread", + GetLastError(), GetLastError()); + watchdog_interval = 0; + } + } + jk_log(logger, JK_LOG_INFO, "%s initialized", (FULL_VERSION_STRING)); + } + return rc; +} + +static BOOL initialize_extension(void) +{ + + if (read_registry_init_data()) { + if (get_iis_info(&iis_info) != JK_TRUE) { + jk_log(logger, JK_LOG_ERROR, "Could not retrieve IIS version from registry"); + } + is_inited = JK_TRUE; + } + return is_inited; +} + +int parse_uri_select(const char *uri_select) +{ + if (0 == strcasecmp(uri_select, URI_SELECT_PARSED_VERB)) { + return URI_SELECT_OPT_PARSED; + } + + if (0 == strcasecmp(uri_select, URI_SELECT_UNPARSED_VERB)) { + return URI_SELECT_OPT_UNPARSED; + } + + if (0 == strcasecmp(uri_select, URI_SELECT_ESCAPED_VERB)) { + return URI_SELECT_OPT_ESCAPED; + } + + if (0 == strcasecmp(uri_select, URI_SELECT_PROXY_VERB)) { + return URI_SELECT_OPT_PROXY; + } + + return -1; +} + +static int read_registry_init_data(void) +{ + char tmpbuf[MAX_PATH]; + int ok = JK_TRUE; + LPVOID src; + HKEY hkey; + jk_map_t *map = NULL; + + if (jk_map_alloc(&map)) { + if (jk_map_read_properties(map, jk_environment_map, ini_file_name, NULL, + JK_MAP_HANDLE_DUPLICATES, logger)) { + using_ini_file = JK_TRUE; + src = map; + } + else { + jk_map_free(&map); + } + } + if (!using_ini_file) { + long rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_LOCATION, + (DWORD)0, KEY_READ, &hkey); + if (ERROR_SUCCESS != rc) { + return JK_FALSE; + } + else { + src = &hkey; + } + } + ok = ok && get_config_parameter(src, JK_LOG_FILE_TAG, log_file, sizeof(log_file)); + if (get_config_parameter(src, JK_LOG_LEVEL_TAG, tmpbuf, sizeof(tmpbuf))) { + log_level = jk_parse_log_level(tmpbuf); + } + if (get_config_parameter(src, LOG_ROTATION_TIME_TAG, tmpbuf, sizeof(tmpbuf))) { + log_rotationtime = atol(tmpbuf); + if (log_rotationtime < 0) { + log_rotationtime = 0; + } + } + if (get_config_parameter(src, LOG_FILESIZE_TAG, tmpbuf, sizeof(tmpbuf))) { + size_t tl = strlen(tmpbuf); + if (tl > 0) { + /* rotatelogs has an 'M' suffix on filesize, which we optionally support for consistency */ + if (tmpbuf[tl - 1] == 'M') { + tmpbuf[tl - 1] = '\0'; + } + log_filesize = atol(tmpbuf); + if (log_filesize < 0) { + log_filesize = 0; + } + /* Convert to MB as per Apache rotatelogs */ + log_filesize *= (1000 * 1000); + } + } + + ok = ok && get_config_parameter(src, EXTENSION_URI_TAG, extension_uri, sizeof(extension_uri)); + ok = ok && get_config_parameter(src, JK_WORKER_FILE_TAG, worker_file, sizeof(worker_file)); + ok = ok && get_config_parameter(src, JK_MOUNT_FILE_TAG, worker_mount_file, sizeof(worker_mount_file)); + get_config_parameter(src, URI_REWRITE_TAG, rewrite_rule_file, sizeof(rewrite_rule_file)); + if (get_config_parameter(src, URI_SELECT_TAG, tmpbuf, sizeof(tmpbuf))) { + int opt = parse_uri_select(tmpbuf); + if (opt >= 0) { + uri_select_option = opt; + } + else { + ok = JK_FALSE; + } + } + shm_config_size = (size_t) get_config_int(src, SHM_SIZE_TAG, 0); + worker_mount_reload = get_config_int(src, WORKER_MOUNT_RELOAD_TAG, JK_URIMAP_DEF_RELOAD); + strip_session = get_config_bool(src, STRIP_SESSION_TAG, JK_FALSE); +#ifndef AUTOMATIC_AUTH_NOTIFICATION + use_auth_notification_flags = get_config_int(src, AUTH_COMPLETE_TAG, 1); +#endif + reject_unsafe = get_config_bool(src, REJECT_UNSAFE_TAG, JK_FALSE); + watchdog_interval = get_config_int(src, WATCHDOG_INTERVAL_TAG, 0); + if (watchdog_interval < 0) + watchdog_interval = 0; + chunked_encoding_enabled = get_config_bool(src, ENABLE_CHUNKED_ENCODING_TAG, JK_FALSE); + if (get_config_parameter(src, ERROR_PAGE_TAG, error_page_buf, sizeof(error_page_buf))) { + error_page = error_page_buf; + } + + if (using_ini_file) { + jk_map_free(&map); + } + else { + RegCloseKey(hkey); + } + return ok; +} + +static int get_config_parameter(LPVOID src, const char *tag, + char *val, DWORD sz) +{ + const char *tmp = NULL; + if (using_ini_file) { + tmp = jk_map_get_string((jk_map_t*)src, tag, NULL); + if (tmp && (strlen(tmp) < sz)) { + StringCbCopy(val, sz, tmp); + return JK_TRUE; + } + else { + return JK_FALSE; + } + } else { + return get_registry_config_parameter(*((HKEY*)src), tag, val, sz); + } +} + +static int get_config_int(LPVOID src, const char *tag, int def) +{ + if (using_ini_file) { + return jk_map_get_int((jk_map_t*)src, tag, def); + } else { + int val; + if (get_registry_config_number(*((HKEY*)src), tag, &val) ) { + return val; + } + else { + return def; + } + } +} + +static int get_config_bool(LPVOID src, const char *tag, int def) +{ + if (using_ini_file) { + return jk_map_get_bool((jk_map_t*)src, tag, def); + } else { + char tmpbuf[128]; + if (get_registry_config_parameter(*((HKEY*)src), tag, + tmpbuf, sizeof(tmpbuf))) { + return jk_get_bool_code(tmpbuf, def); + } + else { + return def; + } + } +} + +static int get_registry_config_parameter(HKEY hkey, + const char *tag, char *b, DWORD sz) +{ + DWORD type = 0; + LONG lrc; + + sz = sz - 1; /* Reserve space for RegQueryValueEx to add null terminator */ + b[sz] = '\0'; /* Null terminate in case RegQueryValueEx doesn't */ + + lrc = RegQueryValueEx(hkey, tag, (LPDWORD) 0, &type, (LPBYTE) b, &sz); + if ((ERROR_SUCCESS != lrc) || (type != REG_SZ)) { + return JK_FALSE; + } + + return JK_TRUE; +} + +static int get_registry_config_number(HKEY hkey, + const char *tag, int *val) +{ + DWORD type = 0; + DWORD data = 0; + DWORD sz = sizeof(DWORD); + LONG lrc; + + lrc = RegQueryValueEx(hkey, tag, (LPDWORD)0, &type, (LPBYTE)&data, &sz); + if ((ERROR_SUCCESS != lrc) || (type != REG_DWORD)) { + return JK_FALSE; + } + + *val = (int)data; + + return JK_TRUE; +} + +static int init_ws_service(isapi_private_data_t * private_data, + jk_ws_service_t *s, char **worker_name) +{ + char *huge_buf = NULL; /* should be enough for all */ + int worker_index = -1; + rule_extension_t *e; + char temp_buf[64]; + DWORD huge_buf_sz; + BOOL unknown_content_length = FALSE; + + JK_TRACE_ENTER(logger); + + s->start_response = start_response; + s->read = iis_read; + s->write = iis_write; + s->done = iis_done; + + if (!(huge_buf = jk_pool_alloc(&private_data->p, MAX_PACKET_SIZE))) { + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + huge_buf_sz = MAX_PACKET_SIZE; + GET_SERVER_VARIABLE_VALUE(HTTP_WORKER_HEADER_NAME, (*worker_name)); + GET_SERVER_VARIABLE_VALUE(HTTP_URI_HEADER_NAME, s->req_uri); + GET_SERVER_VARIABLE_VALUE(HTTP_QUERY_HEADER_NAME, s->query_string); + GET_SERVER_VARIABLE_VALUE_INT(HTTP_WORKER_HEADER_INDEX, worker_index, -1); + + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Reading extension header %s: %s", HTTP_WORKER_HEADER_NAME, (*worker_name)); + jk_log(logger, JK_LOG_DEBUG, "Reading extension header %s: %d", HTTP_WORKER_HEADER_INDEX, worker_index); + jk_log(logger, JK_LOG_DEBUG, "Reading extension header %s: %s", HTTP_URI_HEADER_NAME, s->req_uri); + jk_log(logger, JK_LOG_DEBUG, "Reading extension header %s: %s", HTTP_QUERY_HEADER_NAME, s->query_string); + } + + if (s->req_uri == NULL) { + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "No URI header value provided. Defaulting to old behaviour" ); + s->query_string = private_data->lpEcb->lpszQueryString; + *worker_name = DEFAULT_WORKER_NAME; + GET_SERVER_VARIABLE_VALUE("URL", s->req_uri); + if (unescape_url(s->req_uri) < 0) { + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + getparents(s->req_uri); + } + + GET_SERVER_VARIABLE_VALUE("AUTH_TYPE", s->auth_type); + GET_SERVER_VARIABLE_VALUE("REMOTE_USER", s->remote_user); + GET_SERVER_VARIABLE_VALUE("SERVER_PROTOCOL", s->protocol); + GET_SERVER_VARIABLE_VALUE("REMOTE_HOST", s->remote_host); + GET_SERVER_VARIABLE_VALUE("REMOTE_ADDR", s->remote_addr); + GET_SERVER_VARIABLE_VALUE("REMOTE_PORT", s->remote_port); + GET_SERVER_VARIABLE_VALUE(SERVER_NAME, s->server_name); + GET_SERVER_VARIABLE_VALUE_INT("SERVER_PORT", s->server_port, 80); + GET_SERVER_VARIABLE_VALUE(SERVER_SOFTWARE, s->server_software); + GET_SERVER_VARIABLE_VALUE_INT("SERVER_PORT_SECURE", s->is_ssl, 0); + + s->method = private_data->lpEcb->lpszMethod; + /* Check for Transfer Encoding */ + if (get_server_value(private_data->lpEcb, + "HTTP_TRANSFER_ENCODING", + temp_buf, + (DWORD)sizeof(temp_buf))) { + if (strcasecmp(temp_buf, TRANSFER_ENCODING_CHUNKED_VALUE) == 0) { + s->is_chunked = JK_TRUE; + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Request is Transfer-Encoding: chunked"); + } + } + else { + /* XXX: What to do with non chunked T-E ? + */ + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Unsupported Transfer-Encoding: %s", + temp_buf); + } + } + if (private_data->lpEcb->cbTotalBytes == 0xFFFFFFFF) { + /* We have size larger then 4Gb or Transfer-Encoding: chunked + * ReadClient should be called until no more data is returned + */ + unknown_content_length = TRUE; + } + else { + /* Use the IIS provided content length */ + s->content_length = (jk_uint64_t)private_data->lpEcb->cbTotalBytes; + } + e = get_uri_to_worker_ext(uw_map, worker_index); + if (e) { + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Applying service extensions" ); + s->extension.reply_timeout = e->reply_timeout; + s->extension.use_server_error_pages = e->use_server_error_pages; + if (e->activation) { + s->extension.activation = jk_pool_alloc(s->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 = jk_pool_alloc(s->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)); + } + } + + s->uw_map = uw_map; + /* + * Add SSL IIS environment + */ + if (s->is_ssl) { + char *ssl_env_names[9] = { + "CERT_ISSUER", + "CERT_SUBJECT", + "CERT_COOKIE", + "HTTPS_SERVER_SUBJECT", + "CERT_FLAGS", + "HTTPS_SECRETKEYSIZE", + "CERT_SERIALNUMBER", + "HTTPS_SERVER_ISSUER", + "HTTPS_KEYSIZE" + }; + char *ssl_env_values[9] = { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL + }; + unsigned int i; + unsigned int num_of_vars = 0; + + for (i = 0; i < 9; i++) { + GET_SERVER_VARIABLE_VALUE(ssl_env_names[i], ssl_env_values[i]); + if (ssl_env_values[i]) { + num_of_vars++; + } + } + /* XXX: To make the isapi plugin more consistent with the other web servers */ + /* we should also set s->ssl_cipher, s->ssl_session, and s->ssl_key_size. */ + if (num_of_vars) { + unsigned int j; + + s->attributes_names = + jk_pool_alloc(&private_data->p, num_of_vars * sizeof(char *)); + s->attributes_values = + jk_pool_alloc(&private_data->p, num_of_vars * sizeof(char *)); + + j = 0; + for (i = 0; i < 9; i++) { + if (ssl_env_values[i]) { + s->attributes_names[j] = ssl_env_names[i]; + s->attributes_values[j] = ssl_env_values[i]; + j++; + } + } + s->num_attributes = num_of_vars; + if (ssl_env_values[4] && ssl_env_values[4][0] == '1') { + CERT_CONTEXT_EX cc; + cc.cbAllocated = MAX_PACKET_SIZE; + cc.CertContext.pbCertEncoded = (BYTE *) huge_buf; + cc.CertContext.cbCertEncoded = 0; + + if (private_data->lpEcb-> + ServerSupportFunction(private_data->lpEcb->ConnID, + (DWORD) HSE_REQ_GET_CERT_INFO_EX, + (LPVOID) & cc, NULL, + NULL) != FALSE) { + jk_log(logger, JK_LOG_DEBUG, + "Client Certificate encoding:%d sz:%d flags:%ld", + cc.CertContext. + dwCertEncodingType & X509_ASN_ENCODING, + cc.CertContext.cbCertEncoded, + cc.dwCertificateFlags); + s->ssl_cert = + jk_pool_alloc(&private_data->p, + base64_encode_cert_len(cc.CertContext. + cbCertEncoded)); + + s->ssl_cert_len = base64_encode_cert(s->ssl_cert, + huge_buf, + cc.CertContext. + cbCertEncoded) - 1; + } + } + } + } + + huge_buf_sz = MAX_PACKET_SIZE; + if (get_server_value(private_data->lpEcb, +#ifndef USE_CGI_HEADERS + "ALL_RAW", huge_buf, huge_buf_sz)) { +#else + "ALL_HTTP", huge_buf, huge_buf_sz)) { +#endif + unsigned int cnt = 0; + char *tmp; + + for (tmp = huge_buf; *tmp; tmp++) { + if (*tmp == '\n') { + cnt++; + } + } + + if (cnt) { + char *headers_buf = huge_buf; + unsigned int i; + BOOL need_content_length_header = FALSE; + + if (s->content_length == 0 && unknown_content_length == FALSE) { + /* Add content-length=0 only if really zero + */ + need_content_length_header = TRUE; + } + + /* allocate an extra header slot in case we need to add a content-length header */ + s->headers_names = + jk_pool_alloc(&private_data->p, (cnt + 1) * sizeof(char *)); + s->headers_values = + jk_pool_alloc(&private_data->p, (cnt + 1) * sizeof(char *)); + + if (!s->headers_names || !s->headers_values || !headers_buf) { + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + for (i = 0, tmp = headers_buf; *tmp && i < cnt;) { + int real_header = JK_TRUE; + +#ifdef USE_CGI_HEADERS + /* Skip the HTTP_ prefix to the beginning of the header name */ + tmp += HTTP_HEADER_PREFIX_LEN; +#endif + + if (!strnicmp(tmp, URI_HEADER_NAME, strlen(URI_HEADER_NAME)) + || !strnicmp(tmp, WORKER_HEADER_NAME, strlen(WORKER_HEADER_NAME)) + || !strnicmp(tmp, WORKER_HEADER_INDEX, strlen(WORKER_HEADER_INDEX)) + || !strnicmp(tmp, QUERY_HEADER_NAME, strlen(QUERY_HEADER_NAME))) { + /* Skip redirector headers */ + cnt--; + real_header = JK_FALSE; + } + else if (!strnicmp(tmp, CONTENT_LENGTH, + sizeof(CONTENT_LENGTH) - 1)) { + need_content_length_header = FALSE; + + /* If the content-length is unknown + * or larger then 4Gb do not send it. + * IIS can also create a synthetic Content-Length header to make + * lpcbTotalBytes and the CONTENT_LENGTH server variable agree + * on small requests where the entire chunk encoded message is + * read into the available buffer. + */ + if (unknown_content_length || s->is_chunked) { + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Disregarding Content-Length in request - content is %s", + s->is_chunked ? "chunked" : "unknown length"); + } + cnt--; + real_header = JK_FALSE; + } + else { + s->headers_names[i] = tmp; + } + } + else if (!strnicmp(tmp, TOMCAT_TRANSLATE_HEADER_NAME, + strlen(TOMCAT_TRANSLATE_HEADER_NAME))) { + s->headers_names[i] = TRANSLATE_HEADER_NAME_LC; + } + else { + s->headers_names[i] = tmp; + } + + while (':' != *tmp && *tmp) { +#ifdef USE_CGI_HEADERS + if (real_header) { + if ('_' == *tmp) { + *tmp = '-'; + } + else { + *tmp = JK_TOLOWER(*tmp); + } + } +#endif + tmp++; + } + *tmp = '\0'; + tmp++; + + /* Skip all the WS chars after the ':' to the beginning of the header value */ + while (' ' == *tmp || '\t' == *tmp || '\v' == *tmp) { + tmp++; + } + + if (real_header) { + s->headers_values[i] = tmp; + } + + while (*tmp && *tmp != '\n' && *tmp != '\r') { + tmp++; + } + *tmp = '\0'; + tmp++; + + /* skip CR LF */ + while (*tmp == '\n' || *tmp == '\r') { + tmp++; + } + + if (real_header) { + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Forwarding request header %s : %s", + s->headers_names[i], s->headers_values[i]); + } + i++; + } + } + /* 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) { + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Incoming request needs explicit Content-Length: 0 in AJP13"); + } + s->headers_names[cnt] = "Content-Length"; + s->headers_values[cnt] = "0"; + cnt++; + } + s->num_headers = cnt; + } + else { + /* We must have our two headers */ + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + else { + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + /* Dump all connection param so we can trace what's going to + * the remote tomcat + */ + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, + "Service protocol=%s method=%s host=%s addr=%s name=%s port=%d " + "auth=%s user=%s uri=%s", + STRNULL_FOR_NULL(s->protocol), + STRNULL_FOR_NULL(s->method), + 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(s->req_uri)); + jk_log(logger, JK_LOG_DEBUG, + "Service request headers=%d attributes=%d " + "chunked=%s content-length=%" JK_UINT64_T_FMT " available=%u", + s->num_headers, + s->num_attributes, + (s->is_chunked == JK_TRUE) ? "yes" : "no", + s->content_length, + private_data->lpEcb->cbTotalBytes); + } + + JK_TRACE_EXIT(logger); + return JK_TRUE; +} + +static int get_server_value(LPEXTENSION_CONTROL_BLOCK lpEcb, + char *name, char *buf, DWORD bufsz) +{ + DWORD sz = bufsz; + buf[0] = '\0'; + if (!lpEcb->GetServerVariable(lpEcb->ConnID, name, + buf, (LPDWORD) &sz)) + return JK_FALSE; + + if (sz <= bufsz) + buf[sz-1] = '\0'; + return JK_TRUE; +} + +static const char begin_cert[] = "-----BEGIN CERTIFICATE-----\r\n"; + +static const char end_cert[] = "-----END CERTIFICATE-----\r\n"; + +static const char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int base64_encode_cert_len(int len) +{ + int n = ((len + 2) / 3 * 4) + 1; /* base64 encoded size */ + n += (n + 63 / 64) * 2; /* add CRLF's */ + n += sizeof(begin_cert) + sizeof(end_cert) - 2; /* add enclosing strings. */ + return n; +} + +static int base64_encode_cert(char *encoded, + const char *string, int len) +{ + int i, c; + char *p; + const char *t; + + p = encoded; + + t = begin_cert; + while (*t != '\0') + *p++ = *t++; + + c = 0; + for (i = 0; i < len - 2; i += 3) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int)(string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2) | + ((int)(string[i + 2] & 0xC0) >> 6)]; + *p++ = basis_64[string[i + 2] & 0x3F]; + c += 4; + if (c >= 64) { + *p++ = '\r'; + *p++ = '\n'; + c = 0; + } + } + if (i < len) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + if (i == (len - 1)) { + *p++ = basis_64[((string[i] & 0x3) << 4)]; + *p++ = '='; + } + else { + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int)(string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + c++; + } + if (c != 0) { + *p++ = '\r'; + *p++ = '\n'; + } + + t = end_cert; + while (*t != '\0') + *p++ = *t++; + + *p++ = '\0'; + return (int)(p - encoded); +} + +/** +* Determine version info and the primary notification event +*/ +static int get_iis_info(iis_info_t* iis_info) +{ + HKEY hkey; + long rc; + int rv = JK_FALSE; + + iis_info->major = 0; + iis_info->minor = 0; + iis_info->filter_notify_event = SF_NOTIFY_PREPROC_HEADERS; + + /* Retrieve the IIS version Major/Minor */ + rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + W3SVC_REGISTRY_KEY, (DWORD) 0, KEY_READ, &hkey); + if (ERROR_SUCCESS == rc) { + if (get_registry_config_number(hkey, "MajorVersion", &iis_info->major) == JK_TRUE) { +#ifdef AUTOMATIC_AUTH_NOTIFICATION + if (iis_info->major > 4) +#else + if (use_auth_notification_flags && iis_info->major > 4) +#endif + iis_info->filter_notify_event = SF_NOTIFY_AUTH_COMPLETE; + if (get_registry_config_number(hkey, "MinorVersion", &iis_info->minor) == JK_TRUE) { + +#ifdef AUTOMATIC_AUTH_NOTIFICATION + /* SF_NOTIFY_AUTH_COMPLETE causes redirect failures + * (ERROR_INVALID_PARAMETER) on IIS 5.1 with OPTIONS/PUT + * and is only available from IIS 5+ + */ + if (iis_info->major == 5 && iis_info->minor == 1) { + iis_info->filter_notify_event = SF_NOTIFY_PREPROC_HEADERS; + } +#endif + rv = JK_TRUE; + } + } + } + CloseHandle(hkey); + return rv; +} -- cgit 1.2.3-korg