You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Randy Terbush <ra...@zyzzyva.com> on 1997/07/22 05:41:13 UTC

Re: mod_isapi

+1

Sounds very cool.


> I sent this to the list a week ago, but have seen no (or little)
> response, so I'll send it again; the first time was buried in a larger
> email about something else, people may not have read it.
> 
> Enclosed is a module I wrote that allows ISAPI Extensions (aka ISAs) to
> be run on Windows with Apache. Internet Server Applications are DLLs that
> use the ISAPI (Internet Server API) to talk to a web server. They
> basically function as a CGI replacement, and do little else. This module
> implements all of the ISAPI 2.0 specification except for a few
> Microsoft-specific extensions that IIS has, mostly allowing for
> asynchronous I/O. Since Apache's I/O is not multithreaded as IIS' is,
> this is nearly impossible with Apache. Other than that, the entire spec
> is implemented, and the number of ISAs that I've tried seem to work.
> 
> I'd like to see this module tossed in nt/ and included with Apache 1.3
> (maybe even 1.3a1). ISAPI support is one part of getting Apache used in
> the Windows world, as it allows them to use any ISAs they might be used
> to using with Windows servers. So I think we should include it as part of
> the default Apache installation on Windows
> 
> A few more technical notes: There is one other deviation from the ISAPI
> spec; IIS puts only 48k of any input data into the
> EXTENSION_CONTROL_BLOCK structure; the rest must be read with
> ReadClient(). Apache (like WebSite) reads in all the incoming data and
> makes it all available. It's easier, and seems to work.
> 
> IIS, being multithreaded through and through, loads an ISA the first time
> it is called, and keeps it loaded until it decides it needs to get rid of
> the memory, and unloads it. Apache has no mechanism to do that properly,
> so I have the module load and unload the DLL for each request. It's still
> faster than CGI.
> 
> Also note that while this module does implement the ISAPI Extension spec,
> it does not do ISAPI Filters. Filters are more like Apache modules, they
> are loaded at config time, and can do things like map URLs to filenames,
> processes headers, handle logging. They can also do things like
> encryption. Implementing ISAPi Filters would be, IMHO, more trouble than
> it would be worth, and would probably involve a lot of hacking into
> Apache's core.
> 
> Another issue is security: I have it currently so that ISAs have the same
> access restrictions as CGIs (Options ExecCGI). This makes sense to me;
> they do similar things, and are both basically executable
> content. Although ISAs are loaded into the server's process space, while
> CGIs are not, they cannot access the Apache API, and so they are just as
> safe. Although a poorly-programmed or malicious ISA can crash its parent
> process, so can a CGI, and Apache will spawn more children.
> 
> Anyhow, here's mod_isapi.c:
> 
> /* ====================================================================
>  * Copyright (c) 1995-1997 The Apache Group.  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
>  *    the documentation and/or other materials provided with the
>  *    distribution.
>  *
>  * 3. All advertising materials mentioning features or use of this
>  *    software must display the following acknowledgment:
>  *    "This product includes software developed by the Apache Group
>  *    for use in the Apache HTTP server project (http://www.apache.org/)."
>  *
>  * 4. The names "Apache Server" and "Apache Group" must not be used to
>  *    endorse or promote products derived from this software without
>  *    prior written permission.
>  *
>  * 5. Redistributions of any form whatsoever must retain the following
>  *    acknowledgment:
>  *    "This product includes software developed by the Apache Group
>  *    for use in the Apache HTTP server project (http://www.apache.org/)."
>  *
>  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``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 GROUP 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 Group and was originally based
>  * on public domain software written at the National Center for
>  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
>  * For more information on the Apache Group and the Apache HTTP server
>  * project, please see <http://www.apache.org/>.
>  *
>  */
> 
> /*
>  * mod_isapi.c - Internet Server Application (ISA) module for Apache
>  * by Alexei Kosut <ak...@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 "../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"
> 
> /* We use the exact same header file as the original */
> #include <HttpExt.h>
> 
> module isapi_module;
> 
> /* Our "Connection ID" structure */
> 
> typedef struct {
>     LPEXTENSION_CONTROL_BLOCK ecb;
>     request_rec *r;
>     int status;
> } isapi_cid;
> 
> /* Declare the ISAPI functions */
> 
> BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
> 			       LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer);
> BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpwdwBytes,
> 			 DWORD dwReserved);
> BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize);
> BOOL WINAPI ServerSupportFunction (HCONN hConn, DWORD dwHSERequest,
> 				   LPVOID lpvBuffer, LPDWORD lpdwSize,
> 				   LPDWORD lpdwDataType);
> 
> int isapi_handler (request_rec *r) {
>     LPEXTENSION_CONTROL_BLOCK ecb =
> 	pcalloc(r->pool, sizeof(struct _EXTENSION_CONTROL_BLOCK));
>     HSE_VERSION_INFO *pVer = pcalloc(r->pool, sizeof(HSE_VERSION_INFO));
> 
>     HINSTANCE isapi_handle;
>     BOOL (*isapi_version)(HSE_VERSION_INFO *); /* entry point 1 */
>     DWORD (*isapi_entry)(LPEXTENSION_CONTROL_BLOCK); /* entry point 2 */
>     BOOL (*isapi_term)(DWORD); /* optional entry point 3 */
> 
>     isapi_cid *cid = pcalloc(r->pool, sizeof(isapi_cid));
>     table *e = r->subprocess_env;
>     int retval;
> 
>     /* Use similar restrictions as CGIs */
> 
>     if (!(allow_options(r) & OPT_EXECCGI))
> 	return FORBIDDEN;
> 
>     if (S_ISDIR(r->finfo.st_mode))
> 	return FORBIDDEN;
> 
>     if (r->finfo.st_mode == 0)
> 	return NOT_FOUND;
> 
>     /* Load the module */
> 
>     if (!(isapi_handle = LoadLibraryEx(r->filename, NULL,
> 				       LOAD_WITH_ALTERED_SEARCH_PATH))) {
> 	log_reason("Could not load DLL", r->filename, r);
> 	return SERVER_ERROR;
>     }
> 
>     if (!(isapi_version =
> 	  (void *)(GetProcAddress(isapi_handle, "GetExtensionVersion")))) {
> 	log_reason("DLL could not load GetExtensionVersion()", r->filename, r);
> 	FreeLibrary(isapi_handle);
> 	return SERVER_ERROR;
>     }
> 
>     if (!(isapi_entry =
> 	  (void *)(GetProcAddress(isapi_handle, "HttpExtensionProc")))) {
> 	log_reason("DLL could not load HttpExtensionProc()", r->filename, r);
> 	FreeLibrary(isapi_handle);
> 	return SERVER_ERROR;
>     }
> 
>     isapi_term = (void *)(GetProcAddress(isapi_handle, "TerminateExtension"));
> 
>     /* Run GetExtensionVersion() */
> 
>     if ((*isapi_version)(pVer) != TRUE) {
> 	log_reason("ISAPI GetExtensionVersion() failed", r->filename, r);
> 	FreeLibrary(isapi_handle);
> 	return SERVER_ERROR;
>     }
> 
>     /* Set up variables */
>     add_common_vars(r);
>     add_cgi_vars(r);
> 
>     /* Set up connection ID */
>     ecb->ConnID = (HCONN)cid;
>     cid->ecb = ecb;
>     cid->r = r;
>     cid->status = 0;
> 
>     ecb->cbSize = sizeof(struct _EXTENSION_CONTROL_BLOCK);
>     ecb->dwVersion = MAKELONG(0, 2);
>     ecb->dwHttpStatusCode = 0;
>     strcpy(ecb->lpszLogData, "");
>     ecb->lpszMethod = r->method;
>     ecb->lpszQueryString = table_get(e, "QUERY_STRING");
>     ecb->lpszPathInfo = table_get(e, "PATH_INFO");
>     ecb->lpszPathTranslated = table_get(e, "PATH_TRANSLATED");
>     ecb->lpszContentType = table_get(e, "CONTENT_TYPE");
> 
>     /* Set up client input */
>     if ((retval = setup_client_block(r, REQUEST_CHUNKED_ERROR))) {
> 	if (isapi_term) (*isapi_term)(HSE_TERM_MUST_UNLOAD);
> 	FreeLibrary(isapi_handle);
> 	return retval;
>     }
> 
>     if (should_client_block(r)) {
> 	/* Unlike IIS, which limits this to 48k, we read the whole
> 	 * sucker in. I suppose this could be bad for memory if someone
> 	 * uploaded the complete works of Shakespear. Well, WebSite
> 	 * does the same thing.
> 	 */
> 	long to_read = atol(table_get(e, "CONTENT_LENGTH"));
> 	long read;
> 
> 	ecb->lpbData = pcalloc(r->pool, 1 + to_read);
> 
> 	if ((read = get_client_block(r, ecb->lpbData, to_read)) < 0) {
> 	    if (isapi_term) (*isapi_term)(HSE_TERM_MUST_UNLOAD);
> 	    FreeLibrary(isapi_handle);
> 	    return SERVER_ERROR;
> 	}
> 
> 	/* Although its not to spec, IIS seems to null-terminate
> 	 * its lpdData string. So we will too. To make sure
> 	 * cbAvailable matches cbTotalBytes, we'll up the latter
> 	 * and equalize them.
> 	 */
> 	ecb->cbAvailable = ecb->cbTotalBytes = read + 1;
> 	ecb->lpbData[read] = '\0';
> 	}
>     else {
> 	ecb->cbTotalBytes = 0;
> 	ecb->cbAvailable = 0;
> 	ecb->lpbData = NULL;
>     }
> 
>     /* Set up the callbacks */
> 
>     ecb->GetServerVariable = &GetServerVariable;
>     ecb->WriteClient = &WriteClient;
>     ecb->ReadClient = &ReadClient;
>     ecb->ServerSupportFunction = &ServerSupportFunction;
> 
>     /* All right... try and load the sucker */
>     retval = (*isapi_entry)(ecb);
> 
>     /* Set the status (for logging) */
>     if (ecb->dwHttpStatusCode)
> 	r->status = ecb->dwHttpStatusCode;
> 
>     /* Check for a log message - and log it */
>     if (ecb->lpszLogData && strcmp(ecb->lpszLogData, ""))
> 	log_reason(ecb->lpszLogData, r->filename, r);
> 
>     /* All done with the DLL... get rid of it */
>     if (isapi_term) (*isapi_term)(HSE_TERM_MUST_UNLOAD);
>     FreeLibrary(isapi_handle);
> 
>     switch(retval) {
>     case HSE_STATUS_SUCCESS:
>     case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
> 	/* Ignore the keepalive stuff; Apache handles it just fine without
> 	 * the ISA's "advice".
> 	 */
> 
> 	if (cid->status) /* We have a special status to return */
> 	    return cid->status;
> 
> 	return OK;
>     case HSE_STATUS_PENDING:	/* We don't support this */
> 	log_reason("ISAPI asynchronous I/O not supported", r->filename, r);
>     case HSE_STATUS_ERROR:
>     default:
> 	return SERVER_ERROR;
>     }
> 
> }
> 
> BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
> 			       LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer) {
>     request_rec *r = ((isapi_cid *)hConn)->r;
>     table *e = r->subprocess_env;
>     char *result;
>     
>     /* Mostly, we just grab it from the environment, but there are
>      * a couple of special cases
>      */
> 
>     if (!strcasecmp(lpszVariableName, "UNMAPPED_REMOTE_USER")) {
> 	/* We don't support NT users, so this is always the same as
> 	 * REMOTE_USER
> 	 */
> 	result = table_get(e, "REMOTE_USER");
>     }
>     else if (!strcasecmp(lpszVariableName, "SERVER_PORT_SECURE")) {
> 	/* Apache doesn't support secure requests inherently, so
> 	 * we have no way of knowing. We'll be conservative, and say
> 	 * all requests are insecure.
> 	 */
> 	result = "0";
>     }
>     else if (!strcasecmp(lpszVariableName, "URL")) {
> 	result = r->uri;
>     }
>     else {
> 	result = table_get(e, lpszVariableName);
>     }
> 
>     if (result) {
> 	if (strlen(result) > *lpdwSizeofBuffer) {
> 	    *lpdwSizeofBuffer = strlen(result);
> 	    SetLastError(ERROR_INSUFFICIENT_BUFFER);
> 	    return FALSE;
> 	}
> 	strncpy(lpvBuffer, result, *lpdwSizeofBuffer);
> 	return TRUE;
>     }
> 
>     /* Didn't find it */
>     SetLastError(ERROR_INVALID_INDEX);
>     return FALSE;
> }
> 
> BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpwdwBytes,
> 			 DWORD dwReserved) {
>     request_rec *r = ((isapi_cid *)ConnID)->r;
>     int writ;	/* written, actually, but why shouldn't I make up words? */
> 
>     /* We only support synchronous writing */
>     if (dwReserved && dwReserved != HSE_IO_SYNC) {
> 	log_reason("ISAPI asynchronous I/O not supported", r->filename, r);
> 	SetLastError(ERROR_INVALID_PARAMETER);
> 	return FALSE;
>     }
> 
>     if ((writ = rwrite(Buffer, *lpwdwBytes, r)) == EOF) {
> 	SetLastError(ERROR); /* XXX: Find the right error code */
> 	return FALSE;
>     }
>    
>     *lpwdwBytes = writ;
>     return TRUE;
> }
> 
> BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize) {
>     /* Doesn't need to do anything; we've read all the data already */
>     return TRUE;
> }
> 
> BOOL WINAPI ServerSupportFunction (HCONN hConn, DWORD dwHSERequest,
> 				   LPVOID lpvBuffer, LPDWORD lpdwSize,
> 				   LPDWORD lpdwDataType) {
>     isapi_cid *cid = (isapi_cid *)hConn;
>     request_rec *subreq, *r = cid->r;
>     char *data;
> 
>     switch (dwHSERequest) {
>     case HSE_REQ_SEND_URL_REDIRECT_RESP:
> 	/* Set the status to be returned when the HttpExtensionProc()
> 	 * is done.
> 	 */
> 	table_set (r->headers_out, "Location", lpvBuffer);
> 	cid->status = cid->r->status = cid->ecb->dwHttpStatusCode = REDIRECT;
> 	return TRUE;
> 
>     case HSE_REQ_SEND_URL:
> 	/* Read any additional input */
> 
> 	if (r->remaining > 0) {
> 	    char argsbuffer[HUGE_STRING_LEN];
> 
> 	    while (get_client_block(r, argsbuffer, HUGE_STRING_LEN));
> 	}
> 	
> 	/* Reset the method to GET */
> 	r->method = pstrdup(r->pool, "GET");
> 	r->method_number = M_GET;
> 
> 	/* Don't let anyone think there's still data */
> 	table_unset(r->headers_in, "Content-Length");
> 	
> 	internal_redirect((char *)lpvBuffer, r);	
> 	return TRUE;
> 
>     case HSE_REQ_SEND_RESPONSE_HEADER:
> 	r->status_line = lpvBuffer ? lpvBuffer : pstrdup(r->pool, "200 OK");
> 	sscanf(r->status_line, "%d", &r->status);
> 	cid->ecb->dwHttpStatusCode = r->status;
> 
> 	/* Now fill in the HTTP headers, and the rest of it. Ick.
> 	 * lpdwDataType contains a string that has headers (in MIME
> 	 * format), a blank like, then (possibly) data. We need
> 	 * to parse it.
> 	 *
> 	 * Easy case first:
> 	 */
> 	if (!lpdwDataType) {
> 	    send_http_header(r);
> 	    return TRUE;
> 	}
> 
> 	/* Make a copy - don't disturb the original */
> 	data = pstrdup(r->pool, (char *)lpdwDataType);
> 
> 	/* We *should* break before this while loop ends */
> 	while (*data) {
> 	    char *value, *lf = strchr(data, '\n');
> 	    int p;
> 
> 	    if (!lf) { /* Huh? Invalid data, I think */
> 		log_reason("ISA sent invalid headers", r->filename, r);
> 		SetLastError(ERROR);	/* XXX: Find right error */
> 		return FALSE;
> 	    }
> 
> 	    /* Get rid of \n and \r */
> 	    *lf = '\0';
> 	    p = strlen(data);
> 	    if (p > 0 && data[p-1] == '\r') data[p-1] = '\0';
> 	    
> 	    /* End of headers */
> 	    if (*data == '\0') {
> 		data = lf + 1;	/* Reset data */
> 		break;
> 	    }
> 
> 	    if (!(value = strchr(data, ':'))) {
> 		SetLastError(ERROR);	/* XXX: Find right error */
> 		log_reason("ISA sent invalid headers", r->filename, r);
> 		return FALSE;
> 	    }
> 
> 	    *value++ = '\0';
> 	    while (*value && isspace(*value)) ++value;
> 
> 	    /* Check all the special-case headers. Similar to what
> 	     * scan_script_header() does (see that function for
> 	     * more detail)
> 	     */
> 
> 	    if (!strcasecmp(data, "Content-Type")) {
> 		/* Nuke trailing whitespace */
> 		
> 		char *endp = value + strlen(value) - 1;
> 		while (endp > value && isspace(*endp)) *endp-- = '\0';
>             
> 		r->content_type = pstrdup (r->pool, value);
> 	    }
> 	    else if (!strcasecmp(data, "Content-Length")) {
> 		table_set(r->headers_out, data, value);
> 	    }
> 	    else if (!strcasecmp(data, "Transfer-Encoding")) {
> 		table_set(r->headers_out, data, value);
> 	    }
> 	    else if (!strcasecmp(data, "Set-Cookie")) {
> 		table_add(r->err_headers_out, data, value);
> 	    }
> 	    else {
> 		table_merge(r->err_headers_out, data, value);
> 	    }
> 	  
> 	    /* Reset data */
> 	    data = lf + 1;
> 	}
> 	
> 	/* All the headers should be set now */
> 
> 	send_http_header(r);
> 
> 	/* Any data left should now be sent directly */
> 	rputs(data, r);
> 
> 	return TRUE;
> 
>     case HSE_REQ_MAP_URL_TO_PATH:
> 	/* Map a URL to a filename */
> 	subreq = sub_req_lookup_uri(pstrndup(r->pool, (char *)lpvBuffer,
> 					      *lpdwSize), r);
> 
> 	GetFullPathName(subreq->filename, *lpdwSize - 1, (char *)lpvBuffer, NULL);
> 
> 	/* IIS puts a trailing slash on directories, Apache doesn't */
> 
> 	if (S_ISDIR (subreq->finfo.st_mode)) {
> 		int l = strlen((char *)lpvBuffer);
> 
> 		((char *)lpvBuffer)[l] = '\\';
> 		((char *)lpvBuffer)[l + 1] = '\0';
> 	}
> 	
> 	return TRUE;
> 
>     case HSE_REQ_DONE_WITH_SESSION:
> 	/* Do nothing... since we don't support async I/O, they'll
> 	 * return from HttpExtensionProc soon
> 	 */
> 	return TRUE;
> 
>     /* We don't support all this async I/O, Microsoft-specific stuff */
>     case HSE_REQ_IO_COMPLETION:
>     case HSE_REQ_TRANSMIT_FILE:
> 	log_reason("ISAPI asynchronous I/O not supported", r->filename, r);
>     default:
> 	SetLastError(ERROR_INVALID_PARAMETER);
> 	return FALSE;
>     }
> }
> 
> handler_rec isapi_handlers[] = {
> { "isapi-isa", isapi_handler },
> { NULL}
> };
> 
> module isapi_module = {
>    STANDARD_MODULE_STUFF,
>    NULL,			/* initializer */
>    NULL,			/* create per-dir config */
>    NULL,			/* merge per-dir config */
>    NULL,			/* server config */
>    NULL,			/* merge server config */
>    NULL,			/* command table */
>    isapi_handlers,	       	/* handlers */
>    NULL,			/* filename translation */
>    NULL,			/* check_user_id */
>    NULL,			/* check auth */
>    NULL,			/* check access */
>    NULL,			/* type_checker */
>    NULL,			/* logger */
>    NULL				/* header parser */
> };