You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Alexei Kosut <ak...@organic.com> on 1997/07/22 03:10:45 UTC

mod_isapi

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 */
};


Re: mod_isapi

Posted by Ben Laurie <be...@algroup.co.uk>.
Alexei Kosut wrote:
> 
> 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.

I wasn't ignoring it, but I don't currently have a way to test it - I
have a friend who uses IIS fairly heavily, though - I can ask him to
have a go.

Cheers,

Ben.

-- 
Ben Laurie                Phone: +44 (181) 994 6435  Email:
ben@algroup.co.uk
Freelance Consultant and  Fax:   +44 (181) 994 6472
Technical Director        URL: http://www.algroup.co.uk/Apache-SSL
A.L. Digital Ltd,         Apache Group member (http://www.apache.org)
London, England.          Apache-SSL author

Re: mod_isapi

Posted by Brian Behlendorf <br...@organic.com>.
At 06:10 PM 7/21/97 -0700, you wrote:
>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

+1.

	Brian


--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--
"Why not?" - TL           brian@organic.com - hyperreal.org - apache.org

Re: mod_isapi

Posted by Alexei Kosut <ak...@organic.com>.
On Tue, 22 Jul 1997, Dean Gaudet wrote:

> Speaking of taking Content-Length at its word... do we care that CGIs can
> screw up a persistent connection by sending a Content-Length and then
> totally botching it?  I fear CGIs that send Content-Lengths with off-by-1
> :) 

IMHO, no, we don't care. The worst that can happen is that the browser
gets rather confused... and that's a valid thing to happen when a CGI is
poorly written.

Besides, we've been doing it for a year now, and no one's complained.
 
-- Alexei Kosut <ak...@organic.com>


Re: mod_isapi

Posted by Dean Gaudet <dg...@arctic.org>.
Speaking of taking Content-Length at its word... do we care that CGIs can
screw up a persistent connection by sending a Content-Length and then
totally botching it?  I fear CGIs that send Content-Lengths with off-by-1
:) 

Dean

On Tue, 22 Jul 1997, Alexei Kosut wrote:

> On Tue, 22 Jul 1997, Dean Gaudet wrote:
> 
> > On Tue, 22 Jul 1997, Alexei Kosut wrote:
> > 
> > > I plan to clean it up. Don't know how. We don't have a prealloc(), so I'm
> > > not quite sure how to do it in chunks without massively wasting memory.
> > 
> > Oh you can't reuse the same 48k buffer after each call?
> 
> Sure, if I implement ReadClient(). But I have no clue how to do that
> and both keep it so that ISAs dependent on IIS' behavior work, and keep
> to the ISAPI spec. I am getting curiouser as to how IIS works exactly.
> 
> Though I know that WebSite works the same way mod_isapi works (or at
> least, in a similar way - the buffer ends up with all the data, 48k or
> not). But I don't know how to do it that way efficiently without taking
> Content-Length at its word, and allocating that much memory immediately.
> 
> -- Alexei Kosut <ak...@organic.com>
> 
> 


Re: mod_isapi

Posted by Alexei Kosut <ak...@organic.com>.
On Tue, 22 Jul 1997, Dean Gaudet wrote:

> On Tue, 22 Jul 1997, Alexei Kosut wrote:
> 
> > I plan to clean it up. Don't know how. We don't have a prealloc(), so I'm
> > not quite sure how to do it in chunks without massively wasting memory.
> 
> Oh you can't reuse the same 48k buffer after each call?

Sure, if I implement ReadClient(). But I have no clue how to do that
and both keep it so that ISAs dependent on IIS' behavior work, and keep
to the ISAPI spec. I am getting curiouser as to how IIS works exactly.

Though I know that WebSite works the same way mod_isapi works (or at
least, in a similar way - the buffer ends up with all the data, 48k or
not). But I don't know how to do it that way efficiently without taking
Content-Length at its word, and allocating that much memory immediately.

-- Alexei Kosut <ak...@organic.com>


Re: mod_isapi

Posted by Dean Gaudet <dg...@arctic.org>.

On Tue, 22 Jul 1997, Alexei Kosut wrote:

