| /* |
| * 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 <shachor@il.ibm.com> * |
| * Author: Larry Isaacs <larryi@apache.org> * |
| * Author: Ignacio J. Ortega <nacho@apache.org> * |
| * Author: Mladen Turk <mturk@apache.org> * |
| ***************************************************************************/ |
| |
| /* |
| * Define WIN32 API we are going to use. |
| */ |
| #ifndef _WIN32_WINNT |
| #define _WIN32_WINNT 0x0502 |
| #endif |
| #include <winsock2.h> |
| #include <httpext.h> |
| #include <httpfilt.h> |
| #include <wininet.h> |
| |
| #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 |
| |
| #ifndef HSE_REQ_SET_FLUSH_FLAG |
| #define HSE_REQ_SET_FLUSH_FLAG (HSE_REQ_END_RESERVED + 43) |
| #endif |
| |
| #include <strsafe.h> |
| |
| #define VERSION_STRING "Tomcat/ISAPI/" JK_EXPOSED_VERSION |
| #define FULL_VERSION_STRING "Tomcat/ISAPI/" JK_FULL_EXPOSED_VERSION |
| #define SHM_DEF_PREFIX "JK_" |
| #define DEFAULT_WORKER_NAME "ajp13" |
| |
| /* |
| * This is default value found inside httpd.conf |
| * for MaxClients |
| */ |
| #define DEFAULT_WORKER_THREADS 250 |
| #define RES_BUFFER_SIZE 64 |
| #define HDR_BUFFER_SIZE 1024 |
| |
| /* |
| * 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:" |
| #define ALL_HEADERS "ALL_RAW" |
| #else |
| #define ALL_HEADERS "ALL_HTTP" |
| #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[RES_BUFFER_SIZE]; |
| static char QUERY_HEADER_NAME[RES_BUFFER_SIZE]; |
| static char WORKER_HEADER_NAME[RES_BUFFER_SIZE]; |
| static char TOMCAT_TRANSLATE_HEADER_NAME[RES_BUFFER_SIZE]; |
| static char WORKER_HEADER_INDEX[RES_BUFFER_SIZE]; |
| |
| /* The variants of the special headers after IIS adds |
| * "HTTP_" to the front of them |
| */ |
| static char HTTP_URI_HEADER_NAME[RES_BUFFER_SIZE]; |
| static char HTTP_QUERY_HEADER_NAME[RES_BUFFER_SIZE]; |
| static char HTTP_WORKER_HEADER_NAME[RES_BUFFER_SIZE]; |
| static char HTTP_WORKER_HEADER_INDEX[RES_BUFFER_SIZE]; |
| |
| #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" |
| #define AUTH_COMPLETE_TAG "auth_complete" |
| #define REJECT_UNSAFE_TAG "reject_unsafe" |
| #define COLLAPSE_SLASHES_TAG "collapse_slashes" |
| #define WATCHDOG_INTERVAL_TAG "watchdog_interval" |
| #define ENABLE_CHUNKED_ENCODING_TAG "enable_chunked_encoding" |
| #define ERROR_PAGE_TAG "error_page" |
| #define FLUSH_PACKETS_TAG "flush_packets" |
| |
| #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 |
| #define NIL "" |
| |
| /* Transfer-Encoding: chunked content trailer */ |
| #define CHUNKED_ENCODING_TRAILER "0\r\n\r\n" |
| #define CHUNKED_ENCODING_TRAILER_LEN 5 |
| |
| #define BAD_REQUEST -1 |
| #define BAD_PATH -2 |
| #define MAX_SERVERNAME 1024 |
| #define MAX_INSTANCEID 32 |
| |
| char HTML_ERROR_HEAD[] = "<!--\n" |
| " Licensed to the Apache Software Foundation (ASF) under one or more\n" |
| " contributor license agreements. See the NOTICE file distributed with\n" |
| " this work for additional information regarding copyright ownership.\n" |
| " The ASF licenses this file to You under the Apache License, Version 2.0\n" |
| " (the \"License\"); you may not use this file except in compliance with\n" |
| " the License. You may obtain a copy of the License at\n\n" |
| " http://www.apache.org/licenses/LICENSE-2.0\n\n" |
| " Unless required by applicable law or agreed to in writing, software\n" |
| " distributed under the License is distributed on an \"AS IS\" BASIS,\n" |
| " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" |
| " See the License for the specific language governing permissions and\n" |
| " limitations under the License.\n" |
| " -->\n" |
| "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\n" |
| "\"http://www.w3c.org/TR/REC-html40/loose.dtd\">\n" |
| "<HTML>\n<HEAD>\n" |
| "<META http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n" |
| "<STYLE TYPE=\"text/css\">\n" |
| " body {\n" |
| " color: #000000;\n" |
| " background-color: #FFFFFF;\n" |
| " font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif;\n" |
| " font-size: 9pt;\n" |
| " margin: 10px 10px;\n" |
| " }\n" |
| " p#footer {\n" |
| " text-align: right;\n" |
| " font-size: 80%;\n" |
| " }\n" |
| "</STYLE>\n"; |
| |
| #define HTML_ERROR_BODY_FMT "<TITLE>%s!</TITLE>\n</HEAD>\n<BODY>\n<H1>%s!</H1>\n<P>\n%s\n</P>\n" |
| |
| char HTML_ERROR_TAIL[] = "<P>\n<BR/> <BR/> <BR/> <BR/> \n" |
| FULL_VERSION_STRING "\n" |
| "<BR/> \n" |
| "<HR/>\n" |
| "<P id=\"footer\">\n" |
| "Copyright © 1999-2019 Apache Software Foundation<BR/>\n" |
| "All Rights Reserved\n" |
| "</P>\n</BODY>\n</HTML>\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 Temporarily 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)") |
| #define JK_TOLOWER(x) ((char)tolower((BYTE)(x))) |
| |
| #define ISIZEOF(X) (int)sizeof(X) |
| |
| #define GET_SERVER_VARIABLE_VALUE(name, place, def) \ |
| do { \ |
| (place) = dup_server_value(private_data->lpEcb, \ |
| (name), \ |
| &private_data->p, \ |
| def); \ |
| } while(0) |
| |
| #define GET_SERVER_VARIABLE_VALUE_INT(name, place, def) \ |
| do { \ |
| char _tb[RES_BUFFER_SIZE]; \ |
| if (get_server_value(private_data->lpEcb, \ |
| (name), \ |
| _tb, \ |
| RES_BUFFER_SIZE)) { \ |
| (place) = atoi(_tb); \ |
| 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 char ini_mutex_name[MAX_PATH]; |
| static int using_ini_file = JK_FALSE; |
| static HANDLE init_cs = NULL; |
| static volatile int is_inited = JK_FALSE; |
| static volatile int is_mapread = JK_FALSE; |
| static BOOL dll_process_detach = 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 *CONTENT_TYPE = "Content-Type:text/html\r\n\r\n"; |
| |
| static char extension_uri[INTERNET_MAX_URL_LENGTH]; |
| 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 shm_loaded_name[MAX_PATH] = {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 int shm_config_size = -1; |
| static int strip_session = 0; |
| static int use_auth_notification_flags = 1; |
| static int chunked_encoding_enabled = JK_FALSE; |
| static int reject_unsafe = 0; |
| static volatile int watchdog_interval = 0; |
| static HANDLE watchdog_handle = NULL; |
| static char error_page_buf[INTERNET_MAX_URL_LENGTH] = {0}; |
| static char *error_page = NULL; |
| static int flush_packets = JK_FALSE; |
| |
| #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 */ |
| char *err_hdrs; |
| 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, size_t 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, size_t sz); |
| |
| static int get_registry_config_number(HKEY hkey, const char *tag, |
| int *val); |
| |
| |
| static BOOL get_server_value(LPEXTENSION_CONTROL_BLOCK lpEcb, |
| char *name, |
| char *buf, size_t bufsz); |
| |
| static char *dup_server_value(LPEXTENSION_CONTROL_BLOCK lpEcb, |
| const char *name, jk_pool_t *p, |
| const char *def); |
| |
| 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 *path_merge(const char *root, const char *path); |
| |
| static int is_path_relative(const char *path); |
| |
| 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; |
| } |
| |
| /* 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 = JK_TOLOWER(*find++)) != 0) { |
| len = strlen(find); |
| do { |
| do { |
| if ((sc = JK_TOLOWER(*s++)) == 0) |
| return (NULL); |
| } while (sc != c); |
| } while (strnicmp(s, find, len) != 0); |
| s--; |
| } |
| return ((char *)s); |
| } |
| |
| static void write_error_response(PHTTP_FILTER_CONTEXT pfc, int err) |
| { |
| char status[1024]; |
| char body[8192] = ""; |
| DWORD len; |
| |
| /* reject !!! */ |
| pfc->AddResponseHeaders(pfc, CONTENT_TYPE, 0); |
| StringCbPrintf(status, sizeof(status), "%d %s", err, status_reason(err)); |
| pfc->ServerSupportFunction(pfc, |
| SF_REQ_SEND_RESPONSE_HEADER, |
| status, 0, 0); |
| len = ISIZEOF(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 = lstrlenA(body); |
| pfc->WriteClient(pfc, body, &len, |
| HSE_IO_SYNC); |
| len = ISIZEOF(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, const char *err_hdrs) |
| { |
| DWORD len; |
| char status[1024]; |
| char body[8192] = ""; |
| |
| if (error_page) { |
| char error_page_url[INTERNET_MAX_URL_LENGTH] = ""; |
| DWORD len_of_error_page; |
| StringCbPrintf(error_page_url, INTERNET_MAX_URL_LENGTH, |
| error_page, err); |
| len_of_error_page = lstrlenA(error_page_url); |
| if (!lpEcb->ServerSupportFunction(lpEcb->ConnID, |
| HSE_REQ_SEND_URL_REDIRECT_RESP, |
| error_page_url, |
| &len_of_error_page, |
| NULL)) { |
| lpEcb->dwHttpStatusCode = err; |
| } |
| else { |
| return; |
| } |
| } |
| lpEcb->dwHttpStatusCode = err; |
| StringCbPrintf(status, sizeof(status), "%d %s", err, status_reason(err)); |
| /* XXX: Should we allow something beside 401? |
| */ |
| if (err_hdrs && err == 401) { |
| /* Include extra error headers */ |
| HRESULT hr = StringCbCopy(body, sizeof(body), err_hdrs); |
| if (FAILED(hr) || strlen(body) > (8191 - strlen(CONTENT_TYPE))) { |
| /* Header is too long. |
| */ |
| jk_log(logger, JK_LOG_WARNING, |
| "error header too long (%d bytes requested).", |
| lstrlenA(err_hdrs)); |
| body[0] = '\0'; |
| } |
| } |
| StringCbCat(body, sizeof(body), CONTENT_TYPE); |
| lpEcb->ServerSupportFunction(lpEcb->ConnID, |
| HSE_REQ_SEND_RESPONSE_HEADER, |
| status, |
| 0, |
| (LPDWORD)body); |
| /* First write the HEAD */ |
| len = ISIZEOF(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 = lstrlenA(body); |
| lpEcb->WriteClient(lpEcb->ConnID, |
| body, &len, |
| HSE_IO_SYNC); |
| len = ISIZEOF(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) |
| { |
| int rv = JK_TRUE; |
| isapi_private_data_t *p; |
| |
| 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 == NULL || s->ws_private == NULL) { |
| JK_LOG_NULL_PARAMS(logger); |
| JK_TRACE_EXIT(logger); |
| return JK_FALSE; |
| } |
| 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")) { |
| p->err_hdrs = jk_pool_strcatv(&p->p, |
| "WWW-Authenticate:", |
| header_values[h], CRLF, NULL); |
| 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 = lstrlenA(status_str); |
| hi.cchHeader = lstrlenA(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 { |
| /* 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, |
| 0, |
| (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; |
| } |
| |
| static int JK_METHOD iis_read(jk_ws_service_t *s, |
| void *b, unsigned int l, unsigned int *a) |
| { |
| isapi_private_data_t *p; |
| |
| JK_TRACE_ENTER(logger); |
| if (s == NULL || s->ws_private == NULL) { |
| JK_LOG_NULL_PARAMS(logger); |
| JK_TRACE_EXIT(logger); |
| return JK_FALSE; |
| } |
| if (a == NULL || b == NULL) { |
| /* XXX: Do we really need those useless checks? |
| */ |
| JK_LOG_NULL_PARAMS(logger); |
| JK_TRACE_EXIT(logger); |
| return JK_FALSE; |
| |
| } |
| 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, &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; |
| } |
| |
| /* |
| * 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) |
| { |
| isapi_private_data_t *p; |
| const char *buf = (const char *)b; |
| |
| JK_TRACE_ENTER(logger); |
| if (!l) { |
| JK_TRACE_EXIT(logger); |
| return JK_TRUE; |
| } |
| if (s == NULL || s->ws_private == NULL || b == NULL) { |
| JK_LOG_NULL_PARAMS(logger); |
| JK_TRACE_EXIT(logger); |
| return JK_FALSE; |
| } |
| p = s->ws_private; |
| |
| 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[RES_BUFFER_SIZE]; |
| |
| /* Construct chunk header : HEX CRLF*/ |
| StringCbPrintf(chunk_header, RES_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, |
| NULL, 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, lstrlenA(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; |
| } |
| |
| /** |
| * 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) |
| { |
| isapi_private_data_t *p; |
| |
| JK_TRACE_ENTER(logger); |
| if (s == NULL || s->ws_private == NULL) { |
| JK_LOG_NULL_PARAMS(logger); |
| JK_TRACE_EXIT(logger); |
| return JK_FALSE; |
| } |
| 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, |
| NULL, 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; |
| } |
| |
| BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) |
| { |
| 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; |
| } |
| WaitForSingleObject(init_cs, INFINITE); |
| if (!is_inited) { |
| rv = initialize_extension(); |
| } |
| ReleaseMutex(init_cs); |
| if (iis_info.major < 5 || (iis_info.major == 5 && iis_info.minor < 1)) { |
| SetLastError(ERROR_OLD_WIN_VERSION); |
| return FALSE; |
| } |
| 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; |
| } |
| |
| |
| /** The max number of regex captures that can be expanded by ap_pregsub */ |
| #define AP_MAX_REG_MATCH 10 |
| |
| /* The structure representing a compiled regular expression. */ |
| typedef struct { |
| void *re_pcre; |
| 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; |
| |
| |
| /************************************************* |
| * 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 |
| |
| Returns: JK_TRUE on success |
| JK_FALSE on failure |
| */ |
| |
| static int ap_regcomp(ap_regex_t *preg, const char *pattern) |
| { |
| const char *errorptr; |
| int erroffset; |
| |
| preg->re_pcre = pcre_compile(pattern, 0, &errorptr, &erroffset, NULL); |
| |
| if (preg->re_pcre == NULL) |
| return JK_FALSE; |
| |
| return JK_TRUE; |
| } |
| |
| /************************************************* |
| * 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 rc; |
| int *ovector = NULL; |
| int small_ovector[POSIX_MALLOC_THRESHOLD * 3]; |
| int allocated_ovector = 0; |
| |
| if (nmatch > 0) { |
| if (nmatch <= POSIX_MALLOC_THRESHOLD) { |
| ovector = &(small_ovector[0]); |
| } |
| else { |
| ovector = (int *)malloc(sizeof(int) * nmatch * 3); |
| if (ovector == NULL) |
| return -1; |
| allocated_ovector = 1; |
| } |
| } |
| |
| rc = pcre_exec((const pcre *)preg->re_pcre, NULL, string, |
| lstrlenA(string), |
| 0, 0, 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]; |
| } |
| for (; i < nmatch; i++) |
| pmatch[i].rm_so = pmatch[i].rm_eo = -1; |
| } |
| if (allocated_ovector) |
| free(ovector); |
| return rc; |
| } |
| |
| /* 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, int nmatch, |
| ap_regmatch_t pmatch[]) |
| { |
| const char *src = input; |
| char *dest, *dst; |
| char c; |
| int 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 char *simple_rewrite(jk_pool_t *p, const char *uri) |
| { |
| if (rewrite_map) { |
| int i; |
| for (i = 0; i < jk_map_size(rewrite_map); i++) { |
| size_t len; |
| const char *src = jk_map_name_at(rewrite_map, i); |
| if (*src == '~') |
| continue; /* Skip regexp rewrites */ |
| len = strlen(src); |
| if (strncmp(uri, src, len) == 0) { |
| return jk_pool_strcat(p, jk_map_value_at(rewrite_map, i), uri + len); |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| static char *rregex_rewrite(jk_pool_t *p, const char *uri) |
| { |
| int nmatch; |
| 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); |
| nmatch = ap_regexec(regexp, uri, AP_MAX_REG_MATCH, regm); |
| if (nmatch > 0) { |
| char *subs; |
| if (nmatch > AP_MAX_REG_MATCH) { |
| nmatch = AP_MAX_REG_MATCH; |
| } |
| subs = ap_pregsub(regexp->fake, uri, nmatch, regm); |
| if (subs) { |
| char *buf, *ptr; |
| size_t orgsz = strlen(uri); |
| size_t subsz = strlen(subs); |
| size_t lefts = orgsz - regm[0].rm_eo; |
| |
| ptr = buf = jk_pool_alloc(p, regm[0].rm_so + subsz + lefts + 1); |
| memcpy(buf, uri, regm[0].rm_so); |
| ptr += regm[0].rm_so; |
| memcpy(ptr, subs, subsz); |
| ptr += subsz; |
| memcpy(ptr, uri + regm[0].rm_eo, lefts); |
| ptr[lefts] = '\0'; |
| return buf; |
| } |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| static __inline void clear_header(PHTTP_FILTER_PREPROC_HEADERS pfp, |
| PHTTP_FILTER_CONTEXT pfc, |
| LPSTR lpszName) |
| { |
| pfp->SetHeader(pfc, lpszName, NIL); |
| } |
| |
| static __inline LPSTR get_pheader(jk_pool_t *pool, |
| PHTTP_FILTER_PREPROC_HEADERS pfp, |
| PHTTP_FILTER_CONTEXT pfc, |
| LPSTR lpszName, LPSTR lpszBuf, size_t bsize) |
| { |
| LPSTR rv = lpszBuf; |
| DWORD dwLen = (DWORD)bsize; |
| |
| if (!pfp->GetHeader(pfc, lpszName, lpszBuf, &dwLen)) { |
| if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) |
| return NULL; |
| if ((rv = jk_pool_alloc(pool, dwLen)) == NULL) |
| return NULL; |
| /* Try again with dynamic buffer */ |
| if (!pfp->GetHeader(pfc, lpszName, rv, &dwLen)) |
| return NULL; |
| } |
| return rv; |
| } |
| |
| static DWORD handle_notify_event(PHTTP_FILTER_CONTEXT pfc, |
| PHTTP_FILTER_PREPROC_HEADERS pfp) |
| { |
| int rc; |
| DWORD rv = SF_STATUS_REQ_NEXT_NOTIFICATION; |
| const char *worker = NULL; |
| rule_extension_t *extensions; |
| int worker_index = -1; |
| int port = 0; |
| jk_pool_atom_t pbuf[HUGE_POOL_SIZE]; |
| jk_pool_t pool; |
| |
| char *uri_undec = NULL; |
| char *cleanuri = NULL; |
| char *uri = NULL; |
| char *host = NULL; |
| char *translate = NULL; |
| char szHB[HDR_BUFFER_SIZE] = "/"; |
| char szUB[HDR_BUFFER_SIZE] = ""; |
| char szTB[16] = ""; |
| char szPB[16] = ""; |
| char swindex[32] = ""; |
| char *query; |
| DWORD len; |
| |
| if (JK_IS_DEBUG_LEVEL(logger)) |
| jk_log(logger, JK_LOG_DEBUG, "Filter started"); |
| jk_open_pool(&pool, pbuf, sizeof(jk_pool_atom_t) * HUGE_POOL_SIZE); |
| /* |
| * Just in case somebody set these headers in the request! |
| */ |
| clear_header(pfp, pfc, URI_HEADER_NAME); |
| clear_header(pfp, pfc, QUERY_HEADER_NAME); |
| clear_header(pfp, pfc, WORKER_HEADER_NAME); |
| clear_header(pfp, pfc, WORKER_HEADER_INDEX); |
| clear_header(pfp, pfc, TOMCAT_TRANSLATE_HEADER_NAME); |
| |
| /* |
| * 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; |
| } |
| uri = get_pheader(&pool, pfp, pfc, "url", szUB, sizeof(szUB)); |
| if (uri == NULL) { |
| jk_log(logger, JK_LOG_ERROR, |
| "error while getting the url"); |
| return SF_STATUS_REQ_ERROR; |
| } |
| if (*uri == '\0') { |
| /* Empty url */ |
| return SF_STATUS_REQ_NEXT_NOTIFICATION; |
| } |
| query = strchr(uri, '?'); |
| if (query) { |
| *query++ = '\0'; |
| if (*query) |
| query = jk_pool_strdup(&pool, query); |
| else |
| query = NULL; |
| } |
| |
| /* Duplicate unparsed uri */ |
| uri_undec = jk_pool_strdup(&pool, uri); |
| |
| 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); |
| rv = SF_STATUS_REQ_FINISHED; |
| goto cleanup; |
| } |
| if (rc == BAD_PATH) { |
| jk_log(logger, JK_LOG_EMERG, |
| "[%s] contains forbidden escape sequences.", |
| uri); |
| write_error_response(pfc, 404); |
| rv = SF_STATUS_REQ_FINISHED; |
| goto cleanup; |
| } |
| cleanuri = jk_pool_strdup(&pool, uri); |
| if (jk_servlet_normalize(cleanuri, logger)) { |
| write_error_response(pfc, 404); |
| rv = SF_STATUS_REQ_FINISHED; |
| goto cleanup; |
| } |
| len = ISIZEOF(szHB) - 1; |
| if (pfc->GetServerVariable(pfc, "SERVER_NAME", &szHB[1], &len) && len > 1) { |
| len = ISIZEOF(szPB); |
| if (pfc->GetServerVariable(pfc, "SERVER_PORT", szPB, &len)) |
| port = atoi(szPB); |
| if (port != 0 && port != 80 && port != 443) { |
| host = jk_pool_strcatv(&pool, szHB, ":", szPB, NULL); |
| } |
| else |
| host = szHB; |
| } |
| worker = map_uri_to_worker_ext(uw_map, cleanuri, host, |
| &extensions, &worker_index, logger); |
| /* |
| * Check if somebody is feading us with his own TOMCAT data headers. |
| * We reject such postings ! |
| */ |
| if (worker) { |
| char *forwardURI; |
| char *rewriteURI; |
| isapi_log_data_t *ld; |
| BOOL rs; |
| |
| /* 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 */ |
| forwardURI = uri_undec; |
| } |
| else if (uri_select_option == URI_SELECT_OPT_ESCAPED) { |
| size_t elen = strlen(uri) * 3 + 1; |
| char *escuri = jk_pool_alloc(&pool, elen); |
| if (!escape_url(uri, escuri, (int)elen)) { |
| jk_log(logger, JK_LOG_ERROR, |
| "[%s] re-encoding request exceeds maximum buffer size.", |
| uri); |
| write_error_response(pfc, 400); |
| rv = SF_STATUS_REQ_FINISHED; |
| goto cleanup; |
| } |
| if (JK_IS_DEBUG_LEVEL(logger)) |
| jk_log(logger, JK_LOG_DEBUG, |
| "fowarding escaped URI [%s]", |
| escuri); |
| forwardURI = escuri; |
| } |
| else if (uri_select_option == URI_SELECT_OPT_PROXY) { |
| size_t elen = strlen(uri) * 3 + 1; |
| char *escuri = jk_pool_alloc(&pool, elen); |
| if (!jk_canonenc(uri, escuri, (int)elen)) { |
| jk_log(logger, JK_LOG_ERROR, |
| "[%s] re-encoding request exceeds maximum buffer size.", |
| uri); |
| write_error_response(pfc, 400); |
| rv = SF_STATUS_REQ_FINISHED; |
| goto cleanup; |
| } |
| if (JK_IS_DEBUG_LEVEL(logger)) |
| jk_log(logger, JK_LOG_DEBUG, |
| "fowarding escaped URI [%s]", |
| escuri); |
| forwardURI = escuri; |
| } |
| 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. |
| */ |
| rewriteURI = simple_rewrite(&pool, forwardURI); |
| if (rewriteURI == NULL) |
| rewriteURI = rregex_rewrite(&pool, forwardURI); |
| if (rewriteURI) { |
| if (JK_IS_DEBUG_LEVEL(logger)) { |
| jk_log(logger, JK_LOG_DEBUG, |
| "rewritten URI [%s]->[%s]", |
| forwardURI, rewriteURI); |
| } |
| forwardURI = rewriteURI; |
| } |
| itoa(worker_index, swindex, 10); |
| rs = pfp->AddHeader(pfc, URI_HEADER_NAME, forwardURI); |
| if (rs && query) |
| rs = pfp->AddHeader(pfc, QUERY_HEADER_NAME, query); |
| rs = rs && pfp->AddHeader(pfc, WORKER_HEADER_NAME, (LPSTR)worker); |
| rs = rs && pfp->AddHeader(pfc, WORKER_HEADER_INDEX, swindex); |
| rs = rs && pfp->SetHeader(pfc, "url", extension_uri); |
| |
| if (!rs) { |
| jk_log(logger, JK_LOG_ERROR, |
| "error while adding request headers"); |
| SetLastError(ERROR_INVALID_PARAMETER); |
| rv = SF_STATUS_REQ_ERROR; |
| goto cleanup; |
| } |
| translate = get_pheader(&pool, pfp, pfc, TRANSLATE_HEADER, |
| szTB, sizeof(szTB)); |
| /* Move Translate: header to a temporary header so |
| * that the extension proc will be called. |
| * This allows the servlet to handle 'Translate: f'. |
| */ |
| if (translate && &translate) { |
| if (!pfp->AddHeader(pfc, TOMCAT_TRANSLATE_HEADER_NAME, translate)) { |
| jk_log(logger, JK_LOG_ERROR, |
| "error while adding Tomcat-Translate headers"); |
| rv = SF_STATUS_REQ_ERROR; |
| goto cleanup; |
| } |
| clear_header(pfp, pfc, TRANSLATE_HEADER); |
| } |
| ld = (isapi_log_data_t *)pfc->pFilterContext; |
| if (ld == NULL) { |
| ld = (isapi_log_data_t *)pfc->AllocMem(pfc, sizeof(isapi_log_data_t), 0); |
| if (ld == NULL) { |
| jk_log(logger, JK_LOG_ERROR, |
| "error while allocating memory"); |
| SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
| rv = SF_STATUS_REQ_ERROR; |
| goto cleanup; |
| } |
| pfc->pFilterContext = ld; |
| } |
| memset(ld, 0, sizeof(isapi_log_data_t)); |
| StringCbCopy(ld->uri, sizeof(ld->uri), forwardURI); |
| if (query) |
| StringCbCopy(ld->query, sizeof(ld->query), query); |
| ld->request_matched = JK_TRUE; |
| if (JK_IS_DEBUG_LEVEL(logger)) { |
| jk_log(logger, JK_LOG_DEBUG, |
| "forwarding to : %s", extension_uri); |
| jk_log(logger, JK_LOG_DEBUG, |
| "forward URI : %s%s", URI_HEADER_NAME, forwardURI); |
| if (query) |
| jk_log(logger, JK_LOG_DEBUG, |
| "forward query : %s%s", QUERY_HEADER_NAME, query); |
| jk_log(logger, JK_LOG_DEBUG, |
| "forward worker: %s%s", WORKER_HEADER_NAME, worker); |
| jk_log(logger, JK_LOG_DEBUG, |
| "worker index : %s%s", WORKER_HEADER_INDEX, swindex); |
| } |
| } |
| else { |
| if (JK_IS_DEBUG_LEVEL(logger)) |
| jk_log(logger, JK_LOG_DEBUG, "[%s] is not a servlet url", uri_undec); |
| if (strip_session) { |
| if (jk_strip_session_id(uri_undec, JK_PATH_SESSION_IDENTIFIER, logger)) { |
| pfp->SetHeader(pfc, "url", uri_undec); |
| } |
| } |
| } |
| cleanup: |
| jk_close_pool(&pool); |
| return rv; |
| } |
| |
| DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, |
| DWORD dwNotificationType, LPVOID pvNotification) |
| { |
| /* Initialise jk */ |
| EnterCriticalSection(&log_cs); |
| 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)) { |
| dwLen = MAX_INSTANCEID; |
| if (pfc->GetServerVariable(pfc, "INSTANCE_ID", instanceId, &dwLen)) { |
| if (dwLen > 1) { |
| StringCbCat(serverName, MAX_SERVERNAME, "_"); |
| StringCbCat(serverName, MAX_SERVERNAME, instanceId); |
| } |
| } |
| if (!is_mapread) { |
| WaitForSingleObject(init_cs, INFINITE); |
| if (!is_mapread) |
| is_mapread = init_jk(serverName); |
| ReleaseMutex(init_cs); |
| } |
| } |
| /* If we can't read the map we become dormant */ |
| if (!is_mapread) |
| is_inited = JK_FALSE; |
| } |
| LeaveCriticalSection(&log_cs); |
| 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) { |
| return handle_notify_event(pfc, pvNotification); |
| } |
| 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) |
| { |
| BOOL rv = TRUE; |
| |
| pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR); |
| |
| StringCbCopy(pVer->lpszExtensionDesc, HSE_MAX_EXT_DLL_NAME_LEN, (VERSION_STRING)); |
| |
| |
| WaitForSingleObject(init_cs, INFINITE); |
| if (!is_inited) { |
| rv = initialize_extension(); |
| } |
| ReleaseMutex(init_cs); |
| |
| return rv; |
| } |
| |
| DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpEcb) |
| { |
| DWORD rc = HSE_STATUS_ERROR; |
| isapi_private_data_t private_data; |
| jk_ws_service_t s; |
| jk_pool_atom_t buf[SMALL_POOL_SIZE]; |
| char *worker_name; |
| |
| lpEcb->dwHttpStatusCode = HTTP_STATUS_SERVER_ERROR; |
| |
| JK_TRACE_ENTER(logger); |
| |
| /* Initialise jk */ |
| EnterCriticalSection(&log_cs); |
| 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)) { |
| dwLen = MAX_INSTANCEID; |
| if (lpEcb->GetServerVariable(lpEcb->ConnID, "INSTANCE_ID", |
| instanceId, &dwLen)) { |
| if (dwLen > 1) { |
| StringCbCat(serverName, MAX_SERVERNAME, "_"); |
| StringCbCat(serverName, MAX_SERVERNAME, instanceId); |
| } |
| } |
| if (!is_mapread) { |
| WaitForSingleObject(init_cs, INFINITE); |
| if (!is_mapread) |
| is_mapread = init_jk(serverName); |
| ReleaseMutex(init_cs); |
| } |
| } |
| if (!is_mapread) |
| is_inited = JK_FALSE; |
| } |
| LeaveCriticalSection(&log_cs); |
| |
| if (!is_inited) { |
| jk_log(logger, JK_LOG_ERROR, "not initialized"); |
| JK_TRACE_EXIT(logger); |
| return HSE_STATUS_ERROR; |
| } |
| |
| 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 (flush_packets) { |
| lpEcb->ServerSupportFunction(lpEcb->ConnID, HSE_REQ_SET_FLUSH_FLAG, |
| (LPVOID) TRUE, NULL, NULL); |
| } |
| |
| if (init_ws_service(&private_data, &s, &worker_name)) { |
| jk_endpoint_t *e = NULL; |
| jk_worker_t *worker = wc_get_worker_for_name(worker_name, logger); |
| |
| if (!worker) { |
| jk_log(logger, JK_LOG_ERROR, |
| "could not get a worker for name %s", |
| worker_name); |
| jk_close_pool(&private_data.p); |
| JK_TRACE_EXIT(logger); |
| return rc; |
| } |
| if (JK_IS_DEBUG_LEVEL(logger)) |
| jk_log(logger, JK_LOG_DEBUG, |
| "got a worker for name %s", worker_name); |
| 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, |
| private_data.err_hdrs); |
| } |
| 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, private_data.err_hdrs); |
| } |
| e->done(&e, logger); |
| } |
| else { |
| int is_error = JK_HTTP_SERVER_BUSY; |
| 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!"); |
| lpEcb->dwHttpStatusCode = is_error; |
| write_error_message(lpEcb, is_error, private_data.err_hdrs); |
| } |
| } |
| else { |
| jk_log(logger, JK_LOG_ERROR, |
| "failed to init service for request."); |
| } |
| jk_close_pool(&private_data.p); |
| JK_TRACE_EXIT(logger); |
| |
| return rc; |
| } |
| |
| BOOL WINAPI TerminateExtension(DWORD dwFlags) |
| { |
| return TerminateFilter(dwFlags); |
| } |
| |
| BOOL WINAPI TerminateFilter(DWORD dwFlags) |
| { |
| UNREFERENCED_PARAMETER(dwFlags); |
| |
| if (dll_process_detach) { |
| /* Simple case */ |
| if (!is_inited) |
| return TRUE; |
| watchdog_interval = 0; |
| if (watchdog_handle) { |
| WaitForSingleObject(watchdog_handle, INFINITE); |
| CloseHandle(watchdog_handle); |
| watchdog_handle = NULL; |
| } |
| jk_shm_close(logger); |
| if (logger) |
| jk_close_file_logger(&logger); |
| return TRUE; |
| } |
| EnterCriticalSection(&log_cs); |
| if (!is_inited) { |
| LeaveCriticalSection(&log_cs); |
| return TRUE; |
| } |
| WaitForSingleObject(init_cs, INFINITE); |
| if (is_inited) { |
| is_inited = JK_FALSE; |
| jk_log(logger, JK_LOG_INFO, "%s stopping", (FULL_VERSION_STRING)); |
| watchdog_interval = 0; |
| if (watchdog_handle) { |
| WaitForSingleObject(watchdog_handle, INFINITE); |
| CloseHandle(watchdog_handle); |
| watchdog_handle = NULL; |
| } |
| wc_close(logger); |
| 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); |
| } |
| jk_shm_close(logger); |
| if (logger) { |
| jk_close_file_logger(&logger); |
| } |
| } |
| ReleaseMutex(init_cs); |
| LeaveCriticalSection(&log_cs); |
| 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 */ |
| { |
| char fname[MAX_PATH]; |
| UNREFERENCED_PARAMETER(lpReserved); |
| |
| switch (ulReason) { |
| case DLL_PROCESS_ATTACH: |
| if (GetModuleFileName(hInst, fname, sizeof(fname) - 12)) { |
| char *p = strrchr(fname, '.'); |
| if (p) { |
| *p = '\0'; |
| StringCbCopy(ini_file_name, MAX_PATH, fname); |
| StringCbCat(ini_file_name, MAX_PATH, ".properties"); |
| StringCbCopy(ini_mutex_name, MAX_PATH, "Local\\JK_"); |
| StringCbCat(ini_mutex_name, MAX_PATH, fname); |
| StringCbCat(ini_mutex_name, MAX_PATH, "_mutex"); |
| p = &ini_mutex_name[10]; |
| while (*p) { |
| if (!isalnum((unsigned char)*p)) |
| *p = '_'; |
| else |
| *p = toupper(*p); |
| p++; |
| } |
| } |
| else { |
| /* Cannot obtain file name ? */ |
| return 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 ? */ |
| return JK_FALSE; |
| } |
| } |
| else { |
| return JK_FALSE; |
| } |
| /* Construct redirector headers to use for this redirector instance */ |
| StringCbPrintf(URI_HEADER_NAME, RES_BUFFER_SIZE, HEADER_TEMPLATE, URI_HEADER_NAME_BASE, hInst); |
| StringCbPrintf(QUERY_HEADER_NAME, RES_BUFFER_SIZE, HEADER_TEMPLATE, QUERY_HEADER_NAME_BASE, hInst); |
| StringCbPrintf(WORKER_HEADER_NAME, RES_BUFFER_SIZE, HEADER_TEMPLATE, WORKER_HEADER_NAME_BASE, hInst); |
| StringCbPrintf(WORKER_HEADER_INDEX, RES_BUFFER_SIZE, HEADER_TEMPLATE, WORKER_HEADER_INDEX_BASE, hInst); |
| StringCbPrintf(TOMCAT_TRANSLATE_HEADER_NAME, RES_BUFFER_SIZE, HEADER_TEMPLATE, TOMCAT_TRANSLATE_HEADER_NAME_BASE, hInst); |
| |
| /* Construct the HTTP_ headers that will be seen in ExtensionProc */ |
| StringCbPrintf(HTTP_URI_HEADER_NAME, RES_BUFFER_SIZE, HTTP_HEADER_TEMPLATE, URI_HEADER_NAME_BASE, hInst); |
| StringCbPrintf(HTTP_QUERY_HEADER_NAME, RES_BUFFER_SIZE, HTTP_HEADER_TEMPLATE, QUERY_HEADER_NAME_BASE, hInst); |
| StringCbPrintf(HTTP_WORKER_HEADER_NAME, RES_BUFFER_SIZE, HTTP_HEADER_TEMPLATE, WORKER_HEADER_NAME_BASE, hInst); |
| StringCbPrintf(HTTP_WORKER_HEADER_INDEX, RES_BUFFER_SIZE, HTTP_HEADER_TEMPLATE, WORKER_HEADER_INDEX_BASE, hInst); |
| |
| InitializeCriticalSection(&log_cs); |
| init_cs = CreateMutex(jk_get_sa_with_null_dacl(), FALSE, ini_mutex_name); |
| if (init_cs == NULL && GetLastError() == ERROR_ALREADY_EXISTS) |
| init_cs = OpenMutex(MUTEX_ALL_ACCESS, FALSE, ini_mutex_name); |
| if (init_cs == NULL) |
| return JK_FALSE; |
| break; |
| case DLL_PROCESS_DETACH: |
| dll_process_detach = TRUE; |
| __try { |
| TerminateFilter(HSE_TERM_MUST_UNLOAD); |
| } |
| __except(1) { |
| } |
| if (init_cs != NULL) |
| CloseHandle(init_cs); |
| DeleteCriticalSection(&log_cs); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return TRUE; |
| } |
| |
| 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) |
| { |
| 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]; |
| jk_logger_t *org = NULL; |
| |
| /* 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 % format patterns in the log file name, |
| * treat it as a strftime 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\n", log_file_name); |
| fflush(lf); |
| org = 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 = org; |
| org = NULL; |
| rc = JK_FALSE; |
| } |
| } |
| if (org) |
| jk_close_file_logger(&org); |
| 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(void) |
| { |
| 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 && logger) { |
| LARGE_INTEGER filesize; |
| HANDLE h = (HANDLE)_get_osfhandle(fileno(((jk_file_logger_t *)(logger)->logger_private)->logfile)); |
| GetFileSizeEx(h, &filesize); |
| |
| if ((ULONGLONG)filesize.QuadPart >= log_filesize) { |
| rotate = JK_TRUE; |
| } |
| } |
| if (rotate) { |
| rc = init_logger(JK_TRUE); |
| } |
| 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) { |
| #if 0 |
| what[used++] = '\r'; |
| #endif |
| what[used++] = '\n'; |
| what[used] = '\0'; |
| |
| /* Perform logging within critical section to protect rotation */ |
| EnterCriticalSection(&log_cs); |
| if (rotate_log_file() && logger) { |
| /* The rotation process will reallocate the jk_logger_t structure, so refetch */ |
| FILE *rotated = ((jk_file_logger_t *)logger->logger_private)->logfile; |
| fputs(what, rotated); |
| fflush(rotated); |
| } |
| LeaveCriticalSection(&log_cs); |
| } |
| } |
| return rc; |
| } |
| |
| static int init_jk(char *serverName) |
| { |
| char *p, shm_name[MAX_PATH]; |
| int i, rc = JK_FALSE; |
| |
| init_logger(JK_FALSE); |
| /* TODO: Use System logging to notify the user that |
| * we cannot open the configured log file. |
| */ |
| StringCbCopy(shm_name, MAX_PATH, SHM_DEF_PREFIX); |
| jk_log(logger, JK_LOG_INFO, "Starting " FULL_VERSION_STRING); |
| StringCbCat(shm_name, MAX_PATH, serverName); |
| StringCbCat(shm_name, MAX_PATH, "_"); |
| StringCbCat(shm_name, MAX_PATH, extension_uri + 1); |
| if ((p = strrchr(shm_name, '.'))) |
| *p = '\0'; |
| 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)) { |
| 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 */ |
| src++; |
| regexp->fake = val; |
| if (ap_regcomp(regexp, src) == JK_TRUE) { |
| jk_map_add(rregexp_map, src, regexp); |
| if (JK_IS_DEBUG_LEVEL(logger)) { |
| jk_log(logger, JK_LOG_DEBUG, |
| "Added regular expression rule %s -> %s", |
| src, regexp->fake); |
| } |
| } |
| else { |
| jk_log(logger, JK_LOG_ERROR, |
| "Unable to compile regular expression %s", |
| src); |
| 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 = -1; |
| |
| /* 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 == -1) { |
| shm_config_size = jk_shm_calculate_size(workers_map, logger); |
| } |
| else if (shm_config_size > 0) { |
| 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 ((shm_config_size > 0) && |
| (rv = jk_shm_open(shm_name, shm_config_size, logger)) != 0) { |
| jk_log(logger, JK_LOG_ERROR, |
| "Initializing shm:%s errno=%d. Load balancing workers will not function properly", |
| jk_shm_name(), rv); |
| } |
| if (rv != 0 && (rv = jk_shm_open(NULL, shm_config_size, logger)) != 0) { |
| /* Do not try to open the worker if we cannot create |
| * the shared memory segment or heap memory. |
| */ |
| jk_log(logger, JK_LOG_EMERG, |
| "Initializing shm:%s errno=%d. Cannot continue", |
| jk_shm_name(), rv); |
| jk_map_free(&workers_map); |
| workers_map = NULL; |
| return JK_FALSE; |
| } |
| else { |
| if (shm_loaded_name[0]) { |
| if (strcmp(shm_loaded_name, shm_name)) { |
| jk_log(logger, JK_LOG_WARNING, |
| "Loading different shared memory %s. Already loaded %s", |
| shm_name, shm_loaded_name); |
| } |
| } |
| else { |
| StringCbCopy(shm_loaded_name, MAX_PATH, shm_name); |
| } |
| } |
| 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_shm_close(logger); |
| } |
| } |
| else { |
| jk_log(logger, JK_LOG_EMERG, |
| "Unable to read worker file %s. (errno=%d, err=%s)", worker_file, errno, strerror(errno)); |
| } |
| 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"); |
| } |
| else { |
| if (use_auth_notification_flags) |
| iis_info.filter_notify_event = SF_NOTIFY_AUTH_COMPLETE; |
| else |
| iis_info.filter_notify_event = SF_NOTIFY_PREPROC_HEADERS; |
| } |
| is_inited = JK_TRUE; |
| } |
| return is_inited; |
| } |
| |
| int parse_uri_select(const char *uri_select) |
| { |
| if (!strcasecmp(uri_select, URI_SELECT_PARSED_VERB)) |
| return URI_SELECT_OPT_PARSED; |
| if (!strcasecmp(uri_select, URI_SELECT_UNPARSED_VERB)) |
| return URI_SELECT_OPT_UNPARSED; |
| if (!strcasecmp(uri_select, URI_SELECT_ESCAPED_VERB)) |
| return URI_SELECT_OPT_ESCAPED; |
| if (!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_FALSE; |
| LPVOID src; |
| HKEY hkey; |
| int remain; |
| jk_map_t *map = NULL; |
| |
| remain = jk_check_buffer_size(); |
| if (remain < 0) { |
| jk_log(logger, JK_LOG_ERROR, |
| "mod_jk: JK_MAX_ATTRIBUTE_NAME_LEN in jk_util.c is too small, " |
| "increase by %d", -1 * remain); |
| } |
| 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; |
| } |
| } |
| if (!get_config_parameter(src, JK_LOG_FILE_TAG, log_file, sizeof(log_file))) |
| goto cleanup; |
| if (is_path_relative(log_file)) { |
| char *fp = path_merge(dll_file_path, log_file); |
| if (fp) { |
| StringCbCopy(log_file, sizeof(log_file), fp); |
| free(fp); |
| } |
| } |
| 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))) { |
| int tl = lstrlenA(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); |
| } |
| } |
| |
| if (!get_config_parameter(src, EXTENSION_URI_TAG, extension_uri, sizeof(extension_uri))) |
| goto cleanup; |
| if (!get_config_parameter(src, JK_WORKER_FILE_TAG, worker_file, sizeof(worker_file))) |
| goto cleanup; |
| if (is_path_relative(worker_file)) { |
| char *fp = path_merge(dll_file_path, worker_file); |
| if (fp) { |
| StringCbCopy(worker_file, sizeof(worker_file), fp); |
| free(fp); |
| } |
| } |
| if (!get_config_parameter(src, JK_MOUNT_FILE_TAG, worker_mount_file, sizeof(worker_mount_file))) |
| goto cleanup; |
| if (is_path_relative(worker_mount_file)) { |
| char *fp = path_merge(dll_file_path, worker_mount_file); |
| if (fp) { |
| StringCbCopy(worker_mount_file, sizeof(worker_mount_file), fp); |
| free(fp); |
| } |
| } |
| if (get_config_parameter(src, URI_REWRITE_TAG, rewrite_rule_file, sizeof(rewrite_rule_file))) { |
| if (is_path_relative(rewrite_rule_file)) { |
| char *fp = path_merge(dll_file_path, rewrite_rule_file); |
| if (fp) { |
| StringCbCopy(rewrite_rule_file, sizeof(rewrite_rule_file), fp); |
| free(fp); |
| } |
| } |
| } |
| 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 { |
| jk_log(logger, JK_LOG_ERROR, "Invalid value '%s' for configuration item '" |
| URI_SELECT_TAG "'", tmpbuf); |
| } |
| } |
| if (get_config_parameter(src, COLLAPSE_SLASHES_TAG, tmpbuf, sizeof(tmpbuf))) { |
| jk_log(logger, JK_LOG_ERROR, "Configuration item '" COLLAPSE_SLASHES_TAG |
| "' is deprecated and will be ignored"); |
| } |
| shm_config_size = get_config_int(src, SHM_SIZE_TAG, -1); |
| 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); |
| use_auth_notification_flags = get_config_int(src, AUTH_COMPLETE_TAG, 1); |
| 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); |
| flush_packets = get_config_bool(src, FLUSH_PACKETS_TAG, JK_FALSE); |
| if (get_config_parameter(src, ERROR_PAGE_TAG, error_page_buf, sizeof(error_page_buf))) { |
| error_page = error_page_buf; |
| } |
| ok = JK_TRUE; |
| cleanup: |
| 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, size_t 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(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(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, size_t sz) |
| { |
| DWORD type = 0; |
| LONG lrc; |
| DWORD ssz = (DWORD)sz -1; |
| |
| b[ssz] = '\0'; /* Null terminate in case RegQueryValueEx doesn't */ |
| lrc = RegQueryValueEx(hkey, tag, NULL, &type, (LPBYTE)b, &ssz); |
| 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, NULL, &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 *all_headers; |
| int worker_index = -1; |
| rule_extension_t *e; |
| char temp_buf[64]; |
| BOOL unknown_content_length = FALSE; |
| unsigned int cnt = 0; |
| char *tmp; |
| |
| |
| JK_TRACE_ENTER(logger); |
| |
| s->start_response = start_response; |
| s->read = iis_read; |
| s->write = iis_write; |
| s->done = iis_done; |
| |
| GET_SERVER_VARIABLE_VALUE(HTTP_URI_HEADER_NAME, s->req_uri, NULL); |
| |
| 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; |
| } |
| jk_servlet_normalize(s->req_uri, logger); |
| } else { |
| GET_SERVER_VARIABLE_VALUE(HTTP_QUERY_HEADER_NAME, s->query_string, ""); |
| GET_SERVER_VARIABLE_VALUE(HTTP_WORKER_HEADER_NAME, (*worker_name), ""); |
| 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); |
| } |
| |
| GET_SERVER_VARIABLE_VALUE("AUTH_TYPE", s->auth_type, NULL); |
| GET_SERVER_VARIABLE_VALUE("REMOTE_USER", s->remote_user, NULL); |
| 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("LOCAL_ADDR", s->local_addr, ""); |
| 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, |
| 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.sticky_ignore = e->sticky_ignore; |
| s->extension.stateless = e->stateless; |
| 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)); |
| } |
| if (e->session_cookie) { |
| s->extension.session_cookie = jk_pool_strdup(s->pool, e->session_cookie); |
| } |
| if (e->session_path) { |
| s->extension.session_path = jk_pool_strdup(s->pool, e->session_path); |
| } |
| if (e->set_session_cookie) { |
| s->extension.set_session_cookie = e->set_session_cookie; |
| } |
| if (e->session_cookie_path) { |
| s->extension.session_cookie_path = jk_pool_strdup(s->pool, e->session_cookie_path); |
| } |
| |
| } |
| |
| 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], NULL); |
| 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 = 0; |
| |
| 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 *)); |
| if (!s->attributes_names || !s->attributes_values) { |
| JK_TRACE_EXIT(logger); |
| return JK_FALSE; |
| } |
| 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; |
| BYTE *cb = jk_pool_alloc(&private_data->p, AJP13_MAX_PACKET_SIZE); |
| |
| if (!cb) { |
| JK_TRACE_EXIT(logger); |
| return JK_FALSE; |
| } |
| cc.cbAllocated = AJP13_MAX_PACKET_SIZE; |
| cc.CertContext.pbCertEncoded = cb; |
| cc.CertContext.cbCertEncoded = 0; |
| |
| if (private_data->lpEcb->ServerSupportFunction(private_data->lpEcb->ConnID, |
| HSE_REQ_GET_CERT_INFO_EX, |
| &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, cb, |
| cc.CertContext.cbCertEncoded) - 1; |
| } |
| } |
| } |
| } |
| |
| GET_SERVER_VARIABLE_VALUE(ALL_HEADERS, all_headers, NULL); |
| if (!all_headers) { |
| JK_TRACE_EXIT(logger); |
| return JK_FALSE; |
| } |
| for (tmp = all_headers; *tmp; tmp++) { |
| if (*tmp == '\n') { |
| cnt++; |
| } |
| } |
| |
| if (cnt) { |
| char *headers_buf = all_headers; |
| 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) { |
| 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'; |
| |
| /* 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'; |
| /* 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 { |
| 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 BOOL get_server_value(LPEXTENSION_CONTROL_BLOCK lpEcb, |
| char *name, char *buf, size_t bufsz) |
| { |
| DWORD sz = (DWORD)bufsz; |
| |
| buf[0] = '\0'; |
| return lpEcb->GetServerVariable(lpEcb->ConnID, name, |
| buf, &sz); |
| } |
| |
| static char *dup_server_value(LPEXTENSION_CONTROL_BLOCK lpEcb, |
| const char *name, jk_pool_t *p, |
| const char *def) |
| { |
| DWORD sz = HDR_BUFFER_SIZE; |
| char buf[HDR_BUFFER_SIZE]; |
| char *dp; |
| |
| if (lpEcb->GetServerVariable(lpEcb->ConnID, (LPSTR)name, buf, &sz)) { |
| if (sz <= 1) { |
| return def == NULL ? NULL : jk_pool_strdup(p, def); |
| } |
| return jk_pool_strdup(p, buf); |
| } |
| if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) |
| return NULL; |
| if ((dp = jk_pool_alloc(p, sz))) { |
| if (lpEcb->GetServerVariable(lpEcb->ConnID, (LPSTR)name, dp, &sz)) |
| return dp; |
| } |
| return NULL; |
| } |
| |
| 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; |
| int rv = JK_FALSE; |
| |
| iis_info->major = 0; |
| iis_info->minor = 0; |
| |
| /* Retrieve the IIS version Major/Minor */ |
| if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, W3SVC_REGISTRY_KEY, |
| 0, KEY_READ, &hkey) == ERROR_SUCCESS) { |
| if (get_registry_config_number(hkey, "MajorVersion", &iis_info->major) == JK_TRUE && |
| get_registry_config_number(hkey, "MinorVersion", &iis_info->minor) == JK_TRUE) { |
| rv = JK_TRUE; |
| } |
| CloseHandle(hkey); |
| } |
| return rv; |
| } |
| |
| /* |
| * 1. Remove //?/ prefix |
| * 2. Replace UNC/ with // |
| */ |
| static __inline char *NO2UNC(char *fname) |
| { |
| if (fname[0] == '/' && fname[1] == '/' && |
| fname[2] == '?' && fname[3] == '/') { |
| fname += 4; |
| if (fname[0] == 'U' && fname[1] == 'N' && |
| fname[2] == 'C' && fname[3] == '/') { |
| fname += 2; |
| *fname = '/'; |
| } |
| else if (fname[0] == 'U' && fname[1] == 'N' && |
| fname[2] == '/' && fname[3] == '/') { |
| fname += 2; |
| /* Already modified in-place. |
| */ |
| fname += 2; |
| } |
| } |
| return fname; |
| } |
| |
| static __inline void FS2BSA(char *str) |
| { |
| for (; *str != 0; str++) { |
| if (*str == '/') |
| *str = '\\'; |
| } |
| } |
| |
| static __inline void BS2FSA(char *str) |
| { |
| for (; *str != 0; str++) { |
| if (*str == '\\') |
| *str = '/'; |
| } |
| } |
| |
| #define NON_UNC_PATH_LENGTH 248 |
| |
| #define IS_PATH_SEP(C) ((C) == '/' || (C) == '\0') |
| #define IS_DRIVE_CHAR(C) (((C) >= 'A' && (C) <= 'Z') || \ |
| ((C) >= 'a' && (C) <= 'z')) |
| #define _INS_TRAILING_PATH_SEP(s, l) \ |
| if (l > 0 && s[l - 1] != '/') s[l++] = '/', s[l] = '\0' |
| #define _DEL_TRAILING_PATH_SEP(s, l) \ |
| do { \ |
| if (l > 1 && s[l - 1] == '/') { \ |
| if (s[l - 2] != '/' && s[l - 2] != ':') \ |
| s[--l] = '\0'; \ |
| } \ |
| } while(0) |
| |
| static char *skip_prefix(char *path, char **sp) |
| { |
| size_t size; |
| char *cp; |
| |
| /* Convert everything to forward slashes |
| */ |
| BS2FSA(path); |
| /* Remove \\?\ and replace \\?\UNC\ with \\ |
| */ |
| path = NO2UNC(path); |
| size = strlen(path); |
| *sp = path; |
| if (size < 2) { |
| if (path[0] == ' ') { |
| /* Single Trailing space is invalid path |
| */ |
| SetLastError(ERROR_BAD_PATHNAME); |
| return 0; |
| } |
| return path; |
| } |
| else { |
| /* Remove any trailing slash unless this is |
| * a single slash path. |
| */ |
| _DEL_TRAILING_PATH_SEP(path, size); |
| if (path[size - 1] == ' ' || path[size - 1] == '.') { |
| /* Trailing space and dot are invalid file or dir names |
| */ |
| SetLastError(ERROR_BAD_PATHNAME); |
| return 0; |
| } |
| } |
| if (size > 1 && path[1] == ':' && IS_DRIVE_CHAR(path[0])) { |
| /* Never go above C: */ |
| path += 2; |
| } |
| else if (path[0] == '/' && path[1] == '/') { |
| /* Never go above //share/ |
| */ |
| if (path[2] == '.' && path[3] == '/') { |
| /* This is probably //./pipe/ */ |
| return path; |
| } |
| cp = strchr(path + 2, '/'); |
| if (cp != 0) |
| path = cp; |
| else { |
| /* We only have //share |
| */ |
| return path; |
| } |
| } |
| return path; |
| } |
| |
| static char *relative_path(char *path, int* remain) |
| { |
| char *sp; |
| char *cp; |
| int ch = '/'; |
| |
| path = skip_prefix(path, &sp); |
| if (!path) |
| return 0; |
| if (path != sp) { |
| /* Unexpected. Expected a relative path, but it starts with C: or //share/ */ |
| SetLastError(ERROR_BAD_PATHNAME); |
| return 0; |
| } |
| cp = path; |
| while (*path) { |
| if (IS_PATH_SEP(ch) && *path == '.') { |
| /* nd: number of consecutive dot characters */ |
| int nd = 0; |
| while (path[nd] == '.') |
| nd++; |
| if (IS_PATH_SEP(path[nd])) { |
| if (nd > 2) { |
| SetLastError(ERROR_BAD_PATHNAME); |
| return 0; |
| } |
| path += nd; |
| if (*path) |
| path++; |
| if (nd > 1) { |
| if (cp > sp + 1) { |
| cp--; |
| while (cp > sp) { |
| if (IS_PATH_SEP(*(cp - 1))) |
| break; |
| cp--; |
| } |
| } |
| else { |
| (*remain)++; |
| } |
| } |
| } |
| else { |
| ch = *cp++ = *path++; |
| } |
| } |
| else { |
| ch = *cp++ = *path++; |
| } |
| } |
| *cp = '\0'; |
| return sp; |
| } |
| |
| static char *path_merge(const char *root, const char *path) |
| { |
| char merge[8192]; |
| char dir[MAX_PATH]; |
| char *rel; |
| char *out = 0; |
| size_t sz; |
| size_t rsz; |
| int remain = 0; |
| |
| if (root == NULL || path == NULL) { |
| SetLastError(ERROR_INVALID_PARAMETER ); |
| return 0; |
| } |
| if (FAILED(StringCbCopy(dir, MAX_PATH, root))) { |
| SetLastError(ERROR_FILENAME_EXCED_RANGE); |
| return 0; |
| } |
| if (FAILED(StringCbCopy(merge, 8190, path))) { |
| /* TODO: Use dynamic buffer with larger size */ |
| SetLastError(ERROR_FILENAME_EXCED_RANGE); |
| return 0; |
| } |
| sz = strlen(merge); |
| rsz = strlen(dir); |
| /* Normalize path */ |
| if ((rel = relative_path(merge, &remain))) { |
| size_t bl; |
| if (remain > 0) { |
| char *skip = dir + rsz - 1; |
| char *spr; |
| char *start = skip_prefix(dir, &spr); |
| if (*skip == '/') |
| skip--; |
| while (remain > 0 && skip >= start) { |
| if (*skip == '/') { |
| remain--; |
| } |
| skip--; |
| } |
| if (remain > 0) { |
| SetLastError(ERROR_BAD_PATHNAME); |
| return 0; |
| } |
| if (skip < start) { |
| skip = start; |
| } |
| *++skip = '\0'; |
| } |
| /* one additional byte for trailing '\0', |
| * one additional byte for eventual path |
| * separator between dir and merge */ |
| bl = strlen(dir) + sz + 2; |
| out = malloc(bl); |
| if (out == 0) |
| return 0; |
| /* Prepend dir */ |
| StringCbCopy(out, bl, dir); |
| sz = strlen(out); |
| BS2FSA(out); |
| _INS_TRAILING_PATH_SEP(out, sz); |
| if ((IS_DRIVE_CHAR(rel[0]) && rel[1] == ':')) |
| rel += 2; |
| if (rel[0] == '/') |
| ++rel; |
| if (*rel != '\0') |
| StringCbCopy(out + sz, bl - sz, rel); |
| FS2BSA(out); |
| } |
| return out; |
| } |
| |
| static int is_path_relative(const char *path) |
| { |
| if ((IS_DRIVE_CHAR(path[0]) && path[1] == ':') || |
| path[0] == '/' || path[0] == '\\') |
| return 0; |
| else |
| return 1; |
| } |