mod_isapi.c revision 3d81f57512275ca06a60a9bcbd23c1f8b429fdf2
/* Copyright 1999-2006 The Apache Software Foundation or its licensors, as
* applicable.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* 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
*
* 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 extentions" 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 */
/**********************************************************
*
* ISAPI Module Configuration
*
**********************************************************/
#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;
typedef struct isapi_loaded isapi_loaded;
{
return dir;
}
{
: add->read_ahead_buflen;
: add->log_unsupported;
: add->log_to_errlog;
? base->log_to_query
: add->log_to_query;
? base->fake_async
: add->fake_async;
return dir;
}
const char *filename)
{
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
* ISAPICacheFile myapp.dll anotherapp.dll thirdapp.dll
* </Directory>
* would be very convienent.
*/
if (!fspec) {
"ISAPI: invalid module path, skipping %s", filename);
return NULL;
}
"ISAPI: unable to stat, skipping %s", fspec);
return NULL;
}
"ISAPI: not a regular file, skipping %s", fspec);
return NULL;
}
/* Load the extention as cached (with null request_rec) */
if (rv != APR_SUCCESS) {
"ISAPI: unable to cache, skipping %s", fspec);
return NULL;
}
return NULL;
}
static const command_rec isapi_cmds[] = {
OR_FILEINFO, "Maximum client request body to initially pass to the"
" ISAPI handler (default: 49152)"),
OR_FILEINFO, "Log requests not supported by the ISAPI server"
" on or off (default: off)"),
OR_FILEINFO, "Send all Append Log requests to the error log"
" on or off (default: off)"),
OR_FILEINFO, "Append Log requests are concatinated to the query args"
" on or off (default: on)"),
OR_FILEINFO, "Fake Asynchronous support for isapi callbacks"
" on or off [Experimental] (default: off)"),
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 {
} loaded;
/* Our loaded isapi module description structure */
struct isapi_loaded {
const char *filename;
};
{
/* 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.)
*/
return APR_SUCCESS;
}
if (isa->TerminateExtension) {
if (force) {
}
return APR_EGENERAL;
}
}
return APR_SUCCESS;
}
{
/* We must force the module to unload, we are about
* to lose the isapi structure's allocation entirely.
*/
}
{
/* 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.
*/
if (rv)
{
return rv;
}
"GetExtensionVersion");
if (rv)
{
"ISAPI: missing GetExtensionVersion() in %s",
return rv;
}
"HttpExtensionProc");
if (rv)
{
"ISAPI: missing HttpExtensionProc() in %s",
return rv;
}
/* TerminateExtension() is an optional interface */
"TerminateExtension");
apr_set_os_error(0);
/* Run GetExtensionVersion() */
"ISAPI: failed call to GetExtensionVersion() in %s",
return rv;
}
return APR_SUCCESS;
}
{
const char *key;
return rv;
}
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.)
*/
/* gainlock is NULLed after the module loads successfully.
* This free-threaded module can be used without any locking.
*/
if (!gainlock) {
return rv;
}
!= APR_SUCCESS) {
return rv;
}
return rv;
}
/* 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_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.
*/
}
else {
}
return rv;
}
/* We haven't hit timeup on retry, let's grab the last_rv
* within the hash mutex before unlocking.
*/
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.
*/
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...
*/
}
/* Now attempt to load the isapi on our own time,
* allow other isapi processing to resume.
*/
if (r && (rv == APR_SUCCESS)) {
/* Let others who are blocked on this particular
* module resume their requests, for better or worse.
*/
}
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.
*/
}
return (*isa)->last_load_rv;
}
/**********************************************************
*
* ISAPI Module request callbacks section
*
**********************************************************/
/* Our "Connection ID" structure */
struct isapi_cid {
request_rec *r;
int headers_set;
int response_sent;
void *completion_arg;
};
char *variable_name,
void *buf_ptr,
{
request_rec *r = cid->r;
const char *result;
{
/* crlf delimited, colon split, comma separated and
* null terminated list of HTTP_ vars
*/
int i;
}
}
return 0;
}
*(buf_data++) = ':';
*(buf_data++) = '\r';
*(buf_data++) = '\n';
}
}
*(buf_data++) = '\0';
return 1;
}
{
/* crlf delimited, colon split, comma separated and
* null terminated list of the raw request header
*/
int i;
}
return 0;
}
*(buf_data++) = ':';
*(buf_data++) = ' ';
*(buf_data++) = '\r';
*(buf_data++) = '\n';
}
*(buf_data++) = '\0';
return 1;
}
/* Not a special case */
if (result) {
return 0;
}
return 1;
}
/* Not Found */
return 0;
}
void *buf_data,
{
request_rec *r = cid->r;
apr_uint32_t read = 0;
int res;
}
}
if (res < 0) {
}
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
* get a proper count of bytes consumed. The argument passed to stat
* isn't counted as the head bytes are.
*/
const char *stat,
const char *head,
{
int head_present = 1;
int termarg;
const char *termch;
apr_size_t ate = 0;
statlen = 0;
head_present = 0; /* Don't eat the header */
}
stat = "Status: 200 OK";
}
else {
}
}
}
char *newstat;
if (!apr_isdigit(*stat)) {
}
}
/* Now decide if we follow the xxx message
*/
}
}
statlen += 8;
}
head = "\r\n";
headlen = 2;
}
else
{
/* Whoops... not NULL terminated */
}
}
/* 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
*/
if (stat) {
}
else {
/* We tried every way to Sunday to get the status...
* so 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.
*/
}
else {
}
}
return -1;
}
/* If only Status was passed, we consumed nothing
*/
if (!head_present)
return 0;
/* If all went well, tell the caller we consumed the headers complete
*/
if (!termch)
/* 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.
*/
}
return ate;
}
void *buf_ptr,
{
request_rec *r = cid->r;
conn_rec *c = r->connection;
apr_bucket *b;
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.
*/
if (ate < 0) {
return 0;
}
}
if (buf_size) {
b = apr_bucket_flush_create(c->bucket_alloc);
}
}
else {
}
}
}
void *buf_ptr,
{
request_rec *r = cid->r;
conn_rec *c = r->connection;
switch (HSE_code) {
/* 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 equivalant per the Jan 2000 SDK.
* They most definately are not, even in their own samples.
*/
return 1;
case HSE_REQ_SEND_URL:
/* Soak up remaining input */
if (r->remaining > 0) {
char argsbuffer[HUGE_STRING_LEN];
}
/* Reset the method to GET */
r->method_number = M_GET;
/* Don't let anyone think there's still data */
/* AV fault per PR3598 - redirected path is lost! */
return 1;
{
/* Parse them out, or die trying */
if (buf_data)
if (data_type)
(char*) data_type,
if (ate < 0) {
return 0;
}
apr_bucket *b;
b = apr_bucket_flush_create(c->bucket_alloc);
}
return 1;
}
/* Signal to resume the thread completing this request,
* leave it to the pool cleanup to dispose of our mutex.
*/
return 1;
}
"ISAPI: ServerSupportFunction "
"HSE_REQ_DONE_WITH_SESSION is not supported: %s",
r->filename);
}
return 0;
case HSE_REQ_MAP_URL_TO_PATH:
{
/* Map a URL to a filename */
r, NULL);
/* IIS puts a trailing slash on directories, Apache doesn't */
}
}
return 1;
}
case HSE_REQ_GET_SSPI_INFO:
"ISAPI: ServerSupportFunction HSE_REQ_GET_SSPI_INFO "
"is not supported: %s", r->filename);
return 0;
case HSE_APPEND_LOG_PARAMETER:
/* Log buf_data, of buf_size bytes, in the URI Query (cs-uri-query) field
*/
if (r->args)
else
}
(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.
*/
return 1;
}
"ISAPI: ServerSupportFunction HSE_REQ_IO_COMPLETION "
"is not supported: %s", r->filename);
return 0;
case HSE_REQ_TRANSMIT_FILE:
{
/* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND)
*/
apr_uint32_t sent = 0;
apr_ssize_t ate = 0;
apr_bucket *b;
apr_file_t *fd;
"ISAPI: ServerSupportFunction HSE_REQ_TRANSMIT_FILE "
"as HSE_IO_ASYNC is not supported: %s", r->filename);
return 0;
}
/* Presume the handle was opened with the CORRECT semantics
* for TransmitFile
*/
!= APR_SUCCESS) {
return 0;
}
if (tf->BytesToWrite) {
}
else {
return 0;
}
}
/* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */
/* 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).
*/
tf->HeadLength);
}
0, tf->HeadLength);
if (ate < 0)
{
return 0;
}
}
c->bucket_alloc);
}
/* APR_HAS_LARGE_FILES issue; must split into mutiple buckets,
* no greater than MAX(apr_size_t), and more granular than that
*/
r->pool, c->bucket_alloc);
while (fsize > AP_MAX_SENDFILE) {
apr_bucket *bc;
apr_bucket_copy(b, &bc);
b->start += AP_MAX_SENDFILE;
fsize -= AP_MAX_SENDFILE;
}
}
else
#endif
r->pool, c->bucket_alloc);
}
b = apr_bucket_flush_create(c->bucket_alloc);
/* Use tf->pfnHseIO + tf->pContext, or if NULL, then use cid->fnIOComplete
* pass pContect to the HseIO callback.
*/
}
else {
}
}
else if (cid->completion) {
}
else {
}
}
}
}
"ISAPI: ServerSupportFunction "
"HSE_REQ_REFRESH_ISAPI_ACL "
"is not supported: %s", r->filename);
return 0;
case HSE_REQ_IS_KEEP_CONN:
return 1;
{
apr_uint32_t read = 0;
int res;
"ISAPI: asynchronous I/O not supported: %s",
r->filename);
return 0;
}
}
}
if (res >= 0) {
}
else {
}
}
return (res >= 0);
}
case HSE_REQ_GET_IMPERSONATION_TOKEN: /* Added in ISAPI 4.0 */
"ISAPI: ServerSupportFunction "
"HSE_REQ_GET_IMPERSONATION_TOKEN "
"is not supported: %s", r->filename);
return 0;
{
/* Map a URL to a filename */
/* Mapping started with assuming both strings matched.
* Now roll on the path_info as a mismatch and handle
* terminating slashes for directory matches.
*/
/* roll forward over path_info's first slash */
++info->cchMatchingPath;
++info->cchMatchingURL;
}
}
/* Add a trailing slash for directory */
}
/* If the matched isn't a file, roll match back to the prior slash */
break;
--info->cchMatchingPath;
--info->cchMatchingURL;
}
}
/* Paths returned with back slashes */
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.
*/
return 1;
}
case HSE_REQ_ABORTIVE_CLOSE:
"ISAPI: ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE"
" is not supported: %s", r->filename);
return 0;
case HSE_REQ_GET_CERT_INFO_EX: /* Added in ISAPI 4.0 */
"ISAPI: ServerSupportFunction "
"HSE_REQ_GET_CERT_INFO_EX "
"is not supported: %s", r->filename);
return 0;
case HSE_REQ_SEND_RESPONSE_HEADER_EX: /* Added in ISAPI 4.0 */
{
/* Ignore shi->fKeepConn - we don't want the advise
*/
if (ate < 0) {
return 0;
}
apr_bucket *b;
c->bucket_alloc);
b = apr_bucket_flush_create(c->bucket_alloc);
}
return 1;
}
case HSE_REQ_CLOSE_CONNECTION: /* Added after ISAPI 4.0 */
"ISAPI: ServerSupportFunction "
"HSE_REQ_CLOSE_CONNECTION "
"is not supported: %s", r->filename);
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
*/
return 1;
case HSE_REQ_EXTENSION_TRIGGER: /* Added after ISAPI 4.0 */
/* Undocumented - defined by the Microsoft Jan '00 Platform SDK
*/
"ISAPI: ServerSupportFunction "
"HSE_REQ_EXTENSION_TRIGGER "
"is not supported: %s", r->filename);
return 0;
default:
"ISAPI: ServerSupportFunction (%d) not supported: "
return 0;
}
}
/**********************************************************
*
* ISAPI Module request invocation section
*
**********************************************************/
{
apr_table_t *e;
const char *val;
int res;
/* 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;
}
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;
}
return HTTP_NOT_FOUND;
}
return HTTP_FORBIDDEN;
}
if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
/* default to accept */
return HTTP_NOT_FOUND;
}
!= APR_SUCCESS) {
return HTTP_INTERNAL_SERVER_ERROR;
}
/* Set up variables */
ap_add_cgi_vars(r);
else
/* Set up connection structure and ecb,
* NULL or zero out most fields.
*/
/* Fixup defaults for dconf */
? 0 : dconf->log_unsupported;
? 0 : dconf->log_to_errlog;
? 0 : dconf->fake_async;
cid->r = r;
r->status = 0;
/* TODO: are copies really needed here?
*/
/* Set up the callbacks */
/* Set up client input */
if (res) {
isapi_unload(isa, 0);
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) {
else
}
else
{
}
read = 0;
}
if (res < 0) {
isapi_unload(isa, 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)
else
}
else {
}
/* 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.
*/
r->pool);
}
"ISAPI: Failed to create completion mutex");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
/* All right... try and run the sucker */
/* Check for a log message - and log it */
switch(rv) {
case 0: /* Strange, but MS isapi accepts this as success */
case HSE_STATUS_SUCCESS:
/* 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...
*/
/* 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.
*/
break;
}
"ISAPI: asynch I/O result HSE_STATUS_PENDING "
"from HttpExtensionProc() is not supported: %s",
r->filename);
}
break;
case HSE_STATUS_ERROR:
/* end response if we have yet to do so.
*/
break;
default:
/* TODO: log unrecognized retval for debugging
*/
"ISAPI: return code %d from HttpExtensionProc() "
"was not not recognized", rv);
break;
}
/* Flush the response now, including headers-only responses */
if (cid->headers_set) {
conn_rec *c = r->connection;
apr_bucket *b;
b = apr_bucket_eos_create(c->bucket_alloc);
b = apr_bucket_flush_create(c->bucket_alloc);
return OK; /* NOT r->status or cid->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.
*/
}
/* For all missing-response situations simply return the status.
* and let the core deal respond to the client.
*/
return r->status;
}
/**********************************************************
*
* ISAPI Module Setup Hooks
*
**********************************************************/
{
"ISAPI: could not create the isapi cache pool");
return APR_EGENERAL;
}
"ISAPI: Failed to create module cache");
return APR_EGENERAL;
}
if (rv != APR_SUCCESS) {
"ISAPI: Failed to create module cache lock");
return rv;
}
return OK;
}
{
}
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 */
};