> I plan to clean it up. Don't know how. We don't have a prealloc(), so I'm
> not quite sure how to do it in chunks without massively wasting memory.

Oh you can't reuse the same 48k buffer after each call?

> Yes, yes. True. But a CGI can do that too... I think. Although I'll grant
> you it's much trickier.

No a CGI talks to Apache through a pipe().  Unless we've got a buffer
overrun somewhere there's absolutely nothing the CGI can do to affect
apache's memory space.  Whereas ISAPI lives right in Apache's memory
space.  It'd be trivial, for example, to snoop http auth passwords.  Or to
steal hits from all vhosts... or wreak any sort of havoc.

How to do it?  Well you just get yourself a memory map for Apache (easy to
do, just build it the same way we do).  Then you find prelinked_modules,
you walk that structure looking for core, or whatever, you patch one of
the vectors to point at your devious routine. 

Dean



Re: mod_isapi

Posted by Alexei Kosut <ak...@organic.com>.
On Mon, 21 Jul 1997, Dean Gaudet wrote:

> 
> 
> On Mon, 21 Jul 1997, Alexei Kosut wrote:
> 
> > Actually, it's a tad worse. You can send a "Content-length: 10000000" and
> > Apache will malloc() ten megabytes. I do see that as a problem. But I
> > forgot to do anything about it... Couldn't think of quite the right thing
> > to do.
> 
> You mean it does this with your mod_isapi right?

Yes, that's right.

> > Makes sense. Only one problem. IIS doesn't do that. It doesn't set the
> > variables to what CONTENT_LENGTH would be. It doesn't pass the data into
> > the buffer unmodified. It tacks on a \0 and adds one to the
> > numbers. Which means that if there is more than 48k, the algorithm
> > described in the spec goes to hell, because everything's off by one. The
> > only way I could figure out how to do this without dissasembling IIS to
> > figure out what the hell it does was to send all the data to the client,
> > tack on the null, and up both numbers (so they keep equal). It seems to
> > work with the demos that come with IIS.
> 
> Ok I don't see why you can't also use a 48k buffer and do the same
> thing... rather than just reading it all at once and allocating a lot of
> memory.  Was it just a first-pass convenience thing you plan to clean up? 
> I'll buy that.  Or maybe I'm confused.

I plan to clean it up. Don't know how. We don't have a prealloc(), so I'm
not quite sure how to do it in chunks without massively wasting memory.

One option is just to cap the input at some reasonable number, like 48k
or so, and the people who want to upload files are out of luck?

> > aren't in use anymore, and so I couldn't figure out how to make it unload
> > the DLL at a good time.
> 
> Couldn't you just register a cleanup for it in the appropriate pool?
> Probably the connection pool given the sounds of the api.

No... because the ISA might be doing something more long-lived than
that. They're required to be multithreaded, and so they handle all the
requests at once, for as long as they're loaded. For example, I might
open up a database connection. Or I might keep state information. That's
all useful over many connections, for many minutes.

LoadLibrary()/FreeLibrary() does keep a reference count, and unloads only
when the last use frees it. The problem is how to get Apache to figure
out when to free the last one, if you don't knock them out of memory when
you're done with them.

> > It's not that trivial. Unless they have knowledge of Apache's
> > internals. But a CGI script can do the same thing, unless you're using
> > suexec. Remember, DLLs have seperate address spaces unless they're
> > explicitly merged by linking to an export library, or calling
> > GetProcAddress(). So the DLL can only talk to Apache through the
> > interfaces Apache sets up for it. Similar to a CGI, in that respect.
> 
> The interfaces pass it pointers to Apache's memory space right?  DLLs have
> access to the calling process' memory space.  DLLs are just mapped above a
> certain address, much like shared libraries are mapped in unix.  Given
> acess to Apache's memory space you can do anything to it.  It's trivial to
> have knowledge of Apache's internals, you just get the source code and
> build it the same way we do and you'll have a symbol map.

Yes, yes. True. But a CGI can do that too... I think. Although I'll grant
you it's much trickier.

-- Alexei Kosut <ak...@organic.com>


Re: mod_isapi

Posted by Dean Gaudet <dg...@arctic.org>.

On Mon, 21 Jul 1997, Alexei Kosut wrote:

