diff options
Diffstat (limited to 'modules/arch/win32/mod_isapi.c')
-rw-r--r-- | modules/arch/win32/mod_isapi.c | 1727 |
1 files changed, 1727 insertions, 0 deletions
diff --git a/modules/arch/win32/mod_isapi.c b/modules/arch/win32/mod_isapi.c new file mode 100644 index 0000000..2e51d51 --- /dev/null +++ b/modules/arch/win32/mod_isapi.c @@ -0,0 +1,1727 @@ +/* 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. + */ + +/* + * mod_isapi.c - Internet Server Application (ISA) module for Apache + * by Alexei Kosut <akosut@apache.org>, significant overhauls and + * redesign by William Rowe <wrowe@covalent.net>, and hints from many + * other developer/users who have hit on specific flaws. + * + * This module implements the ISAPI Handler architecture, allowing + * Apache to load Internet Server Applications (ISAPI extensions), + * similar to the support in IIS, Zope, O'Reilly's WebSite and others. + * + * It is a complete implementation of the ISAPI 2.0 specification, + * except for "Microsoft extensions" to the API which provide + * asynchronous I/O. It is further extended to include additional + * "Microsoft extensions" through IIS 5.0, with some deficiencies + * where one-to-one mappings don't exist. + * + * Refer to /manual/mod/mod_isapi.html for additional details on + * configuration and use, but check this source for specific support + * of the API, + */ + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_log.h" +#include "util_script.h" +#include "mod_core.h" +#include "apr_lib.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_buckets.h" +#include "apr_thread_mutex.h" +#include "apr_thread_rwlock.h" +#include "apr_hash.h" +#include "mod_isapi.h" + +/* Retry frequency for a failed-to-load isapi .dll */ +#define ISAPI_RETRY apr_time_from_sec(30) + +/********************************************************** + * + * ISAPI Module Configuration + * + **********************************************************/ + +module AP_MODULE_DECLARE_DATA isapi_module; + +#define ISAPI_UNDEF -1 + +/* Our isapi per-dir config structure */ +typedef struct isapi_dir_conf { + int read_ahead_buflen; + int log_unsupported; + int log_to_errlog; + int log_to_query; + int fake_async; +} isapi_dir_conf; + +typedef struct isapi_loaded isapi_loaded; + +apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r, + const char *fpath, isapi_loaded** isa); + +static void *create_isapi_dir_config(apr_pool_t *p, char *dummy) +{ + isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf)); + + dir->read_ahead_buflen = ISAPI_UNDEF; + dir->log_unsupported = ISAPI_UNDEF; + dir->log_to_errlog = ISAPI_UNDEF; + dir->log_to_query = ISAPI_UNDEF; + dir->fake_async = ISAPI_UNDEF; + + return dir; +} + +static void *merge_isapi_dir_configs(apr_pool_t *p, void *base_, void *add_) +{ + isapi_dir_conf *base = (isapi_dir_conf *) base_; + isapi_dir_conf *add = (isapi_dir_conf *) add_; + isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf)); + + dir->read_ahead_buflen = (add->read_ahead_buflen == ISAPI_UNDEF) + ? base->read_ahead_buflen + : add->read_ahead_buflen; + dir->log_unsupported = (add->log_unsupported == ISAPI_UNDEF) + ? base->log_unsupported + : add->log_unsupported; + dir->log_to_errlog = (add->log_to_errlog == ISAPI_UNDEF) + ? base->log_to_errlog + : add->log_to_errlog; + dir->log_to_query = (add->log_to_query == ISAPI_UNDEF) + ? base->log_to_query + : add->log_to_query; + dir->fake_async = (add->fake_async == ISAPI_UNDEF) + ? base->fake_async + : add->fake_async; + + return dir; +} + +static const char *isapi_cmd_cachefile(cmd_parms *cmd, void *dummy, + const char *filename) +{ + isapi_loaded *isa; + apr_finfo_t tmp; + apr_status_t rv; + char *fspec; + + /* ### Just an observation ... it would be terribly cool to be + * able to use this per-dir, relative to the directory block being + * defined. The hash result remains global, but shorthand of + * <Directory "c:/webapps/isapi"> + * ISAPICacheFile myapp.dll anotherapp.dll thirdapp.dll + * </Directory> + * would be very convienent. + */ + fspec = ap_server_root_relative(cmd->pool, filename); + if (!fspec) { + ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server, APLOGNO(02103) + "invalid module path, skipping %s", filename); + return NULL; + } + if ((rv = apr_stat(&tmp, fspec, APR_FINFO_TYPE, + cmd->temp_pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server, APLOGNO(02104) + "unable to stat, skipping %s", fspec); + return NULL; + } + if (tmp.filetype != APR_REG) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(02105) + "not a regular file, skipping %s", fspec); + return NULL; + } + + /* Load the extension as cached (with null request_rec) */ + rv = isapi_lookup(cmd->pool, cmd->server, NULL, fspec, &isa); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server, APLOGNO(02106) + "unable to cache, skipping %s", fspec); + return NULL; + } + + return NULL; +} + +static const command_rec isapi_cmds[] = { + AP_INIT_TAKE1("ISAPIReadAheadBuffer", ap_set_int_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, read_ahead_buflen), + OR_FILEINFO, "Maximum client request body to initially pass to the" + " ISAPI handler (default: 49152)"), + AP_INIT_FLAG("ISAPILogNotSupported", ap_set_flag_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, log_unsupported), + OR_FILEINFO, "Log requests not supported by the ISAPI server" + " on or off (default: off)"), + AP_INIT_FLAG("ISAPIAppendLogToErrors", ap_set_flag_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, log_to_errlog), + OR_FILEINFO, "Send all Append Log requests to the error log" + " on or off (default: off)"), + AP_INIT_FLAG("ISAPIAppendLogToQuery", ap_set_flag_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, log_to_query), + OR_FILEINFO, "Append Log requests are concatinated to the query args" + " on or off (default: on)"), + AP_INIT_FLAG("ISAPIFakeAsync", ap_set_flag_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, fake_async), + OR_FILEINFO, "Fake Asynchronous support for isapi callbacks" + " on or off [Experimental] (default: off)"), + AP_INIT_ITERATE("ISAPICacheFile", isapi_cmd_cachefile, NULL, + RSRC_CONF, "Cache the specified ISAPI extension in-process"), + {NULL} +}; + +/********************************************************** + * + * ISAPI Module Cache handling section + * + **********************************************************/ + +/* Our isapi global config values */ +static struct isapi_global_conf { + apr_pool_t *pool; + apr_thread_mutex_t *lock; + apr_hash_t *hash; +} loaded; + +/* Our loaded isapi module description structure */ +struct isapi_loaded { + const char *filename; + apr_thread_rwlock_t *in_progress; + apr_status_t last_load_rv; + apr_time_t last_load_time; + apr_dso_handle_t *handle; + HSE_VERSION_INFO *isapi_version; + apr_uint32_t report_version; + apr_uint32_t timeout; + PFN_GETEXTENSIONVERSION GetExtensionVersion; + PFN_HTTPEXTENSIONPROC HttpExtensionProc; + PFN_TERMINATEEXTENSION TerminateExtension; +}; + +static apr_status_t isapi_unload(isapi_loaded *isa, int force) +{ + /* All done with the DLL... get rid of it... + * + * If optionally cached, and we weren't asked to force the unload, + * pass HSE_TERM_ADVISORY_UNLOAD, and if it returns 1, unload, + * otherwise, leave it alone (it didn't choose to cooperate.) + */ + if (!isa->handle) { + return APR_SUCCESS; + } + if (isa->TerminateExtension) { + if (force) { + (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD); + } + else if (!(*isa->TerminateExtension)(HSE_TERM_ADVISORY_UNLOAD)) { + return APR_EGENERAL; + } + } + apr_dso_unload(isa->handle); + isa->handle = NULL; + return APR_SUCCESS; +} + +static apr_status_t cleanup_isapi(void *isa_) +{ + isapi_loaded* isa = (isapi_loaded*) isa_; + + /* We must force the module to unload, we are about + * to lose the isapi structure's allocation entirely. + */ + return isapi_unload(isa, 1); +} + +static apr_status_t isapi_load(apr_pool_t *p, server_rec *s, isapi_loaded *isa) +{ + apr_status_t rv; + + isa->isapi_version = apr_pcalloc(p, sizeof(HSE_VERSION_INFO)); + + /* TODO: These aught to become overrideable, so that we + * assure a given isapi can be fooled into behaving well. + * + * The tricky bit, they aren't really a per-dir sort of + * config, they will always be constant across every + * reference to the .dll no matter what context (vhost, + * location, etc) they apply to. + */ + isa->report_version = 0x500; /* Revision 5.0 */ + isa->timeout = 300 * 1000000; /* microsecs, not used */ + + rv = apr_dso_load(&isa->handle, isa->filename, p); + if (rv) + { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02107) + "failed to load %s", isa->filename); + isa->handle = NULL; + return rv; + } + + rv = apr_dso_sym((void**)&isa->GetExtensionVersion, isa->handle, + "GetExtensionVersion"); + if (rv) + { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02108) + "missing GetExtensionVersion() in %s", + isa->filename); + apr_dso_unload(isa->handle); + isa->handle = NULL; + return rv; + } + + rv = apr_dso_sym((void**)&isa->HttpExtensionProc, isa->handle, + "HttpExtensionProc"); + if (rv) + { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02109) + "missing HttpExtensionProc() in %s", + isa->filename); + apr_dso_unload(isa->handle); + isa->handle = NULL; + return rv; + } + + /* TerminateExtension() is an optional interface */ + rv = apr_dso_sym((void**)&isa->TerminateExtension, isa->handle, + "TerminateExtension"); + apr_set_os_error(0); + + /* Run GetExtensionVersion() */ + if (!(isa->GetExtensionVersion)(isa->isapi_version)) { + apr_status_t rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02110) + "failed call to GetExtensionVersion() in %s", + isa->filename); + apr_dso_unload(isa->handle); + isa->handle = NULL; + return rv; + } + + apr_pool_cleanup_register(p, isa, cleanup_isapi, + apr_pool_cleanup_null); + + return APR_SUCCESS; +} + +apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r, + const char *fpath, isapi_loaded** isa) +{ + apr_status_t rv; + const char *key; + + if ((rv = apr_thread_mutex_lock(loaded.lock)) != APR_SUCCESS) { + return rv; + } + + *isa = apr_hash_get(loaded.hash, fpath, APR_HASH_KEY_STRING); + + if (*isa) { + + /* If we find this lock exists, use a set-aside copy of gainlock + * to avoid race conditions on NULLing the in_progress variable + * when the load has completed. Release the global isapi hash + * lock so other requests can proceed, then rdlock for completion + * of loading our desired dll or wrlock if we would like to retry + * loading the dll (because last_load_rv failed and retry is up.) + */ + apr_thread_rwlock_t *gainlock = (*isa)->in_progress; + + /* gainlock is NULLed after the module loads successfully. + * This free-threaded module can be used without any locking. + */ + if (!gainlock) { + rv = (*isa)->last_load_rv; + apr_thread_mutex_unlock(loaded.lock); + return rv; + } + + + if ((*isa)->last_load_rv == APR_SUCCESS) { + apr_thread_mutex_unlock(loaded.lock); + if ((rv = apr_thread_rwlock_rdlock(gainlock)) + != APR_SUCCESS) { + return rv; + } + rv = (*isa)->last_load_rv; + apr_thread_rwlock_unlock(gainlock); + return rv; + } + + if (apr_time_now() > (*isa)->last_load_time + ISAPI_RETRY) { + + /* Remember last_load_time before releasing the global + * hash lock to avoid colliding with another thread + * that hit this exception at the same time as our + * retry attempt, since we unlock the global mutex + * before attempting a write lock for this module. + */ + apr_time_t check_time = (*isa)->last_load_time; + apr_thread_mutex_unlock(loaded.lock); + + if ((rv = apr_thread_rwlock_wrlock(gainlock)) + != APR_SUCCESS) { + return rv; + } + + /* If last_load_time is unchanged, we still own this + * retry, otherwise presume another thread provided + * our retry (for good or ill). Relock the global + * hash for updating last_load_ vars, so their update + * is always atomic to the global lock. + */ + if (check_time == (*isa)->last_load_time) { + + rv = isapi_load(loaded.pool, s, *isa); + + apr_thread_mutex_lock(loaded.lock); + (*isa)->last_load_rv = rv; + (*isa)->last_load_time = apr_time_now(); + apr_thread_mutex_unlock(loaded.lock); + } + else { + rv = (*isa)->last_load_rv; + } + apr_thread_rwlock_unlock(gainlock); + + return rv; + } + + /* We haven't hit timeup on retry, let's grab the last_rv + * within the hash mutex before unlocking. + */ + rv = (*isa)->last_load_rv; + apr_thread_mutex_unlock(loaded.lock); + + return rv; + } + + /* If the module was not found, it's time to create a hash key entry + * before releasing the hash lock to avoid multiple threads from + * loading the same module. + */ + key = apr_pstrdup(loaded.pool, fpath); + *isa = apr_pcalloc(loaded.pool, sizeof(isapi_loaded)); + (*isa)->filename = key; + if (r) { + /* A mutex that exists only long enough to attempt to + * load this isapi dll, the release this module to all + * other takers that came along during the one-time + * load process. Short lifetime for this lock would + * be great, however, using r->pool is nasty if those + * blocked on the lock haven't all unlocked before we + * attempt to destroy. A nastier race condition than + * I want to deal with at this moment... + */ + apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool); + apr_thread_rwlock_wrlock((*isa)->in_progress); + } + + apr_hash_set(loaded.hash, key, APR_HASH_KEY_STRING, *isa); + + /* Now attempt to load the isapi on our own time, + * allow other isapi processing to resume. + */ + apr_thread_mutex_unlock(loaded.lock); + + rv = isapi_load(loaded.pool, s, *isa); + (*isa)->last_load_time = apr_time_now(); + (*isa)->last_load_rv = rv; + + if (r && (rv == APR_SUCCESS)) { + /* Let others who are blocked on this particular + * module resume their requests, for better or worse. + */ + apr_thread_rwlock_t *unlock = (*isa)->in_progress; + (*isa)->in_progress = NULL; + apr_thread_rwlock_unlock(unlock); + } + else if (!r && (rv != APR_SUCCESS)) { + /* We must leave a rwlock around for requests to retry + * loading this dll after timeup... since we were in + * the setup code we had avoided creating this lock. + */ + apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool); + } + + return (*isa)->last_load_rv; +} + +/********************************************************** + * + * ISAPI Module request callbacks section + * + **********************************************************/ + +/* Our "Connection ID" structure */ +struct isapi_cid { + EXTENSION_CONTROL_BLOCK *ecb; + isapi_dir_conf dconf; + isapi_loaded *isa; + request_rec *r; + int headers_set; + int response_sent; + PFN_HSE_IO_COMPLETION completion; + void *completion_arg; + apr_thread_mutex_t *completed; +}; + +static int APR_THREAD_FUNC regfnGetServerVariable(isapi_cid *cid, + char *variable_name, + void *buf_ptr, + apr_uint32_t *buf_size) +{ + request_rec *r = cid->r; + const char *result; + char *buf_data = (char*)buf_ptr; + apr_uint32_t len; + + if (!strcmp(variable_name, "ALL_HTTP")) + { + /* crlf delimited, colon split, comma separated and + * null terminated list of HTTP_ vars + */ + const apr_array_header_t *arr = apr_table_elts(r->subprocess_env); + const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts; + int i; + + for (len = 0, i = 0; i < arr->nelts; i++) { + if (!strncmp(elts[i].key, "HTTP_", 5)) { + len += strlen(elts[i].key) + strlen(elts[i].val) + 3; + } + } + + if (*buf_size < len + 1) { + *buf_size = len + 1; + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER)); + return 0; + } + + for (i = 0; i < arr->nelts; i++) { + if (!strncmp(elts[i].key, "HTTP_", 5)) { + strcpy(buf_data, elts[i].key); + buf_data += strlen(elts[i].key); + *(buf_data++) = ':'; + strcpy(buf_data, elts[i].val); + buf_data += strlen(elts[i].val); + *(buf_data++) = '\r'; + *(buf_data++) = '\n'; + } + } + + *(buf_data++) = '\0'; + *buf_size = len + 1; + return 1; + } + + if (!strcmp(variable_name, "ALL_RAW")) + { + /* crlf delimited, colon split, comma separated and + * null terminated list of the raw request header + */ + const apr_array_header_t *arr = apr_table_elts(r->headers_in); + const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts; + int i; + + for (len = 0, i = 0; i < arr->nelts; i++) { + len += strlen(elts[i].key) + strlen(elts[i].val) + 4; + } + + if (*buf_size < len + 1) { + *buf_size = len + 1; + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER)); + return 0; + } + + for (i = 0; i < arr->nelts; i++) { + strcpy(buf_data, elts[i].key); + buf_data += strlen(elts[i].key); + *(buf_data++) = ':'; + *(buf_data++) = ' '; + strcpy(buf_data, elts[i].val); + buf_data += strlen(elts[i].val); + *(buf_data++) = '\r'; + *(buf_data++) = '\n'; + } + *(buf_data++) = '\0'; + *buf_size = len + 1; + return 1; + } + + /* Not a special case */ + result = apr_table_get(r->subprocess_env, variable_name); + + if (result) { + len = strlen(result); + if (*buf_size < len + 1) { + *buf_size = len + 1; + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER)); + return 0; + } + strcpy(buf_data, result); + *buf_size = len + 1; + return 1; + } + + /* Not Found */ + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_INDEX)); + return 0; +} + +static int APR_THREAD_FUNC regfnReadClient(isapi_cid *cid, + void *buf_data, + apr_uint32_t *buf_size) +{ + request_rec *r = cid->r; + apr_uint32_t read = 0; + int res = 0; + + if (r->remaining < *buf_size) { + *buf_size = (apr_size_t)r->remaining; + } + + while (read < *buf_size && + ((res = ap_get_client_block(r, (char*)buf_data + read, + *buf_size - read)) > 0)) { + read += res; + } + + *buf_size = read; + if (res < 0) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_READ_FAULT)); + } + return (res >= 0); +} + +/* Common code invoked for both HSE_REQ_SEND_RESPONSE_HEADER and + * the newer HSE_REQ_SEND_RESPONSE_HEADER_EX ServerSupportFunction(s) + * as well as other functions that write responses and presume that + * the support functions above are optional. + * + * Other callers trying to split headers and body bytes should pass + * head/headlen alone (leaving stat/statlen NULL/0), so that they + * get a proper count of bytes consumed. The argument passed to stat + * isn't counted as the head bytes are. + */ +static apr_ssize_t send_response_header(isapi_cid *cid, + const char *stat, + const char *head, + apr_size_t statlen, + apr_size_t headlen) +{ + int head_present = 1; + int termarg; + int res; + int old_status; + const char *termch; + apr_size_t ate = 0; + + if (!head || headlen == 0 || !*head) { + head = stat; + stat = NULL; + headlen = statlen; + statlen = 0; + head_present = 0; /* Don't eat the header */ + } + + if (!stat || statlen == 0 || !*stat) { + if (head && headlen && *head && ((stat = memchr(head, '\r', headlen)) + || (stat = memchr(head, '\n', headlen)) + || (stat = memchr(head, '\0', headlen)) + || (stat = head + headlen))) { + statlen = stat - head; + if (memchr(head, ':', statlen)) { + stat = "Status: 200 OK"; + statlen = strlen(stat); + } + else { + const char *flip = head; + head = stat; + stat = flip; + headlen -= statlen; + ate += statlen; + if (*head == '\r' && headlen) + ++head, --headlen, ++ate; + if (*head == '\n' && headlen) + ++head, --headlen, ++ate; + } + } + } + + if (stat && (statlen > 0) && *stat) { + char *newstat; + if (!apr_isdigit(*stat)) { + const char *stattok = stat; + int toklen = statlen; + while (toklen && *stattok && !apr_isspace(*stattok)) { + ++stattok; --toklen; + } + while (toklen && apr_isspace(*stattok)) { + ++stattok; --toklen; + } + /* Now decide if we follow the xxx message + * or the http/x.x xxx message format + */ + if (toklen && apr_isdigit(*stattok)) { + statlen = toklen; + stat = stattok; + } + } + newstat = apr_palloc(cid->r->pool, statlen + 9); + strcpy(newstat, "Status: "); + apr_cpystrn(newstat + 8, stat, statlen + 1); + stat = newstat; + statlen += 8; + } + + if (!head || headlen == 0 || !*head) { + head = "\r\n"; + headlen = 2; + } + else + { + if (head[headlen - 1] && head[headlen]) { + /* Whoops... not NULL terminated */ + head = apr_pstrndup(cid->r->pool, head, headlen); + } + } + + /* Seems IIS does not enforce the requirement for \r\n termination + * on HSE_REQ_SEND_RESPONSE_HEADER, but we won't panic... + * ap_scan_script_header_err_strs handles this aspect for us. + * + * Parse them out, or die trying + */ + old_status = cid->r->status; + + if (stat) { + res = ap_scan_script_header_err_strs_ex(cid->r, NULL, + APLOG_MODULE_INDEX, &termch, &termarg, stat, head, NULL); + } + else { + res = ap_scan_script_header_err_strs_ex(cid->r, NULL, + APLOG_MODULE_INDEX, &termch, &termarg, head, NULL); + } + + /* Set our status. */ + if (res) { + /* This is an immediate error result from the parser + */ + cid->r->status = res; + cid->r->status_line = ap_get_status_line(cid->r->status); + cid->ecb->dwHttpStatusCode = cid->r->status; + } + else if (cid->r->status) { + /* We have a status in r->status, so let's just use it. + * This is likely to be the Status: parsed above, and + * may also be a delayed error result from the parser. + * If it was filled in, status_line should also have + * been filled in. + */ + cid->ecb->dwHttpStatusCode = cid->r->status; + } + else if (cid->ecb->dwHttpStatusCode + && cid->ecb->dwHttpStatusCode != HTTP_OK) { + /* Now we fall back on dwHttpStatusCode if it appears + * ap_scan_script_header fell back on the default code. + * Any other results set dwHttpStatusCode to the decoded + * status value. + */ + cid->r->status = cid->ecb->dwHttpStatusCode; + cid->r->status_line = ap_get_status_line(cid->r->status); + } + else if (old_status) { + /* Well... either there is no dwHttpStatusCode or it's HTTP_OK. + * In any case, we don't have a good status to return yet... + * Perhaps the one we came in with will be better. Let's use it, + * if we were given one (note this is a pedantic case, it would + * normally be covered above unless the scan script code unset + * the r->status). Should there be a check here as to whether + * we are setting a valid response code? + */ + cid->r->status = old_status; + cid->r->status_line = ap_get_status_line(cid->r->status); + cid->ecb->dwHttpStatusCode = cid->r->status; + } + else { + /* None of dwHttpStatusCode, the parser's r->status nor the + * old value of r->status were helpful, and nothing was decoded + * from Status: string passed to us. Let's just say HTTP_OK + * and get the data out, this was the isapi dev's oversight. + */ + cid->r->status = HTTP_OK; + cid->r->status_line = ap_get_status_line(cid->r->status); + cid->ecb->dwHttpStatusCode = cid->r->status; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, cid->r, APLOGNO(02111) + "Could not determine HTTP response code; using %d", + cid->r->status); + } + + if (cid->r->status == HTTP_INTERNAL_SERVER_ERROR) { + return -1; + } + + /* If only Status was passed, we consumed nothing + */ + if (!head_present) + return 0; + + cid->headers_set = 1; + + /* If all went well, tell the caller we consumed the headers complete + */ + if (!termch) + return(ate + headlen); + + /* Any data left must be sent directly by the caller, all we + * give back is the size of the headers we consumed (which only + * happens if the parser got to the head arg, which varies based + * on whether we passed stat+head to scan, or only head. + */ + if (termch && (termarg == (stat ? 1 : 0)) + && head_present && head + headlen > termch) { + return ate + termch - head; + } + return ate; +} + +static int APR_THREAD_FUNC regfnWriteClient(isapi_cid *cid, + void *buf_ptr, + apr_uint32_t *size_arg, + apr_uint32_t flags) +{ + request_rec *r = cid->r; + conn_rec *c = r->connection; + apr_uint32_t buf_size = *size_arg; + char *buf_data = (char*)buf_ptr; + apr_bucket_brigade *bb; + apr_bucket *b; + apr_status_t rv = APR_SUCCESS; + + if (!cid->headers_set) { + /* It appears that the foxisapi module and other clients + * presume that WriteClient("headers\n\nbody") will work. + * Parse them out, or die trying. + */ + apr_ssize_t ate; + ate = send_response_header(cid, NULL, buf_data, 0, buf_size); + if (ate < 0) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + + buf_data += ate; + buf_size -= ate; + } + + if (buf_size) { + bb = apr_brigade_create(r->pool, c->bucket_alloc); + b = apr_bucket_transient_create(buf_data, buf_size, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(r->output_filters, bb); + cid->response_sent = 1; + if (rv != APR_SUCCESS) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02984) + "WriteClient ap_pass_brigade failed: %s", + r->filename); + } + + if ((flags & HSE_IO_ASYNC) && cid->completion) { + if (rv == APR_SUCCESS) { + cid->completion(cid->ecb, cid->completion_arg, + *size_arg, ERROR_SUCCESS); + } + else { + cid->completion(cid->ecb, cid->completion_arg, + *size_arg, ERROR_WRITE_FAULT); + } + } + return (rv == APR_SUCCESS); +} + +static int APR_THREAD_FUNC regfnServerSupportFunction(isapi_cid *cid, + apr_uint32_t HSE_code, + void *buf_ptr, + apr_uint32_t *buf_size, + apr_uint32_t *data_type) +{ + request_rec *r = cid->r; + conn_rec *c = r->connection; + char *buf_data = (char*)buf_ptr; + request_rec *subreq; + apr_status_t rv; + + switch (HSE_code) { + case HSE_REQ_SEND_URL_REDIRECT_RESP: + /* Set the status to be returned when the HttpExtensionProc() + * is done. + * WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP + * and HSE_REQ_SEND_URL as equivalent per the Jan 2000 SDK. + * They most definitely are not, even in their own samples. + */ + apr_table_set (r->headers_out, "Location", buf_data); + cid->r->status = cid->ecb->dwHttpStatusCode = HTTP_MOVED_TEMPORARILY; + cid->r->status_line = ap_get_status_line(cid->r->status); + cid->headers_set = 1; + return 1; + + case HSE_REQ_SEND_URL: + /* Soak up remaining input */ + if (r->remaining > 0) { + char argsbuffer[HUGE_STRING_LEN]; + while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)); + } + + /* Reset the method to GET */ + r->method = "GET"; + r->method_number = M_GET; + + /* Don't let anyone think there's still data */ + apr_table_unset(r->headers_in, "Content-Length"); + + /* AV fault per PR3598 - redirected path is lost! */ + buf_data = apr_pstrdup(r->pool, (char*)buf_data); + ap_internal_redirect(buf_data, r); + return 1; + + case HSE_REQ_SEND_RESPONSE_HEADER: + { + /* Parse them out, or die trying */ + apr_size_t statlen = 0, headlen = 0; + apr_ssize_t ate; + if (buf_data) + statlen = strlen((char*) buf_data); + if (data_type) + headlen = strlen((char*) data_type); + ate = send_response_header(cid, (char*) buf_data, + (char*) data_type, + statlen, headlen); + if (ate < 0) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + else if ((apr_size_t)ate < headlen) { + apr_bucket_brigade *bb; + apr_bucket *b; + bb = apr_brigade_create(cid->r->pool, c->bucket_alloc); + b = apr_bucket_transient_create((char*) data_type + ate, + headlen - ate, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(cid->r->output_filters, bb); + cid->response_sent = 1; + if (rv != APR_SUCCESS) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03177) + "ServerSupportFunction " + "HSE_REQ_SEND_RESPONSE_HEADER " + "ap_pass_brigade failed: %s", r->filename); + return (rv == APR_SUCCESS); + } + /* Deliberately hold off sending 'just the headers' to begin to + * accumulate the body and speed up the overall response, or at + * least wait for the end the session. + */ + return 1; + } + + case HSE_REQ_DONE_WITH_SESSION: + /* Signal to resume the thread completing this request, + * leave it to the pool cleanup to dispose of our mutex. + */ + if (cid->completed) { + (void)apr_thread_mutex_unlock(cid->completed); + return 1; + } + else if (cid->dconf.log_unsupported) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02671) + "ServerSupportFunction " + "HSE_REQ_DONE_WITH_SESSION is not supported: %s", + r->filename); + } + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_MAP_URL_TO_PATH: + { + /* Map a URL to a filename */ + char *file = (char *)buf_data; + apr_uint32_t len; + subreq = ap_sub_req_lookup_uri( + apr_pstrndup(cid->r->pool, file, *buf_size), r, NULL); + + if (!subreq->filename) { + ap_destroy_sub_req(subreq); + return 0; + } + + len = (apr_uint32_t)strlen(r->filename); + + if ((subreq->finfo.filetype == APR_DIR) + && (!subreq->path_info) + && (file[len - 1] != '/')) + file = apr_pstrcat(cid->r->pool, subreq->filename, "/", NULL); + else + file = apr_pstrcat(cid->r->pool, subreq->filename, + subreq->path_info, NULL); + + ap_destroy_sub_req(subreq); + +#ifdef WIN32 + /* We need to make this a real Windows path name */ + apr_filepath_merge(&file, "", file, APR_FILEPATH_NATIVE, r->pool); +#endif + + *buf_size = apr_cpystrn(buf_data, file, *buf_size) - buf_data; + + return 1; + } + + case HSE_REQ_GET_SSPI_INFO: + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02672) + "ServerSupportFunction HSE_REQ_GET_SSPI_INFO " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_APPEND_LOG_PARAMETER: + /* Log buf_data, of buf_size bytes, in the URI Query (cs-uri-query) field + */ + apr_table_set(r->notes, "isapi-parameter", (char*) buf_data); + if (cid->dconf.log_to_query) { + if (r->args) + r->args = apr_pstrcat(r->pool, r->args, (char*) buf_data, NULL); + else + r->args = apr_pstrdup(r->pool, (char*) buf_data); + } + if (cid->dconf.log_to_errlog) + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02985) + "%s: %s", cid->r->filename, + (char*) buf_data); + return 1; + + case HSE_REQ_IO_COMPLETION: + /* Emulates a completion port... Record callback address and + * user defined arg, we will call this after any async request + * (e.g. transmitfile) as if the request executed async. + * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call + * to HSE_REQ_IO_COMPLETION, and buf_data may be set to NULL. + */ + if (cid->dconf.fake_async) { + cid->completion = (PFN_HSE_IO_COMPLETION) buf_data; + cid->completion_arg = (void *) data_type; + return 1; + } + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02673) + "ServerSupportFunction HSE_REQ_IO_COMPLETION " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_TRANSMIT_FILE: + { + /* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND) + */ + HSE_TF_INFO *tf = (HSE_TF_INFO*)buf_data; + apr_uint32_t sent = 0; + apr_ssize_t ate = 0; + apr_bucket_brigade *bb; + apr_bucket *b; + apr_file_t *fd; + apr_off_t fsize; + + if (!cid->dconf.fake_async && (tf->dwFlags & HSE_IO_ASYNC)) { + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02674) + "ServerSupportFunction HSE_REQ_TRANSMIT_FILE " + "as HSE_IO_ASYNC is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + + /* Presume the handle was opened with the CORRECT semantics + * for TransmitFile + */ + if ((rv = apr_os_file_put(&fd, &tf->hFile, + APR_READ | APR_XTHREAD, r->pool)) + != APR_SUCCESS) { + return 0; + } + if (tf->BytesToWrite) { + fsize = tf->BytesToWrite; + } + else { + apr_finfo_t fi; + if (apr_file_info_get(&fi, APR_FINFO_SIZE, fd) != APR_SUCCESS) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + fsize = fi.size - tf->Offset; + } + + /* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */ + bb = apr_brigade_create(r->pool, c->bucket_alloc); + + /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the + * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any + * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag, + * you must have done so. They document that the pHead headers + * option is valid only for HSE_IO_SEND_HEADERS - we are a bit + * more flexible and assume with the flag, pHead are the + * response headers, and without, pHead simply contains text + * (handled after this case). + */ + if ((tf->dwFlags & HSE_IO_SEND_HEADERS) && tf->pszStatusCode) { + ate = send_response_header(cid, tf->pszStatusCode, + (char*)tf->pHead, + strlen(tf->pszStatusCode), + tf->HeadLength); + } + else if (!cid->headers_set && tf->pHead && tf->HeadLength + && *(char*)tf->pHead) { + ate = send_response_header(cid, NULL, (char*)tf->pHead, + 0, tf->HeadLength); + if (ate < 0) + { + apr_brigade_destroy(bb); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + } + + if (tf->pHead && (apr_size_t)ate < tf->HeadLength) { + b = apr_bucket_transient_create((char*)tf->pHead + ate, + tf->HeadLength - ate, + c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + sent = tf->HeadLength; + } + + sent += (apr_uint32_t)fsize; + apr_brigade_insert_file(bb, fd, tf->Offset, fsize, r->pool); + + if (tf->pTail && tf->TailLength) { + sent += tf->TailLength; + b = apr_bucket_transient_create((char*)tf->pTail, + tf->TailLength, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + } + + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(r->output_filters, bb); + cid->response_sent = 1; + if (rv != APR_SUCCESS) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03178) + "ServerSupportFunction " + "HSE_REQ_TRANSMIT_FILE " + "ap_pass_brigade failed: %s", r->filename); + + /* Use tf->pfnHseIO + tf->pContext, or if NULL, then use cid->fnIOComplete + * pass pContect to the HseIO callback. + */ + if (tf->dwFlags & HSE_IO_ASYNC) { + if (tf->pfnHseIO) { + if (rv == APR_SUCCESS) { + tf->pfnHseIO(cid->ecb, tf->pContext, + ERROR_SUCCESS, sent); + } + else { + tf->pfnHseIO(cid->ecb, tf->pContext, + ERROR_WRITE_FAULT, sent); + } + } + else if (cid->completion) { + if (rv == APR_SUCCESS) { + cid->completion(cid->ecb, cid->completion_arg, + sent, ERROR_SUCCESS); + } + else { + cid->completion(cid->ecb, cid->completion_arg, + sent, ERROR_WRITE_FAULT); + } + } + } + return (rv == APR_SUCCESS); + } + + case HSE_REQ_REFRESH_ISAPI_ACL: + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02675) + "ServerSupportFunction " + "HSE_REQ_REFRESH_ISAPI_ACL " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_IS_KEEP_CONN: + *((int *)buf_data) = (r->connection->keepalive == AP_CONN_KEEPALIVE); + return 1; + + case HSE_REQ_ASYNC_READ_CLIENT: + { + apr_uint32_t read = 0; + int res = 0; + if (!cid->dconf.fake_async) { + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02986) + "asynchronous I/O not supported: %s", + r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + + if (r->remaining < *buf_size) { + *buf_size = (apr_size_t)r->remaining; + } + + while (read < *buf_size && + ((res = ap_get_client_block(r, (char*)buf_data + read, + *buf_size - read)) > 0)) { + read += res; + } + + if ((*data_type & HSE_IO_ASYNC) && cid->completion) { + /* XXX: Many authors issue their next HSE_REQ_ASYNC_READ_CLIENT + * within the completion logic. An example is MS's own PSDK + * sample web/iis/extensions/io/ASyncRead. This potentially + * leads to stack exhaustion. To refactor, the notification + * logic needs to move to isapi_handler() - differentiating + * the cid->completed event with a new flag to indicate + * an async-notice versus the async request completed. + */ + if (res >= 0) { + cid->completion(cid->ecb, cid->completion_arg, + read, ERROR_SUCCESS); + } + else { + cid->completion(cid->ecb, cid->completion_arg, + read, ERROR_READ_FAULT); + } + } + return (res >= 0); + } + + case HSE_REQ_GET_IMPERSONATION_TOKEN: /* Added in ISAPI 4.0 */ + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02676) + "ServerSupportFunction " + "HSE_REQ_GET_IMPERSONATION_TOKEN " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_MAP_URL_TO_PATH_EX: + { + /* Map a URL to a filename */ + HSE_URL_MAPEX_INFO *info = (HSE_URL_MAPEX_INFO*)data_type; + char* test_uri = apr_pstrndup(r->pool, (char *)buf_data, *buf_size); + + subreq = ap_sub_req_lookup_uri(test_uri, r, NULL); + info->cchMatchingURL = strlen(test_uri); + info->cchMatchingPath = apr_cpystrn(info->lpszPath, subreq->filename, + sizeof(info->lpszPath)) - info->lpszPath; + + /* Mapping started with assuming both strings matched. + * Now roll on the path_info as a mismatch and handle + * terminating slashes for directory matches. + */ + if (subreq->path_info && *subreq->path_info) { + apr_cpystrn(info->lpszPath + info->cchMatchingPath, + subreq->path_info, + sizeof(info->lpszPath) - info->cchMatchingPath); + info->cchMatchingURL -= strlen(subreq->path_info); + if (subreq->finfo.filetype == APR_DIR + && info->cchMatchingPath < sizeof(info->lpszPath) - 1) { + /* roll forward over path_info's first slash */ + ++info->cchMatchingPath; + ++info->cchMatchingURL; + } + } + else if (subreq->finfo.filetype == APR_DIR + && info->cchMatchingPath < sizeof(info->lpszPath) - 1) { + /* Add a trailing slash for directory */ + info->lpszPath[info->cchMatchingPath++] = '/'; + info->lpszPath[info->cchMatchingPath] = '\0'; + } + + /* If the matched isn't a file, roll match back to the prior slash */ + if (subreq->finfo.filetype == APR_NOFILE) { + while (info->cchMatchingPath && info->cchMatchingURL) { + if (info->lpszPath[info->cchMatchingPath - 1] == '/') + break; + --info->cchMatchingPath; + --info->cchMatchingURL; + } + } + + /* Paths returned with back slashes */ + for (test_uri = info->lpszPath; *test_uri; ++test_uri) + if (*test_uri == '/') + *test_uri = '\\'; + + /* is a combination of: + * HSE_URL_FLAGS_READ 0x001 Allow read + * HSE_URL_FLAGS_WRITE 0x002 Allow write + * HSE_URL_FLAGS_EXECUTE 0x004 Allow execute + * HSE_URL_FLAGS_SSL 0x008 Require SSL + * HSE_URL_FLAGS_DONT_CACHE 0x010 Don't cache (VRoot only) + * HSE_URL_FLAGS_NEGO_CERT 0x020 Allow client SSL cert + * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert + * HSE_URL_FLAGS_MAP_CERT 0x080 Map client SSL cert to account + * HSE_URL_FLAGS_SSL128 0x100 Require 128-bit SSL cert + * HSE_URL_FLAGS_SCRIPT 0x200 Allow script execution + * + * XxX: As everywhere, EXEC flags could use some work... + * and this could go further with more flags, as desired. + */ + info->dwFlags = (subreq->finfo.protection & APR_UREAD ? 0x001 : 0) + | (subreq->finfo.protection & APR_UWRITE ? 0x002 : 0) + | (subreq->finfo.protection & APR_UEXECUTE ? 0x204 : 0); + return 1; + } + + case HSE_REQ_ABORTIVE_CLOSE: + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02677) + "ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE" + " is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_GET_CERT_INFO_EX: /* Added in ISAPI 4.0 */ + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02678) + "ServerSupportFunction " + "HSE_REQ_GET_CERT_INFO_EX " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_SEND_RESPONSE_HEADER_EX: /* Added in ISAPI 4.0 */ + { + HSE_SEND_HEADER_EX_INFO *shi = (HSE_SEND_HEADER_EX_INFO*)buf_data; + + /* Ignore shi->fKeepConn - we don't want the advise + */ + apr_ssize_t ate = send_response_header(cid, shi->pszStatus, + shi->pszHeader, + shi->cchStatus, + shi->cchHeader); + if (ate < 0) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + else if ((apr_size_t)ate < shi->cchHeader) { + apr_bucket_brigade *bb; + apr_bucket *b; + bb = apr_brigade_create(cid->r->pool, c->bucket_alloc); + b = apr_bucket_transient_create(shi->pszHeader + ate, + shi->cchHeader - ate, + c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(cid->r->output_filters, bb); + cid->response_sent = 1; + if (rv != APR_SUCCESS) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03179) + "ServerSupportFunction " + "HSE_REQ_SEND_RESPONSE_HEADER_EX " + "ap_pass_brigade failed: %s", r->filename); + return (rv == APR_SUCCESS); + } + /* Deliberately hold off sending 'just the headers' to begin to + * accumulate the body and speed up the overall response, or at + * least wait for the end the session. + */ + return 1; + } + + case HSE_REQ_CLOSE_CONNECTION: /* Added after ISAPI 4.0 */ + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02679) + "ServerSupportFunction " + "HSE_REQ_CLOSE_CONNECTION " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_IS_CONNECTED: /* Added after ISAPI 4.0 */ + /* Returns True if client is connected c.f. MSKB Q188346 + * assuming the identical return mechanism as HSE_REQ_IS_KEEP_CONN + */ + *((int *)buf_data) = (r->connection->aborted == 0); + return 1; + + case HSE_REQ_EXTENSION_TRIGGER: /* Added after ISAPI 4.0 */ + /* Undocumented - defined by the Microsoft Jan '00 Platform SDK + */ + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02680) + "ServerSupportFunction " + "HSE_REQ_EXTENSION_TRIGGER " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + default: + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02681) + "ServerSupportFunction (%d) not supported: " + "%s", HSE_code, r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } +} + +/********************************************************** + * + * ISAPI Module request invocation section + * + **********************************************************/ + +static apr_status_t isapi_handler (request_rec *r) +{ + isapi_dir_conf *dconf; + apr_table_t *e; + apr_status_t rv; + isapi_loaded *isa; + isapi_cid *cid; + const char *val; + apr_uint32_t read; + int res; + + if (strcmp(r->handler, "isapi-isa") + && strcmp(r->handler, "isapi-handler")) { + /* Hang on to the isapi-isa for compatibility with older docs + * (wtf did '-isa' mean in the first place?) but introduce + * a newer and clearer "isapi-handler" name. + */ + return DECLINED; + } + dconf = ap_get_module_config(r->per_dir_config, &isapi_module); + e = r->subprocess_env; + + /* Use similar restrictions as CGIs + * + * If this fails, it's pointless to load the isapi dll. + */ + if (!(ap_allow_options(r) & OPT_EXECCGI)) { + return HTTP_FORBIDDEN; + } + if (r->finfo.filetype == APR_NOFILE) { + return HTTP_NOT_FOUND; + } + if (r->finfo.filetype != APR_REG) { + return HTTP_FORBIDDEN; + } + if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) && + r->path_info && *r->path_info) { + /* default to accept */ + return HTTP_NOT_FOUND; + } + + if (isapi_lookup(r->pool, r->server, r, r->filename, &isa) + != APR_SUCCESS) { + return HTTP_INTERNAL_SERVER_ERROR; + } + /* Set up variables */ + ap_add_common_vars(r); + ap_add_cgi_vars(r); + apr_table_setn(e, "UNMAPPED_REMOTE_USER", "REMOTE_USER"); + if ((val = apr_table_get(e, "HTTPS")) && (strcmp(val, "on") == 0)) + apr_table_setn(e, "SERVER_PORT_SECURE", "1"); + else + apr_table_setn(e, "SERVER_PORT_SECURE", "0"); + apr_table_setn(e, "URL", r->uri); + + /* Set up connection structure and ecb, + * NULL or zero out most fields. + */ + cid = apr_pcalloc(r->pool, sizeof(isapi_cid)); + + /* Fixup defaults for dconf */ + cid->dconf.read_ahead_buflen = (dconf->read_ahead_buflen == ISAPI_UNDEF) + ? 49152 : dconf->read_ahead_buflen; + cid->dconf.log_unsupported = (dconf->log_unsupported == ISAPI_UNDEF) + ? 0 : dconf->log_unsupported; + cid->dconf.log_to_errlog = (dconf->log_to_errlog == ISAPI_UNDEF) + ? 0 : dconf->log_to_errlog; + cid->dconf.log_to_query = (dconf->log_to_query == ISAPI_UNDEF) + ? 1 : dconf->log_to_query; + cid->dconf.fake_async = (dconf->fake_async == ISAPI_UNDEF) + ? 0 : dconf->fake_async; + + cid->ecb = apr_pcalloc(r->pool, sizeof(EXTENSION_CONTROL_BLOCK)); + cid->ecb->ConnID = cid; + cid->isa = isa; + cid->r = r; + r->status = 0; + + cid->ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK); + cid->ecb->dwVersion = isa->report_version; + cid->ecb->dwHttpStatusCode = 0; + strcpy(cid->ecb->lpszLogData, ""); + /* TODO: are copies really needed here? + */ + cid->ecb->lpszMethod = (char*) r->method; + cid->ecb->lpszQueryString = (char*) apr_table_get(e, "QUERY_STRING"); + cid->ecb->lpszPathInfo = (char*) apr_table_get(e, "PATH_INFO"); + cid->ecb->lpszPathTranslated = (char*) apr_table_get(e, "PATH_TRANSLATED"); + cid->ecb->lpszContentType = (char*) apr_table_get(e, "CONTENT_TYPE"); + + /* Set up the callbacks */ + cid->ecb->GetServerVariable = regfnGetServerVariable; + cid->ecb->WriteClient = regfnWriteClient; + cid->ecb->ReadClient = regfnReadClient; + cid->ecb->ServerSupportFunction = regfnServerSupportFunction; + + /* Set up client input */ + res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR); + if (res) { + return res; + } + + if (ap_should_client_block(r)) { + /* Time to start reading the appropriate amount of data, + * and allow the administrator to tweak the number + */ + if (r->remaining) { + cid->ecb->cbTotalBytes = (apr_size_t)r->remaining; + if (cid->ecb->cbTotalBytes > (apr_uint32_t)cid->dconf.read_ahead_buflen) + cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen; + else + cid->ecb->cbAvailable = cid->ecb->cbTotalBytes; + } + else + { + cid->ecb->cbTotalBytes = 0xffffffff; + cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen; + } + + cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1); + + read = 0; + while (read < cid->ecb->cbAvailable && + ((res = ap_get_client_block(r, (char*)cid->ecb->lpbData + read, + cid->ecb->cbAvailable - read)) > 0)) { + read += res; + } + + if (res < 0) { + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Although it's not to spec, IIS seems to null-terminate + * its lpdData string. So we will too. + */ + if (res == 0) + cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read; + else + cid->ecb->cbAvailable = read; + cid->ecb->lpbData[read] = '\0'; + } + else { + cid->ecb->cbTotalBytes = 0; + cid->ecb->cbAvailable = 0; + cid->ecb->lpbData = NULL; + } + + /* To emulate async behavior... + * + * We create a cid->completed mutex and lock on it so that the + * app can believe is it running async. + * + * This request completes upon a notification through + * ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION), which + * unlocks this mutex. If the HttpExtensionProc() returns + * HSE_STATUS_PENDING, we will attempt to gain this lock again + * which may *only* happen once HSE_REQ_DONE_WITH_SESSION has + * unlocked the mutex. + */ + if (cid->dconf.fake_async) { + rv = apr_thread_mutex_create(&cid->completed, + APR_THREAD_MUTEX_UNNESTED, + r->pool); + if (cid->completed && (rv == APR_SUCCESS)) { + rv = apr_thread_mutex_lock(cid->completed); + } + + if (!cid->completed || (rv != APR_SUCCESS)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02112) + "Failed to create completion mutex"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + /* All right... try and run the sucker */ + rv = (*isa->HttpExtensionProc)(cid->ecb); + + /* Check for a log message - and log it */ + if (*cid->ecb->lpszLogData) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02113) + "%s: %s", r->filename, cid->ecb->lpszLogData); + } + + switch(rv) { + case 0: /* Strange, but MS isapi accepts this as success */ + case HSE_STATUS_SUCCESS: + case HSE_STATUS_SUCCESS_AND_KEEP_CONN: + /* Ignore the keepalive stuff; Apache handles it just fine without + * the ISAPI Handler's "advice". + * Per Microsoft: "In IIS versions 4.0 and later, the return + * values HSE_STATUS_SUCCESS and HSE_STATUS_SUCCESS_AND_KEEP_CONN + * are functionally identical: Keep-Alive connections are + * maintained, if supported by the client." + * ... so we were pat all this time + */ + break; + + case HSE_STATUS_PENDING: + /* emulating async behavior... + */ + if (cid->completed) { + /* The completion port was locked prior to invoking + * HttpExtensionProc(). Once we can regain the lock, + * when ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION) + * is called by the extension to release the lock, + * we may finally destroy the request. + */ + (void)apr_thread_mutex_lock(cid->completed); + break; + } + else if (cid->dconf.log_unsupported) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02114) + "asynch I/O result HSE_STATUS_PENDING " + "from HttpExtensionProc() is not supported: %s", + r->filename); + r->status = HTTP_INTERNAL_SERVER_ERROR; + } + break; + + case HSE_STATUS_ERROR: + /* end response if we have yet to do so. + */ + ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r, APLOGNO(02115) + "HSE_STATUS_ERROR result from " + "HttpExtensionProc(): %s", r->filename); + r->status = HTTP_INTERNAL_SERVER_ERROR; + break; + + default: + ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r, APLOGNO(02116) + "unrecognized result code %d " + "from HttpExtensionProc(): %s ", + rv, r->filename); + r->status = HTTP_INTERNAL_SERVER_ERROR; + break; + } + + /* Flush the response now, including headers-only responses */ + if (cid->headers_set || cid->response_sent) { + conn_rec *c = r->connection; + apr_bucket_brigade *bb; + apr_bucket *b; + apr_status_t rv; + + bb = apr_brigade_create(r->pool, c->bucket_alloc); + b = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(r->output_filters, bb); + cid->response_sent = 1; + + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02117) + "ap_pass_brigade failed to " + "complete the response: %s ", r->filename); + } + + return OK; /* NOT r->status, even if it has changed. */ + } + + /* As the client returned no error, and if we did not error out + * ourselves, trust dwHttpStatusCode to say something relevant. + */ + if (!ap_is_HTTP_SERVER_ERROR(r->status) && cid->ecb->dwHttpStatusCode) { + r->status = cid->ecb->dwHttpStatusCode; + } + + /* For all missing-response situations simply return the status, + * and let the core respond to the client. + */ + return r->status; +} + +/********************************************************** + * + * ISAPI Module Setup Hooks + * + **********************************************************/ + +static int isapi_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ + apr_status_t rv; + + apr_pool_create_ex(&loaded.pool, pconf, NULL, NULL); + if (!loaded.pool) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, NULL, APLOGNO(02118) + "could not create the isapi cache pool"); + return APR_EGENERAL; + } + + loaded.hash = apr_hash_make(loaded.pool); + if (!loaded.hash) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(02119) + "Failed to create module cache"); + return APR_EGENERAL; + } + + rv = apr_thread_mutex_create(&loaded.lock, APR_THREAD_MUTEX_DEFAULT, + loaded.pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(02682) + "Failed to create module cache lock"); + return rv; + } + return OK; +} + +static void isapi_hooks(apr_pool_t *cont) +{ + ap_hook_pre_config(isapi_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(isapi_handler, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(isapi) = { + STANDARD20_MODULE_STUFF, + create_isapi_dir_config, /* create per-dir config */ + merge_isapi_dir_configs, /* merge per-dir config */ + NULL, /* server config */ + NULL, /* merge server config */ + isapi_cmds, /* command apr_table_t */ + isapi_hooks /* register hooks */ +}; |