mod_isapi.c revision 060a12fa8d0071c4f1206f5b6cf3cc59e8b90226
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/
/*
* mod_isapi.c - Internet Server Application (ISA) module for Apache
* by Alexei Kosut <akosut@apache.org>
*
* This module implements Microsoft's ISAPI, allowing Apache (when running
* under Windows) to load Internet Server Applications (ISAPI extensions).
* It implements all of the ISAPI 2.0 specification, except for the
* "Microsoft-only" extensions dealing with asynchronous I/O. All ISAPI
* extensions that use only synchronous I/O and are compatible with the
* ISAPI 2.0 specification should work (most ISAPI 1.0 extensions should
* function as well).
*
* To load, simply place the ISA in a location in the document tree.
* Then add an "AddHandler isapi-isa dll" into your config file.
* You should now be able to load ISAPI DLLs just be reffering to their
* URLs. Make sure the ExecCGI option is active in the directory
* the ISA is in.
*/
#include "apr_strings.h"
#include "apr_portable.h"
#include "apr_buckets.h"
#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"
/* We use the exact same header file as the original */
#include <HttpExt.h>
#if !defined(HSE_REQ_MAP_URL_TO_PATH_EX) \
|| !defined(HSE_REQ_SEND_RESPONSE_HEADER_EX)
#pragma message("WARNING: This build of Apache is missing the recent changes")
#pragma message("in the Microsoft Win32 Platform SDK; some mod_isapi features")
#pragma message("will be disabled. To obtain the latest Platform SDK files,")
#pragma message("please refer to:")
#endif
/**********************************************************
*
* ISAPI Module Configuration
*
**********************************************************/
/* Our isapi server config structure */
typedef struct {
int log_unsupported;
int log_to_errlog;
int log_to_query;
typedef struct isapi_loaded isapi_loaded;
request_rec *r, const char *fpath,
isapi_loaded** isa);
{
sconf->log_to_errlog = 0;
sconf->log_to_query = 0;
return sconf;
}
/*
* Command handler for the ISAPIread_ahead_buflen directive, which is TAKE1
*/
char *arg)
{
&isapi_module);
char *scan;
long val;
return "ISAPIread_ahead_buflen must be a legitimate value.";
return NULL;
}
/*
* Command handler for the ISAPIread_ahead_buflen directive, which is TAKE1
*/
char *arg)
{
&isapi_module);
}
sconf->log_unsupported = 0;
}
else {
return "ISAPIlog_unsupported must be on or off";
}
return NULL;
}
char *arg)
{
&isapi_module);
}
sconf->log_to_errlog = 0;
}
else {
return "ISAPIlog_to_errlog must be on or off";
}
return NULL;
}
char *arg)
{
&isapi_module);
}
sconf->log_to_query = 0;
}
else {
return "ISAPIlog_to_query must be on or off";
}
return NULL;
}
const char *filename)
{
&isapi_module);
char *fspec;
if (!fspec) {
"ISAPI: Invalid module path %s, skipping", filename);
return NULL;
}
"ISAPI: unable to stat(%s), skipping", fspec);
return NULL;
}
"ISAPI: %s isn't a regular file, skipping", fspec);
return NULL;
}
/* Load the extention as cached (passing sconf) */
if (rv != APR_SUCCESS) {
"ISAPI: unable to cache %s, skipping", fspec);
return NULL;
}
/* Add to cached list of loaded modules */
return NULL;
}
static const command_rec isapi_cmds[] = {
RSRC_CONF, "Maximum bytes to initially pass to the ISAPI handler"),
RSRC_CONF, "Log requests not supported by the ISAPI server"),
RSRC_CONF, "Send all Append Log requests to the error log"),
RSRC_CONF, "Append Log requests are concatinated to the query args"),
RSRC_CONF, "Cache the specified ISAPI extension in-process"),
{ NULL }
};
/**********************************************************
*
* ISAPI Module Cache handling section
*
**********************************************************/
/* Our loaded isapi module description structure */
struct isapi_loaded {
const char *filename;
int refcount;
};
{
/* All done with the DLL... get rid of it...
*
* If optionally cached, pass HSE_TERM_ADVISORY_UNLOAD,
* and if it returns TRUE, unload, otherwise, cache it.
*/
return APR_SUCCESS;
if (isa->TerminateExtension) {
if (force)
return APR_EGENERAL;
}
return APR_SUCCESS;
}
{
}
request_rec *r, const char *fpath,
isapi_loaded** isa)
{
int n;
break;
}
++found;
}
{
{
return APR_SUCCESS;
}
/* Otherwise we fall through and have to reload the resource
* into this existing mod_isapi cache bucket.
*/
}
else
{
/* TODO: These need to become overrideable, so that we
* assure a given isapi can be fooled into behaving well.
*/
}
if (rv)
{
"ISAPI: %s failed to load", fpath);
return rv;
}
"GetExtensionVersion");
if (rv)
{
"ISAPI: %s is missing GetExtensionVersion()",
fpath);
return rv;
}
"HttpExtensionProc");
if (rv)
{
"ISAPI: %s is missing HttpExtensionProc()",
fpath);
return rv;
}
/* TerminateExtension() is an optional interface */
"TerminateExtension");
SetLastError(0);
/* Run GetExtensionVersion() */
"ISAPI: %s call GetExtensionVersion() failed",
fpath);
return rv;
}
return APR_SUCCESS;
}
/**********************************************************
*
* ISAPI Module request callbacks section
*
**********************************************************/
/* Our "Connection ID" structure */
typedef struct isapi_cid {
request_rec *r;
} isapi_cid;
{
const char *result;
{
/* lf delimited, colon split, comma seperated and
* null terminated list of HTTP_ vars
*/
int i;
}
}
return FALSE;
}
*(((char*)lpvBuffer)++) = ':';
*(((char*)lpvBuffer)++) = '\n';
}
}
*(((char*)lpvBuffer)++) = '\0';
*lpdwSizeofBuffer = len;
return TRUE;
}
{
/* lf delimited, colon split, comma seperated and
* null terminated list of the raw request header
*/
int i;
}
return FALSE;
}
*(((char*)lpvBuffer)++) = ':';
*(((char*)lpvBuffer)++) = ' ';
*(((char*)lpvBuffer)++) = '\n';
}
*(((char*)lpvBuffer)++) = '\0';
*lpdwSizeofBuffer = len;
return TRUE;
}
/* Not a special case */
if (result) {
return FALSE;
}
*lpdwSizeofBuffer = len;
return TRUE;
}
/* Not Found */
return FALSE;
}
{
conn_rec *c = r->connection;
apr_bucket *b;
if (dwReserved == HSE_IO_SYNC)
; /* XXX: Fake it */
b = apr_bucket_flush_create(c->bucket_alloc);
return TRUE;
}
{
int res;
}
}
return TRUE;
}
{
int termarg;
char *termch;
stat = "Status: 200 OK";
}
else {
char *newstat;
}
head = "\r\n";
}
else
{
/* Whoops... not NULL terminated */
}
}
/* Parse them out, or die trying */
return -1;
/* Headers will actually go when they are good and ready */
/* If all went well, tell the caller we consumed the headers complete */
if (!termch)
return(headlen);
/* Any data left is sent directly by the caller, all we
* give back is the size of the headers we consumed
*/
}
return 0;
}
{
request_rec *r = cid->r;
conn_rec *c = r->connection;
switch (dwHSERequest) {
case 1: /* 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 equivalant per the Jan 2000 SDK.
* They most definately are not, even in their own samples.
*/
return TRUE;
case 2: /* 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! */
ap_internal_redirect((char*)lpvBuffer, r);
return TRUE;
case 3: /* HSE_REQ_SEND_RESPONSE_HEADER */
{
/* Parse them out, or die trying */
if (lpvBuffer)
if (lpdwDataType)
(char*) lpdwDataType,
if (ate < 0) {
return FALSE;
}
apr_bucket *b;
b = apr_bucket_flush_create(c->bucket_alloc);
}
return TRUE;
}
case 4: /* HSE_REQ_DONE_WITH_SESSION */
/* Signal to resume the thread completing this request
*/
return TRUE;
case 1001: /* 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 TRUE;
}
case 1002: /* HSE_REQ_GET_SSPI_INFO */
"ISAPI: ServerSupportFunction HSE_REQ_GET_SSPI_INFO "
"is not supported: %s", r->filename);
return FALSE;
case 1003: /* HSE_APPEND_LOG_PARAMETER */
/* Log lpvBuffer, of lpdwSize bytes, in the URI Query (cs-uri-query) field
*/
if (r->args)
else
}
(char*) lpvBuffer);
return TRUE;
case 1005: /* 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 lpvBuffer may be set to NULL.
*/
"ISAPI: ServerSupportFunction HSE_REQ_IO_COMPLETION "
"is not supported: %s", r->filename);
return FALSE;
}
return TRUE;
case 1006: /* HSE_REQ_TRANSMIT_FILE */
{
apr_bucket *b;
apr_file_t *fd;
"ISAPI: ServerSupportFunction HSE_REQ_TRANSMIT_FILE "
"as HSE_IO_ASYNC is not supported: %s", r->filename);
return FALSE;
}
return FALSE;
}
/* 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);
if (ate < 0)
{
return FALSE;
}
{
c->bucket_alloc);
}
}
tf->HeadLength,
c->bucket_alloc);
}
}
b = apr_bucket_flush_create(c->bucket_alloc);
/* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND)
*/
/* XXX: Fake async response,
* use tf->pfnHseIO, or if NULL, then use cid->fnIOComplete
* pass pContect to the HseIO callback.
*/
}
return TRUE;
}
case 1007: /* HSE_REQ_REFRESH_ISAPI_ACL */
"ISAPI: ServerSupportFunction "
"HSE_REQ_REFRESH_ISAPI_ACL "
"is not supported: %s", r->filename);
return FALSE;
case 1008: /* HSE_REQ_IS_KEEP_CONN */
return TRUE;
case 1010: /* XXX: Fake it : HSE_REQ_ASYNC_READ_CLIENT */
"ISAPI: asynchronous I/O not supported: %s",
r->filename);
return FALSE;
case 1011: /* HSE_REQ_GET_IMPERSONATION_TOKEN Added in ISAPI 4.0 */
"ISAPI: ServerSupportFunction "
"HSE_REQ_GET_IMPERSONATION_TOKEN "
"is not supported: %s", r->filename);
return FALSE;
#ifdef HSE_REQ_MAP_URL_TO_PATH_EX
case 1012: /* HSE_REQ_MAP_URL_TO_PATH_EX */
{
/* 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 TRUE;
}
#endif
case 1014: /* HSE_REQ_ABORTIVE_CLOSE */
"ISAPI: ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE"
" is not supported: %s", r->filename);
return FALSE;
case 1015: /* 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 FALSE;
case 1016: /* HSE_REQ_SEND_RESPONSE_HEADER_EX Added in ISAPI 4.0 */
{
/* XXX: ignore shi->fKeepConn? We shouldn't need the advise */
/* r->connection->keepalive = shi->fKeepConn; */
if (ate < 0) {
return FALSE;
}
apr_bucket *b;
c->bucket_alloc);
b = apr_bucket_flush_create(c->bucket_alloc);
}
return TRUE;
}
#endif
case 1017: /* HSE_REQ_CLOSE_CONNECTION Added after ISAPI 4.0 */
"ISAPI: ServerSupportFunction "
"HSE_REQ_CLOSE_CONNECTION "
"is not supported: %s", r->filename);
return FALSE;
case 1018: /* 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 TRUE;
case 1020: /* 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 FALSE;
default:
"ISAPI: ServerSupportFunction (%d) not supported: "
return FALSE;
}
}
/**********************************************************
*
* ISAPI Module request invocation section
*
**********************************************************/
{
apr_table_t *e;
const char *val;
int res;
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;
}
/* Load the isapi extention without caching (sconf == NULL)
* but note that we will recover an existing cached module.
*/
return HTTP_INTERNAL_SERVER_ERROR;
/* Set up variables */
ap_add_cgi_vars(r);
else
/* Set up connection structure and ecb */
cid->r = r;
// TODO: are copies really needed here?
(char*) apr_table_get(e, "QUERY_STRING"));
(char*) apr_table_get(e, "PATH_INFO"));
(char*) apr_table_get(e, "PATH_TRANSLATED"));
(char*) apr_table_get(e, "CONTENT_TYPE"));
/* Set up the callbacks */
/* Set up client input */
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
* TODO: add the httpd.conf option for read_ahead_buflen.
*/
if (r->remaining) {
else
}
else
{
}
read = 0;
}
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)
else
}
else {
}
/* 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 ISA'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...
*
* Create a cid->completed event and wait on it for some timeout
* so that the app thinks is it running async.
*
* All async ServerSupportFunction calls will be handled through
* the registered IO_COMPLETION hook.
*/
{
"ISAPI: %s asynch I/O request refused",
r->filename);
}
}
else {
== WAIT_TIMEOUT) {
/* TODO: Now what... if this hung, then do we kill our own
* thread to force its death? For now leave timeout = -1
*/
}
}
break;
case HSE_STATUS_ERROR:
/* end response if we have yet to do so.
*/
break;
default:
/* TODO: log unrecognized retval for debugging
*/
break;
}
/* Set the status (for logging) */
}
/* All done with the DLL... get rid of it... */
return OK; /* NOT r->status, even if it has changed. */
}
/**********************************************************
*
* ISAPI Module Setup Hooks
*
**********************************************************/
{
const isapi_loaded **a = av;
const isapi_loaded **b = bv;
}
{
&isapi_module);
/* sort the elements of the main_server, by filename */
/* and make all virtualhosts share the same */
}
return OK;
}
{
}
NULL, /* create per-dir config */
NULL, /* merge per-dir config */
create_isapi_server_config, /* server config */
NULL, /* merge server config */
isapi_cmds, /* command apr_table_t */
isapi_hooks /* register hooks */
};