> Actually, it's a tad worse. You can send a "Content-length: 10000000" and
> Apache will malloc() ten megabytes. I do see that as a problem. But I
> forgot to do anything about it... Couldn't think of quite the right thing
> to do.

You mean it does this with your mod_isapi right?

> Makes sense. Only one problem. IIS doesn't do that. It doesn't set the
> variables to what CONTENT_LENGTH would be. It doesn't pass the data into
> the buffer unmodified. It tacks on a \0 and adds one to the
> numbers. Which means that if there is more than 48k, the algorithm
> described in the spec goes to hell, because everything's off by one. The
> only way I could figure out how to do this without dissasembling IIS to
> figure out what the hell it does was to send all the data to the client,
> tack on the null, and up both numbers (so they keep equal). It seems to
> work with the demos that come with IIS.

Ok I don't see why you can't also use a 48k buffer and do the same
thing... rather than just reading it all at once and allocating a lot of
memory.  Was it just a first-pass convenience thing you plan to clean up? 
I'll buy that.  Or maybe I'm confused.

> aren't in use anymore, and so I couldn't figure out how to make it unload
> the DLL at a good time.

Couldn't you just register a cleanup for it in the appropriate pool?
Probably the connection pool given the sounds of the api.

> It's not that trivial. Unless they have knowledge of Apache's
> internals. But a CGI script can do the same thing, unless you're using
> suexec. Remember, DLLs have seperate address spaces unless they're
> explicitly merged by linking to an export library, or calling
> GetProcAddress(). So the DLL can only talk to Apache through the
> interfaces Apache sets up for it. Similar to a CGI, in that respect.

The interfaces pass it pointers to Apache's memory space right?  DLLs have
access to the calling process' memory space.  DLLs are just mapped above a
certain address, much like shared libraries are mapped in unix.  Given
acess to Apache's memory space you can do anything to it.  It's trivial to
have knowledge of Apache's internals, you just get the source code and
build it the same way we do and you'll have a symbol map.

Dean


Re: mod_isapi

Posted by Ben Laurie <be...@algroup.co.uk>.
Dean Gaudet wrote:
> 
> On Mon, 21 Jul 1997, Alexei Kosut wrote:
> 
> > aren't in use anymore, and so I couldn't figure out how to make it unload
> > the DLL at a good time.
> 
> Oh I understand your dilemna.  LoadLibrary()s are not reference counted
> probably.  So a single unload would unload it even if other threads were
> using it.  You'd have to implement your own reference counting then to do
> a cleanup.

Actually LoadLibrary does do reference counting.

Cheers,

Ben.

