1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
54 * Portions of this software are based upon public domain software
55 * originally written at the National Center for Supercomputing Applications,
56 * University of Illinois, Urbana-Champaign.
60 * mod_isapi.c - Internet Server Application (ISA) module for Apache
61 * by Alexei Kosut <akosut@apache.org>
63 * This module implements Microsoft's ISAPI, allowing Apache (when running
64 * under Windows) to load Internet Server Applications (ISAPI extensions).
65 * It implements all of the ISAPI 2.0 specification, except for the
66 * "Microsoft-only" extensions dealing with asynchronous I/O. All ISAPI
67 * extensions that use only synchronous I/O and are compatible with the
68 * ISAPI 2.0 specification should work (most ISAPI 1.0 extensions should
71 * To load, simply place the ISA in a location in the document tree.
72 * Then add an "AddHandler isapi-isa dll" into your config file.
73 * You should now be able to load ISAPI DLLs just be reffering to their
74 * URLs. Make sure the ExecCGI option is active in the directory
78 #include "ap_config.h"
80 #include "http_config.h"
81 #include "http_core.h"
82 #include "http_protocol.h"
83 #include "http_request.h"
85 #include "util_script.h"
86 #include "apr_portable.h"
87 #include "apr_strings.h"
90 /* We use the exact same header file as the original */
93 /* TODO: Unknown errors that must be researched for correct codes */
97 /* Seems IIS does not enforce the requirement for \r\n termination on HSE_REQ_SEND_RESPONSE_HEADER,
98 define this to conform */
99 #define RELAX_HEADER_RULE
103 /* Our "Connection ID" structure */
106 LPEXTENSION_CONTROL_BLOCK ecb;
111 /* Declare the ISAPI functions */
113 BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
114 LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer);
115 BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpwdwBytes,
117 BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize);
118 BOOL WINAPI ServerSupportFunction (HCONN hConn, DWORD dwHSERequest,
119 LPVOID lpvBuffer, LPDWORD lpdwSize,
120 LPDWORD lpdwDataType);
123 The optimiser blows it totally here. What happens is that autos are addressed relative to the
124 stack pointer, which, of course, moves around. The optimiser seems to lose track of it somewhere
125 between setting isapi_entry and calling through it. We work around the problem by forcing it to
128 #pragma optimize("y",off)
130 int isapi_handler (request_rec *r)
134 LPEXTENSION_CONTROL_BLOCK ecb =
135 ap_pcalloc(r->pool, sizeof(struct _EXTENSION_CONTROL_BLOCK));
136 HSE_VERSION_INFO *pVer = ap_pcalloc(r->pool, sizeof(HSE_VERSION_INFO));
138 HINSTANCE isapi_handle;
139 BOOL (*isapi_version)(HSE_VERSION_INFO *); /* entry point 1 */
140 DWORD (*isapi_entry)(LPEXTENSION_CONTROL_BLOCK); /* entry point 2 */
141 BOOL (*isapi_term)(DWORD); /* optional entry point 3 */
143 isapi_cid *cid = ap_pcalloc(r->pool, sizeof(isapi_cid));
144 ap_table_t *e = r->subprocess_env;
147 /* Use similar restrictions as CGIs */
149 if (!(ap_allow_options(r) & OPT_EXECCGI))
150 return HTTP_FORBIDDEN;
152 if (r->finfo.protection == 0)
153 return HTTP_NOT_FOUND;
155 if (r->finfo.filetype == APR_DIR)
156 return HTTP_FORBIDDEN;
158 /* Load the module */
160 if (!(isapi_handle = LoadLibraryEx(r->filename, NULL,
161 LOAD_WITH_ALTERED_SEARCH_PATH))) {
163 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
164 "Could not load DLL: %s", r->filename);
165 return HTTP_INTERNAL_SERVER_ERROR;
168 if (!(isapi_version =
169 (void *)(GetProcAddress(isapi_handle, "GetExtensionVersion")))) {
171 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
172 "Could not load DLL %s symbol GetExtensionVersion()",
174 FreeLibrary(isapi_handle);
175 return HTTP_INTERNAL_SERVER_ERROR;
179 (void *)(GetProcAddress(isapi_handle, "HttpExtensionProc")))) {
181 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
182 "Could not load DLL %s symbol HttpExtensionProc()",
184 FreeLibrary(isapi_handle);
185 return HTTP_INTERNAL_SERVER_ERROR;
188 /* TerminateExtension() is an optional interface */
190 isapi_term = (void *)(GetProcAddress(isapi_handle, "TerminateExtension"));
192 /* Run GetExtensionVersion() */
194 if (!(*isapi_version)(pVer)) {
195 /* ### euh... we're passing the wrong type of error code here */
196 ap_log_rerror(APLOG_MARK, APLOG_ALERT, HTTP_INTERNAL_SERVER_ERROR, r,
197 "ISAPI %s GetExtensionVersion() call failed", r->filename);
198 FreeLibrary(isapi_handle);
199 return HTTP_INTERNAL_SERVER_ERROR;
202 /* Set up variables */
203 ap_add_common_vars(r);
206 /* Set up connection ID */
207 ecb->ConnID = (HCONN)cid;
212 ecb->cbSize = sizeof(struct _EXTENSION_CONTROL_BLOCK);
213 ecb->dwVersion = MAKELONG(0, 2);
214 ecb->dwHttpStatusCode = 0;
215 strcpy(ecb->lpszLogData, "");
216 // TODO: is a copy needed here?
217 ecb->lpszMethod = (char*) r->method;
218 // TODO: is a copy needed here?
219 ecb->lpszQueryString = (char*) ap_table_get(e, "QUERY_STRING");
220 // TODO: is a copy needed here?
221 ecb->lpszPathInfo = (char*) ap_table_get(e, "PATH_INFO");
222 // TODO: is a copy needed here?
223 ecb->lpszPathTranslated = (char*) ap_table_get(e, "PATH_TRANSLATED");
224 // TODO: is a copy needed here?
225 ecb->lpszContentType = (char*) ap_table_get(e, "CONTENT_TYPE");
227 /* Set up client input */
228 if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) {
229 if (isapi_term) (*isapi_term)(HSE_TERM_MUST_UNLOAD);
230 FreeLibrary(isapi_handle);
234 if (ap_should_client_block(r)) {
235 /* Unlike IIS, which limits this to 48k, we read the whole
236 * sucker in. I suppose this could be bad for memory if someone
237 * uploaded the complete works of Shakespeare. Well, WebSite
238 * does the same thing.
240 long to_read = atol(ap_table_get(e, "CONTENT_LENGTH"));
243 /* Actually, let's cap it at 48k, until we figure out what
244 * to do with this... we don't want a Content-Length: 1000000000
245 * taking out the machine.
248 if (to_read > 49152) {
249 if (isapi_term) (*isapi_term)(HSE_TERM_MUST_UNLOAD);
250 FreeLibrary(isapi_handle);
251 return HTTP_REQUEST_ENTITY_TOO_LARGE;
254 ecb->lpbData = ap_pcalloc(r->pool, 1 + to_read);
256 if ((read = ap_get_client_block(r, ecb->lpbData, to_read)) < 0) {
257 if (isapi_term) (*isapi_term)(HSE_TERM_MUST_UNLOAD);
258 FreeLibrary(isapi_handle);
259 return HTTP_INTERNAL_SERVER_ERROR;
262 /* Although its not to spec, IIS seems to null-terminate
263 * its lpdData string. So we will too. To make sure
264 * cbAvailable matches cbTotalBytes, we'll up the latter
267 ecb->cbAvailable = ecb->cbTotalBytes = read + 1;
268 ecb->lpbData[read] = '\0';
271 ecb->cbTotalBytes = 0;
272 ecb->cbAvailable = 0;
276 /* Set up the callbacks */
278 ecb->GetServerVariable = &GetServerVariable;
279 ecb->WriteClient = &WriteClient;
280 ecb->ReadClient = &ReadClient;
281 ecb->ServerSupportFunction = &ServerSupportFunction;
283 /* All right... try and load the sucker */
284 retval = (*isapi_entry)(ecb);
286 /* Set the status (for logging) */
287 if (ecb->dwHttpStatusCode)
288 r->status = ecb->dwHttpStatusCode;
290 /* Check for a log message - and log it */
291 if (ecb->lpszLogData && strcmp(ecb->lpszLogData, ""))
292 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
293 "ISAPI %s: %s", r->filename, ecb->lpszLogData);
295 /* All done with the DLL... get rid of it */
296 if (isapi_term) (*isapi_term)(HSE_TERM_MUST_UNLOAD);
297 FreeLibrary(isapi_handle);
300 case HSE_STATUS_SUCCESS:
301 /* TODO: If content length was missing or incorrect, and the response
302 * was not chunked, we need to close the connection here.
303 * If the response was chunked, and no closing chunk was sent, we aught
304 * to transmit one here
307 /* fall through... */
308 case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
309 /* Ignore the keepalive stuff; Apache handles it just fine without
310 * the ISA's "advice".
313 if (cid->status) /* We have a special status to return */
318 case HSE_STATUS_PENDING:
319 /* We don't support this, but we need to... we should simply create a
320 * wait event and die on timeout or resume with the callback to our
321 * ServerSupportFunction with HSE_REQ_DONE_WITH_SESSION to emulate
324 ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_ENOTIMPL, r,
325 "ISAPI asynchronous I/O not supported: %s", r->filename);
327 case HSE_STATUS_ERROR:
328 /* end response if we have yet to do so.
330 return HTTP_INTERNAL_SERVER_ERROR;
333 /* TODO: log unrecognized retval for debugging
335 return HTTP_INTERNAL_SERVER_ERROR;
339 #pragma optimize("",on)
341 BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
342 LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer)
344 request_rec *r = ((isapi_cid *)hConn)->r;
345 ap_table_t *e = r->subprocess_env;
348 /* Mostly, we just grab it from the environment, but there are
349 * a couple of special cases
352 if (!strcasecmp(lpszVariableName, "UNMAPPED_REMOTE_USER")) {
353 /* We don't support NT users, so this is always the same as
356 result = ap_table_get(e, "REMOTE_USER");
358 else if (!strcasecmp(lpszVariableName, "SERVER_PORT_SECURE")) {
359 /* Apache doesn't support secure requests inherently, so
360 * we have no way of knowing. We'll be conservative, and say
361 * all requests are insecure.
365 else if (!strcasecmp(lpszVariableName, "URL")) {
369 result = ap_table_get(e, lpszVariableName);
373 if (strlen(result) > *lpdwSizeofBuffer) {
374 *lpdwSizeofBuffer = strlen(result);
375 SetLastError(ERROR_INSUFFICIENT_BUFFER);
378 strncpy(lpvBuffer, result, *lpdwSizeofBuffer);
383 SetLastError(ERROR_INVALID_INDEX);
387 BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpwdwBytes,
390 request_rec *r = ((isapi_cid *)ConnID)->r;
391 int writ; /* written, actually, but why shouldn't I make up words? */
393 /* We only support synchronous writing */
394 if (dwReserved && dwReserved != HSE_IO_SYNC) {
395 ap_log_rerror(APLOG_MARK, APLOG_WARNING, ERROR_INVALID_PARAMETER, r,
396 "ISAPI asynchronous I/O not supported: %s", r->filename);
397 SetLastError(ERROR_INVALID_PARAMETER);
401 if ((writ = ap_rwrite(Buffer, *lpwdwBytes, r)) == EOF) {
402 SetLastError(WSAEDISCON); /* TODO: Find the right error code */
410 BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize)
412 /* If the request was a huge transmit or chunked, continue piping the
413 * request here, but if it's of a sane size, continue to ...
418 /* XXX: There is an O(n^2) attack possible here. */
419 BOOL WINAPI ServerSupportFunction (HCONN hConn, DWORD dwHSERequest,
420 LPVOID lpvBuffer, LPDWORD lpdwSize,
421 LPDWORD lpdwDataType)
423 isapi_cid *cid = (isapi_cid *)hConn;
424 request_rec *subreq, *r = cid->r;
427 switch (dwHSERequest) {
428 case HSE_REQ_SEND_URL_REDIRECT_RESP:
429 /* Set the status to be returned when the HttpExtensionProc()
432 ap_table_set (r->headers_out, "Location", lpvBuffer);
433 cid->status = cid->r->status = cid->ecb->dwHttpStatusCode =
434 HTTP_MOVED_TEMPORARILY;
437 case HSE_REQ_SEND_URL:
438 /* Read any additional input */
440 if (r->remaining > 0) {
441 char argsbuffer[HUGE_STRING_LEN];
443 while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN));
446 /* Reset the method to GET */
447 r->method = ap_pstrdup(r->pool, "GET");
448 r->method_number = M_GET;
450 /* Don't let anyone think there's still data */
451 ap_table_unset(r->headers_in, "Content-Length");
453 ap_internal_redirect((char *)lpvBuffer, r);
456 case HSE_REQ_SEND_RESPONSE_HEADER_EX:
457 if (((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->pszStatus
458 && ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->cchStatus)
459 r->status_line = ap_pstrndup(r->pool,
460 ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->pszStatus,
461 ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->cchStatus);
463 r->status_line = ap_pstrdup(r->pool, "200 OK");
464 sscanf(r->status_line, "%d", &r->status);
465 cid->ecb->dwHttpStatusCode = r->status;
467 ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->pszHeader; // HTTP header
468 ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->cchHeader; // HTTP header len
470 ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->fKeepConn; // Keep alive? (bool)
472 case HSE_REQ_SEND_RESPONSE_HEADER:
473 r->status_line = lpvBuffer ? lpvBuffer : ap_pstrdup(r->pool, "200 OK");
474 sscanf(r->status_line, "%d", &r->status);
475 cid->ecb->dwHttpStatusCode = r->status;
477 /* Now fill in the HTTP headers, and the rest of it. Ick.
478 * lpdwDataType contains a string that has headers (in MIME
479 * format), a blank like, then (possibly) data. We need
485 ap_send_http_header(r);
489 /* Make a copy - don't disturb the original */
490 data = ap_pstrdup(r->pool, (char *)lpdwDataType);
492 /* We *should* break before this while loop ends */
494 char *value, *lf = strchr(data, '\n');
497 #ifdef RELAX_HEADER_RULE
501 if (!lf) { /* Huh? Invalid data, I think */
502 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
503 "ISA sent invalid headers: %s", r->filename);
504 SetLastError(TODO_ERROR);
508 /* Get rid of \n and \r */
512 if (p > 0 && data[p-1] == '\r') data[p-1] = '\0';
516 #ifdef RELAX_HEADER_RULE
519 data = lf + 1; /* Reset data */
523 if (!(value = strchr(data, ':'))) {
524 SetLastError(TODO_ERROR);
525 /* ### euh... we're passing the wrong type of error
527 ap_log_rerror(APLOG_MARK, APLOG_ERR,
528 HTTP_INTERNAL_SERVER_ERROR, r,
529 "ISA sent invalid headers", r->filename);
534 while (*value && ap_isspace(*value)) ++value;
536 /* Check all the special-case headers. Similar to what
537 * ap_scan_script_header_err() does (see that function for
541 if (!strcasecmp(data, "Content-Type")) {
543 /* Nuke trailing whitespace */
545 char *endp = value + strlen(value) - 1;
546 while (endp > value && ap_isspace(*endp)) *endp-- = '\0';
548 tmp = ap_pstrdup (r->pool, value);
550 r->content_type = tmp;
552 else if (!strcasecmp(data, "Content-Length")) {
553 ap_table_set(r->headers_out, data, value);
555 else if (!strcasecmp(data, "Transfer-Encoding")) {
556 ap_table_set(r->headers_out, data, value);
558 else if (!strcasecmp(data, "Set-Cookie")) {
559 ap_table_add(r->err_headers_out, data, value);
562 ap_table_merge(r->err_headers_out, data, value);
566 #ifdef RELAX_HEADER_RULE
575 /* All the headers should be set now */
577 ap_send_http_header(r);
579 /* Any data left should now be sent directly */
584 case HSE_REQ_MAP_URL_TO_PATH:
585 /* Map a URL to a filename */
586 subreq = ap_sub_req_lookup_uri(ap_pstrndup(r->pool, (char *)lpvBuffer,
589 GetFullPathName(subreq->filename, *lpdwSize - 1, (char *)lpvBuffer, NULL);
591 /* IIS puts a trailing slash on directories, Apache doesn't */
593 if (subreq->finfo.filetype == APR_DIR) {
594 int l = strlen((char *)lpvBuffer);
596 ((char *)lpvBuffer)[l] = '\\';
597 ((char *)lpvBuffer)[l + 1] = '\0';
602 case HSE_REQ_DONE_WITH_SESSION:
603 /* TODO: Signal the main request with the event to complete the session
607 /* We don't support all this async I/O, Microsoft-specific stuff */
608 case HSE_REQ_IO_COMPLETION:
609 /* TODO: Emulate a completion port, if we can...
610 * Record the callback address and user defined argument...
611 * we will call this after any async request (including transmitfile)
612 * as if the request had been async.
615 case HSE_REQ_TRANSMIT_FILE:
616 /* Use TransmitFile (in leiu of WriteClient)... nothing wrong with that
619 /* ### euh... we're passing the wrong type of error code here */
620 ap_log_rerror(APLOG_MARK, APLOG_WARNING,
621 HTTP_INTERNAL_SERVER_ERROR, r,
622 "ISAPI asynchronous I/O not supported: %s",
625 case HSE_APPEND_LOG_PARAMETER:
626 /* Log lpvBuffer, of lpdwSize bytes */
629 case HSE_REQ_ABORTIVE_CLOSE:
630 case HSE_REQ_ASYNC_READ_CLIENT:
631 case HSE_REQ_CLOSE_CONNECTION:
632 case HSE_REQ_GET_CERT_INFO_EX:
633 case HSE_REQ_GET_IMPERSONATION_TOKEN:
634 case HSE_REQ_GET_SSPI_INFO:
635 case HSE_REQ_IS_KEEP_CONN:
636 case HSE_REQ_MAP_URL_TO_PATH_EX:
637 case HSE_REQ_REFRESH_ISAPI_ACL:
640 SetLastError(ERROR_INVALID_PARAMETER);
645 handler_rec isapi_handlers[] = {
646 { "isapi-isa", isapi_handler },
650 module isapi_module = {
651 STANDARD20_MODULE_STUFF,
652 NULL, /* create per-dir config */
653 NULL, /* merge per-dir config */
654 NULL, /* server config */
655 NULL, /* merge server config */
656 NULL, /* command ap_table_t */
657 isapi_handlers, /* handlers */
658 NULL /* register hooks */