-- 
Ben Laurie                Phone: +44 (181) 994 6435  Email:
ben@algroup.co.uk
Freelance Consultant and  Fax:   +44 (181) 994 6472
Technical Director        URL: http://www.algroup.co.uk/Apache-SSL
A.L. Digital Ltd,         Apache Group member (http://www.apache.org)
London, England.          Apache-SSL author

Re: mod_isapi

Posted by Dean Gaudet <dg...@arctic.org>.

On Mon, 21 Jul 1997, Alexei Kosut wrote:

> aren't in use anymore, and so I couldn't figure out how to make it unload
> the DLL at a good time.

Oh I understand your dilemna.  LoadLibrary()s are not reference counted
probably.  So a single unload would unload it even if other threads were
using it.  You'd have to implement your own reference counting then to do
a cleanup.

Dean


Re: mod_isapi

Posted by Alexei Kosut <ak...@organic.com>.
On Mon, 21 Jul 1997, Dean Gaudet wrote:

> On Mon, 21 Jul 1997, Alexei Kosut wrote:
> 
> > 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.
> 
> Hmm, I don't remember seeing it.  Where's the spec anyhow?

It was part of my DLLizing-Apache message a few weeks back. The spec's at
http://www.microsoft.com/win32dev/apiext/isapimrg.htm.

> > 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.
> 
> This allows a DoS attack.  Not that unix Apache doesn't have this problem
> already... just send an infinite stream of headers at Apache.

Actually, it's a tad worse. You can send a "Content-length: 10000000" and
Apache will malloc() ten megabytes. I do see that as a problem. But I
forgot to do anything about it... Couldn't think of quite the right thing
to do.

The problem is that IIS doesn't obey Microsoft's own ISAPI spec in this
matter. According to the spec, the ISAPI request structure is supposed to
contain however much data is read from the client, up to some maximum
number (48k is what IIS uses). It also has two numbers, one of them the
total client input available (equivilent to CONTENT_LENGTH, it says), and
the other the amount of data available in the buffer. If they're equal,
the data's all there. If not, the ISA is supposed to call ReadClient()
until it gets the difference.

Makes sense. Only one problem. IIS doesn't do that. It doesn't set the
variables to what CONTENT_LENGTH would be. It doesn't pass the data into
the buffer unmodified. It tacks on a \0 and adds one to the
numbers. Which means that if there is more than 48k, the algorithm
described in the spec goes to hell, because everything's off by one. The
only way I could figure out how to do this without dissasembling IIS to
figure out what the hell it does was to send all the data to the client,
tack on the null, and up both numbers (so they keep equal). It seems to
work with the demos that come with IIS.

> > 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.
> 
> Apache/NT is multithreaded, isn't it?  You should be able to do this, you
> just need a semaphore to protect a global structure.  Preferably a
> reader/writers lock.

Actually, you don't even need this; Win32 is smart emough that if you
call LoadLibrary() twice on the same DLL, it won't load it twice, it will
return a pointer to the same address as the first time. Unfortunately,
the part I couldn't figure out is how to get Apache to realize it's time
to *unload* the module. Apache's memory model is based on using memory
until the system crashes, reclaiming space after certain memory chunks
aren't in use anymore, and so I couldn't figure out how to make it unload
the DLL at a good time.

> 
> > 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.
> 
> Probably worth it at some point, otherwise reviews will say "they don't
> implement ISAPI".

Hey... you try it. Among other things, it requires the server to let
filters parse *request headers* before the server even gets to see
them. Ugh.

> > 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.
> 
> Scary.  Typical Microsoft.  Totally Scary.  It's trivial to hack apache
> to do whatever you want to in this model.  Essentially they're getting
> speed by switching message passing from pass-by-copy (i.e. unix pipes)
> to pass-by-reference.  Yeah sure pass-by-ref is way faster... but there's
> more reasons to use pass-by-copy.

It's not that trivial. Unless they have knowledge of Apache's
internals. But a CGI script can do the same thing, unless you're using
suexec. Remember, DLLs have seperate address spaces unless they're
explicitly merged by linking to an export library, or calling
GetProcAddress(). So the DLL can only talk to Apache through the
interfaces Apache sets up for it. Similar to a CGI, in that respect.

> Does microsoft claim this is a CGI replacement suitable for multi-user
> servers?

Oh, sure... even though poorly-written ISAs can take down the server. IIS
included, although it does make more of itself. Of the three ISAPI demos
that come with IIS, two of them will do the equvilent of a seg fault of
you GET them instead of POST (as they expect)... great, huh?

> +1 on putting the module in, even for 1.3a1.

-- Alexei Kosut <ak...@organic.com>


Re: mod_isapi

Posted by Dean Gaudet <dg...@arctic.org>.
On Mon, 21 Jul 1997, Alexei Kosut wrote:

> 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.

Hmm, I don't remember seeing it.  Where's the spec anyhow?

> 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.

This allows a DoS attack.  Not that unix Apache doesn't have this problem
already... just send an infinite stream of headers at Apache.

> 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.

Apache/NT is multithreaded, isn't it?  You should be able to do this, you
just need a semaphore to protect a global structure.  Preferably a
reader/writers lock.

> 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.

Probably worth it at some point, otherwise reviews will say "they don't
implement ISAPI".

> 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.

Scary.  Typical Microsoft.  Totally Scary.  It's trivial to hack apache
to do whatever you want to in this model.  Essentially they're getting
speed by switching message passing from pass-by-copy (i.e. unix pipes)
to pass-by-reference.  Yeah sure pass-by-ref is way faster... but there's
more reasons to use pass-by-copy.

Does microsoft claim this is a CGI replacement suitable for multi-user
servers?

+1 on putting the module in, even for 1.3a1.

Dean