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 /* Declare the ISAPI functions */
105 BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
106 LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer);
107 BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpwdwBytes,
109 BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize);
110 BOOL WINAPI ServerSupportFunction (HCONN hConn, DWORD dwHSERequest,
111 LPVOID lpvBuffer, LPDWORD lpdwSize,
112 LPDWORD lpdwDataType);
115 The optimiser blows it totally here. What happens is that autos are addressed relative to the
116 stack pointer, which, of course, moves around. The optimiser seems to lose track of it somewhere
117 between setting HttpExtensionProc's address and calling through it. We work around the problem by
118 forcing it to use frame pointers.
120 The revisions below may eliminate this artifact.
122 #pragma optimize("y",off)
124 /* Our isapi server config structure */
128 apr_array_header_t *loaded;
129 DWORD ReadAheadBuffer;
131 int AppendLogToErrors;
132 int AppendLogToQuery;
135 /* Our loaded isapi module description structure */
138 const char *filename;
140 HSE_VERSION_INFO *pVer;
141 PFN_GETEXTENSIONVERSION GetExtensionVersion;
142 PFN_HTTPEXTENSIONPROC HttpExtensionProc;
143 PFN_TERMINATEEXTENSION TerminateExtension;
150 /* Our "Connection ID" structure */
153 LPEXTENSION_CONTROL_BLOCK ecb;
154 isapi_server_conf *sconf;
157 PFN_HSE_IO_COMPLETION completion;
158 PVOID completion_arg;
162 static BOOL isapi_unload(isapi_loaded* isa, int force);
164 static apr_status_t cleanup_isapi_server_config(void *sconfv)
166 isapi_server_conf *sconf = sconfv;
170 n = sconf->loaded->nelts;
171 isa = (isapi_loaded **)sconf->loaded->elts;
174 isapi_unload(*isa, TRUE);
177 CloseHandle(sconf->lock);
181 static void *create_isapi_server_config(apr_pool_t *p, server_rec *s)
183 isapi_server_conf *sconf = apr_palloc(p, sizeof(isapi_server_conf));
184 sconf->loaded = apr_make_array(p, 20, sizeof(isapi_loaded*));
185 sconf->lock = CreateMutex(NULL, FALSE, NULL);
187 sconf->ReadAheadBuffer = 49152;
188 sconf->LogNotSupported = -1;
189 sconf->AppendLogToErrors = 0;
190 sconf->AppendLogToQuery = 0;
192 apr_register_cleanup(p, sconf, cleanup_isapi_server_config,
198 static int compare_loaded(const void *av, const void *bv)
200 const isapi_loaded **a = av;
201 const isapi_loaded **b = bv;
203 return strcmp((*a)->filename, (*b)->filename);
206 static void isapi_post_config(apr_pool_t *p, apr_pool_t *plog,
207 apr_pool_t *ptemp, server_rec *s)
209 isapi_server_conf *sconf = ap_get_module_config(s->module_config,
211 isapi_loaded **elts = (isapi_loaded **)sconf->loaded->elts;
212 int nelts = sconf->loaded->nelts;
214 /* sort the elements of the main_server, by filename */
215 qsort(elts, nelts, sizeof(isapi_loaded*), compare_loaded);
217 /* and make the virtualhosts share the same thing */
218 for (s = s->next; s; s = s->next) {
219 ap_set_module_config(s->module_config, &isapi_module, sconf);
223 static apr_status_t isapi_load(apr_pool_t *p, isapi_server_conf *sconf,
224 request_rec *r, const char *fpath,
227 isapi_loaded **found = (isapi_loaded **)sconf->loaded->elts;
232 for (n = 0; n < sconf->loaded->nelts; ++n) {
233 if (strcasecmp(fpath, (*found)->filename) == 0) {
239 if (n < sconf->loaded->nelts)
247 /* Otherwise we fall through and have to reload the resource
248 * into this existing mod_isapi cache bucket.
253 *isa = apr_pcalloc(p, sizeof(isapi_module));
254 (*isa)->filename = fpath;
255 (*isa)->pVer = apr_pcalloc(p, sizeof(HSE_VERSION_INFO));
257 /* TODO: These need to become overrideable, so that we
258 * assure a given isapi can be fooled into behaving well.
260 (*isa)->timeout = INFINITE; /* microsecs */
261 (*isa)->fakeasync = TRUE;
262 (*isa)->reportversion = MAKELONG(0, 5); /* Revision 5.0 */
265 /* Per PR2555, the LoadLibraryEx function is very picky about slashes.
266 * Debugging on NT 4 SP 6a reveals First Chance Exception within NTDLL.
267 * LoadLibrary in the MS PSDK also reveals that it -explicitly- states
268 * that backslashes must be used.
270 * Transpose '\' for '/' in the filename.
272 ch = fspec = apr_pstrdup(p, fpath);
279 (*isa)->handle = LoadLibraryEx(fspec, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
283 apr_status_t rv = apr_get_os_error();
284 ap_log_rerror(APLOG_MARK, APLOG_ALERT, apr_get_os_error(), r,
285 "ISAPI %s failed to load", fpath);
286 (*isa)->handle = NULL;
290 if (!((*isa)->GetExtensionVersion = (void *)(GetProcAddress((*isa)->handle,
291 "GetExtensionVersion"))))
293 apr_status_t rv = apr_get_os_error();
294 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
295 "ISAPI %s is missing GetExtensionVersion()",
297 FreeLibrary((*isa)->handle);
298 (*isa)->handle = NULL;
302 if (!((*isa)->HttpExtensionProc = (void *)(GetProcAddress((*isa)->handle,
303 "HttpExtensionProc"))))
305 apr_status_t rv = apr_get_os_error();
306 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
307 "ISAPI %s is missing HttpExtensionProc()",
309 FreeLibrary((*isa)->handle);
310 (*isa)->handle = NULL;
314 /* TerminateExtension() is an optional interface */
315 (*isa)->TerminateExtension = (void *)(GetProcAddress((*isa)->handle,
316 "TerminateExtension"));
319 /* Run GetExtensionVersion() */
320 if (!((*isa)->GetExtensionVersion)((*isa)->pVer)) {
321 apr_status_t rv = apr_get_os_error();
322 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
323 "ISAPI %s call GetExtensionVersion() failed",
325 FreeLibrary((*isa)->handle);
326 (*isa)->handle = NULL;
335 static int isapi_unload(isapi_loaded* isa, int force)
337 /* All done with the DLL... get rid of it...
339 * If optionally cached, pass HSE_TERM_ADVISORY_UNLOAD,
340 * and if it returns TRUE, unload, otherwise, cache it.
342 if ((--isa->refcount > 0) && !force)
344 if (isa->TerminateExtension) {
346 (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD);
347 else if (!(*isa->TerminateExtension)(HSE_TERM_ADVISORY_UNLOAD))
350 FreeLibrary(isa->handle);
355 apr_status_t isapi_handler (request_rec *r)
357 isapi_server_conf *sconf = ap_get_module_config(r->server->module_config,
359 apr_table_t *e = r->subprocess_env;
366 /* Use similar restrictions as CGIs
368 * If this fails, it's pointless to load the isapi dll.
370 if (!(ap_allow_options(r) & OPT_EXECCGI))
371 return HTTP_FORBIDDEN;
373 if (r->finfo.filetype == APR_NOFILE)
374 return HTTP_NOT_FOUND;
376 if (r->finfo.filetype != APR_REG)
377 return HTTP_FORBIDDEN;
379 /* Load the isapi extention without caching (sconf == NULL)
380 * but note that we will recover an existing cached module.
382 if (isapi_load(r->pool, sconf, r, r->filename, &isa) != APR_SUCCESS)
383 return HTTP_INTERNAL_SERVER_ERROR;
385 /* Set up variables */
386 ap_add_common_vars(r);
388 apr_table_setn(r->subprocess_env, "UNMAPPED_REMOTE_USER", "REMOTE_USER");
389 apr_table_setn(r->subprocess_env, "SERVER_PORT_SECURE", "0");
390 apr_table_setn(r->subprocess_env, "URL", r->uri);
392 /* Set up connection structure and ecb */
393 cid = apr_pcalloc(r->pool, sizeof(isapi_cid));
394 cid->sconf = ap_get_module_config(r->server->module_config, &isapi_module);
396 cid->ecb = apr_pcalloc(r->pool, sizeof(struct _EXTENSION_CONTROL_BLOCK));
397 cid->ecb->ConnID = (HCONN)cid;
401 cid->complete = NULL;
402 cid->completion = NULL;
404 cid->ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
405 cid->ecb->dwVersion = isa->reportversion;
406 cid->ecb->dwHttpStatusCode = 0;
407 strcpy(cid->ecb->lpszLogData, "");
408 // TODO: are copies really needed here?
409 cid->ecb->lpszMethod = apr_pstrdup(r->pool, (char*) r->method);
410 cid->ecb->lpszQueryString = apr_pstrdup(r->pool,
411 (char*) apr_table_get(e, "QUERY_STRING"));
412 cid->ecb->lpszPathInfo = apr_pstrdup(r->pool,
413 (char*) apr_table_get(e, "PATH_INFO"));
414 cid->ecb->lpszPathTranslated = apr_pstrdup(r->pool,
415 (char*) apr_table_get(e, "PATH_TRANSLATED"));
416 cid->ecb->lpszContentType = apr_pstrdup(r->pool,
417 (char*) apr_table_get(e, "CONTENT_TYPE"));
418 /* Set up the callbacks */
419 cid->ecb->GetServerVariable = GetServerVariable;
420 cid->ecb->WriteClient = WriteClient;
421 cid->ecb->ReadClient = ReadClient;
422 cid->ecb->ServerSupportFunction = ServerSupportFunction;
425 /* Set up client input */
426 rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
428 isapi_unload(isa, FALSE);
429 return HTTP_INTERNAL_SERVER_ERROR; /* XXX: The wrong error */
432 if (ap_should_client_block(r)) {
433 /* Time to start reading the appropriate amount of data,
434 * and allow the administrator to tweak the number
435 * TODO: add the httpd.conf option for ReadAheadBuffer.
438 cid->ecb->cbTotalBytes = r->remaining;
439 if (cid->ecb->cbTotalBytes > cid->sconf->ReadAheadBuffer)
440 cid->ecb->cbAvailable = cid->sconf->ReadAheadBuffer;
442 cid->ecb->cbAvailable = cid->ecb->cbTotalBytes;
446 cid->ecb->cbTotalBytes = 0xffffffff;
447 cid->ecb->cbAvailable = cid->sconf->ReadAheadBuffer;
450 cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1);
453 while (read < cid->ecb->cbAvailable &&
454 ((res = ap_get_client_block(r, cid->ecb->lpbData + read,
455 cid->ecb->cbAvailable - read)) > 0)) {
460 isapi_unload(isa, FALSE);
461 return HTTP_INTERNAL_SERVER_ERROR;
464 /* Although its not to spec, IIS seems to null-terminate
465 * its lpdData string. So we will too.
468 cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read;
470 cid->ecb->cbAvailable = read;
471 cid->ecb->lpbData[read] = '\0';
474 cid->ecb->cbTotalBytes = 0;
475 cid->ecb->cbAvailable = 0;
476 cid->ecb->lpbData = NULL;
479 /* All right... try and run the sucker */
480 rv = (*isa->HttpExtensionProc)(cid->ecb);
482 /* Check for a log message - and log it */
483 if (cid->ecb->lpszLogData && *cid->ecb->lpszLogData)
484 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
485 "ISAPI %s: %s", r->filename, cid->ecb->lpszLogData);
488 case HSE_STATUS_SUCCESS:
489 case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
490 /* Ignore the keepalive stuff; Apache handles it just fine without
491 * the ISA's "advice".
492 * Per Microsoft: "In IIS versions 4.0 and later, the return
493 * values HSE_STATUS_SUCCESS and HSE_STATUS_SUCCESS_AND_KEEP_CONN
494 * are functionally identical: Keep-Alive connections are
495 * maintained, if supported by the client."
496 * ... so we were pat all this time
500 case HSE_STATUS_PENDING:
501 /* emulating async behavior...
503 * Create a cid->completed event and wait on it for some timeout
504 * so that the app thinks is it running async.
506 * All async ServerSupportFunction calls will be handled through
507 * the registered IO_COMPLETION hook.
510 if (!isa->fakeasync) {
511 if (cid->sconf->LogNotSupported)
513 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
514 "ISAPI %s asynch I/O request refused",
516 cid->r->status = HTTP_INTERNAL_SERVER_ERROR;
520 cid->complete = CreateEvent(NULL, FALSE, FALSE, NULL);
521 if (WaitForSingleObject(cid->complete, isa->timeout)
523 /* TODO: Now what... if this hung, then do we kill our own
524 * thread to force it's death? For now leave timeout = -1
530 case HSE_STATUS_ERROR:
531 /* end response if we have yet to do so.
533 cid->r->status = HTTP_INTERNAL_SERVER_ERROR;
537 /* TODO: log unrecognized retval for debugging
539 cid->r->status = HTTP_INTERNAL_SERVER_ERROR;
543 /* Set the status (for logging) */
544 if (cid->ecb->dwHttpStatusCode) {
545 cid->r->status = cid->ecb->dwHttpStatusCode;
548 /* All done with the DLL... get rid of it... */
549 isapi_unload(isa, FALSE);
551 return OK; /* NOT r->status, even if it has changed. */
553 #pragma optimize("",on)
555 BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
556 LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer)
558 request_rec *r = ((isapi_cid *)hConn)->r;
561 if (!strcmp(lpszVariableName, "ALL_HTTP"))
563 /* lf delimited, colon split, comma seperated and
564 * null terminated list of HTTP_ vars
566 char **env = (char**) apr_table_elts(r->subprocess_env)->elts;
567 int n = apr_table_elts(r->subprocess_env)->nelts;
572 if (!strncmp(env[i], "HTTP_", 5))
573 len += strlen(env[i]) + strlen(env[i + 1]) + 2;
577 if (*lpdwSizeofBuffer < len + 1) {
578 SetLastError(ERROR_INSUFFICIENT_BUFFER);
584 if (!strncmp(env[i], "HTTP_", 5)) {
585 strcpy(lpvBuffer, env[i]);
586 ((char*)lpvBuffer) += strlen(env[i]);
587 *(((char*)lpvBuffer)++) = ':';
588 strcpy(lpvBuffer, env[i + 1]);
589 ((char*)lpvBuffer) += strlen(env[i + 1]);
590 *(((char*)lpvBuffer)++) = '\n';
594 *(((char*)lpvBuffer)++) = '\0';
595 *lpdwSizeofBuffer = len;
598 else if (!strcmp(lpszVariableName, "ALL_RAW"))
600 /* lf delimited, colon split, comma seperated and
601 * null terminated list of the raw request header
603 char **raw = (char**) apr_table_elts(r->headers_in)->elts;
604 int n = apr_table_elts(r->headers_in)->nelts;
609 len += strlen(raw[i]) + strlen(raw[i + 1]) + 2;
613 if (*lpdwSizeofBuffer < len + 1) {
614 SetLastError(ERROR_INSUFFICIENT_BUFFER);
620 strcpy(lpvBuffer, raw[i]);
621 ((char*)lpvBuffer) += strlen(raw[i]);
622 *(((char*)lpvBuffer)++) = ':';
623 *(((char*)lpvBuffer)++) = ' ';
624 strcpy(lpvBuffer, raw[i + 1]);
625 ((char*)lpvBuffer) += strlen(raw[i + 1]);
626 *(((char*)lpvBuffer)++) = '\n';
629 *(((char*)lpvBuffer)++) = '\0';
630 *lpdwSizeofBuffer = len;
634 result = apr_table_get(r->subprocess_env, lpszVariableName);
638 if (strlen(result) > *lpdwSizeofBuffer) {
639 *lpdwSizeofBuffer = strlen(result);
640 SetLastError(ERROR_INSUFFICIENT_BUFFER);
643 strncpy(lpvBuffer, result, *lpdwSizeofBuffer);
647 /* Didn't find it - should this be ERROR_NO_DATA? */
648 SetLastError(ERROR_INVALID_INDEX);
652 BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpwdwBytes,
655 request_rec *r = ((isapi_cid *)ConnID)->r;
656 int writ; /* written, actually, but why shouldn't I make up words? */
658 /* We only support synchronous writing */
659 if (dwReserved && dwReserved != HSE_IO_SYNC) {
660 if (((isapi_cid *)ConnID)->sconf->LogNotSupported)
661 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
662 "ISAPI %s asynch I/O request refused",
664 SetLastError(ERROR_INVALID_PARAMETER);
668 if ((writ = ap_rwrite(Buffer, *lpwdwBytes, r)) == EOF) {
669 SetLastError(WSAEDISCON); /* TODO: Find the right error code */
677 BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize)
679 request_rec *r = ((isapi_cid *)ConnID)->r;
683 if (r->remaining < (long) *lpdwSize)
684 *lpdwSize = r->remaining;
686 while (read < *lpdwSize &&
687 ((res = ap_get_client_block(r, (char*)lpvBuffer + read,
688 *lpdwSize - read)) > 0)) {
691 if (!apr_get_os_error())
692 apr_set_os_error(TODO_ERROR); /* XXX: Find the right error code */
703 static BOOL SendResponseHeaderEx(isapi_cid *cid, const char *stat,
704 const char *head, size_t statlen,
710 if (!stat || !*stat) {
711 stat = "Status: 200 OK";
716 statlen = strlen(stat);
717 /* Whoops... not NULL terminated */
718 newstat = apr_palloc(cid->r->pool, statlen + 9);
719 strcpy(newstat, "Status: ");
720 strncpy(newstat + 8, stat, statlen);
724 if (!head || !*head) {
727 else if ((headlen >= 0) && head[headlen]) {
728 /* Whoops... not NULL terminated */
729 head = apr_pstrndup(cid->r->pool, head, headlen);
732 /* Parse them out, or die trying */
733 cid->r->status= ap_scan_script_header_err_strs(cid->r, NULL, &termch,
734 &termarg, stat, head, NULL);
735 cid->ecb->dwHttpStatusCode = cid->r->status;
736 if (cid->r->status == HTTP_INTERNAL_SERVER_ERROR)
739 /* All the headers should be set now */
740 ap_send_http_header(cid->r);
742 /* Any data left should now be sent directly,
743 * it may be raw if headlen was provided.
745 if (termch && (termarg == 1)) {
746 if (headlen == -1 && *termch)
747 ap_rputs(termch, cid->r);
748 else if (headlen > (size_t) (termch - head))
749 ap_rwrite(termch, headlen - (termch - head), cid->r);
755 /* XXX: Is there is still an O(n^2) attack possible here? Please detail. */
756 BOOL WINAPI ServerSupportFunction (HCONN hConn, DWORD dwHSERequest,
757 LPVOID lpvBuffer, LPDWORD lpdwSize,
758 LPDWORD lpdwDataType)
760 isapi_cid *cid = (isapi_cid *)hConn;
761 request_rec *r = cid->r;
764 switch (dwHSERequest) {
765 case 1: /* HSE_REQ_SEND_URL_REDIRECT_RESP */
766 /* Set the status to be returned when the HttpExtensionProc()
768 * WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP
769 * and HSE_REQ_SEND_URL as equivalant per the Jan 2000 SDK.
770 * They most definately are not, even in their own samples.
772 apr_table_set (r->headers_out, "Location", lpvBuffer);
773 cid->r->status = cid->ecb->dwHttpStatusCode
774 = HTTP_MOVED_TEMPORARILY;
777 case 2: /* HSE_REQ_SEND_URL */
778 /* Soak up remaining input */
779 if (r->remaining > 0) {
780 char argsbuffer[HUGE_STRING_LEN];
781 while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN));
784 /* Reset the method to GET */
785 r->method = apr_pstrdup(r->pool, "GET");
786 r->method_number = M_GET;
788 /* Don't let anyone think there's still data */
789 apr_table_unset(r->headers_in, "Content-Length");
791 /* AV fault per PR3598 - redirected path is lost! */
792 (char*)lpvBuffer = apr_pstrdup(r->pool, (char*)lpvBuffer);
793 ap_internal_redirect((char*)lpvBuffer, r);
796 case 3: /* HSE_REQ_SEND_RESPONSE_HEADER */
797 /* Parse them out, or die trying */
798 return SendResponseHeaderEx(cid, (char*) lpvBuffer,
799 (char*) lpdwDataType, -1, -1);
802 case HSE_REQ_DONE_WITH_SESSION:
803 /* Signal to resume the thread completing this request
806 SetEvent(cid->complete);
809 case 1001: /* HSE_REQ_MAP_URL_TO_PATH */
811 /* Map a URL to a filename */
812 char *file = (char *)lpvBuffer;
813 subreq = ap_sub_req_lookup_uri(apr_pstrndup(r->pool, file, *lpdwSize), r);
815 strncpy(file, subreq->filename, *lpdwSize - 1);
816 file[*lpdwSize - 1] = '\0';
818 /* IIS puts a trailing slash on directories, Apache doesn't */
819 if (subreq->finfo.filetype == APR_DIR) {
820 DWORD l = strlen(file);
821 if (l < *lpdwSize - 1) {
829 case 1002: /* HSE_REQ_GET_SSPI_INFO */
830 if (cid->sconf->LogNotSupported)
831 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
832 "ISAPI ServerSupportFunction HSE_REQ_GET_SSPI_INFO "
833 "is not supported: %s", r->filename);
834 SetLastError(ERROR_INVALID_PARAMETER);
837 case 1003: /* HSE_APPEND_LOG_PARAMETER */
838 /* Log lpvBuffer, of lpdwSize bytes, in the URI Query (cs-uri-query) field
840 apr_table_set(r->notes, "isapi-parameter", (char*) lpvBuffer);
841 if (cid->sconf->AppendLogToQuery) {
843 r->args = apr_pstrcat(r->pool, r->args, (char*) lpvBuffer, NULL);
845 r->args = apr_pstrdup(r->pool, (char*) lpvBuffer);
847 if (cid->sconf->AppendLogToErrors)
848 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
849 "ISAPI %s: %s", cid->r->filename,
853 case 1005: /* HSE_REQ_IO_COMPLETION */
854 /* Emulates a completion port... Record callback address and
855 * user defined arg, we will call this after any async request
856 * (e.g. transmitfile) as if the request executed async.
857 * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call
858 * to HSE_REQ_IO_COMPLETION, and lpvBuffer may be set to NULL.
860 if (!cid->isa->fakeasync)
862 cid->completion = (PFN_HSE_IO_COMPLETION) lpvBuffer;
863 cid->completion_arg = (PVOID) lpdwDataType;
866 case 1006: /* HSE_REQ_TRANSMIT_FILE */
867 /* Use TransmitFile... nothing wrong with that :)
868 * Just not quite ready yet...
871 if (cid->sconf->LogNotSupported)
872 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
873 "ISAPI asynchronous I/O not supported: %s",
875 SetLastError(ERROR_INVALID_PARAMETER);
878 case 1007: /* HSE_REQ_REFRESH_ISAPI_ACL */
879 if (cid->sconf->LogNotSupported)
880 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
881 "ISAPI ServerSupportFunction "
882 "HSE_REQ_REFRESH_ISAPI_ACL "
883 "is not supported: %s", r->filename);
884 SetLastError(ERROR_INVALID_PARAMETER);
887 case 1008: /* HSE_REQ_IS_KEEP_CONN */
888 *((LPBOOL) lpvBuffer) = (r->connection->keepalive == 1);
891 case 1010: /* HSE_REQ_ASYNC_READ_CLIENT */
892 if (cid->sconf->LogNotSupported)
893 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
894 "ISAPI asynchronous I/O not supported: %s",
896 SetLastError(ERROR_INVALID_PARAMETER);
899 case 1011: /* HSE_REQ_GET_IMPERSONATION_TOKEN Added in ISAPI 4.0 */
900 if (cid->sconf->LogNotSupported)
901 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
902 "ISAPI ServerSupportFunction "
903 "HSE_REQ_GET_IMPERSONATION_TOKEN "
904 "is not supported: %s", r->filename);
905 SetLastError(ERROR_INVALID_PARAMETER);
908 case 1012: /* HSE_REQ_MAP_URL_TO_PATH_EX */
910 /* Map a URL to a filename */
911 LPHSE_URL_MAPEX_INFO info = (LPHSE_URL_MAPEX_INFO) lpdwDataType;
912 char* test_uri = apr_pstrndup(r->pool, (char *)lpvBuffer, *lpdwSize);
914 subreq = ap_sub_req_lookup_uri(test_uri, r);
915 info->lpszPath[MAX_PATH - 1] = '\0';
916 strncpy(info->lpszPath, subreq->filename, MAX_PATH - 1);
917 info->cchMatchingURL = strlen(test_uri);
918 info->cchMatchingPath = strlen(info->lpszPath);
919 /* Mapping started with assuming both strings matched.
920 * Now roll on the path_info as a mismatch and handle
921 * terminating slashes for directory matches.
923 if (subreq->path_info && *subreq->path_info) {
924 strncpy(info->lpszPath + info->cchMatchingPath, subreq->path_info,
925 MAX_PATH - info->cchMatchingPath - 1);
926 info->cchMatchingURL -= strlen(subreq->path_info);
927 if (subreq->finfo.filetype == APR_DIR
928 && info->cchMatchingPath < MAX_PATH - 1) {
929 /* roll forward over path_info's first slash */
930 ++info->cchMatchingPath;
931 ++info->cchMatchingURL;
934 else if (subreq->finfo.filetype == APR_DIR
935 && info->cchMatchingPath < MAX_PATH - 1) {
936 /* Add a trailing slash for directory */
937 info->lpszPath[info->cchMatchingPath++] = '/';
938 info->lpszPath[info->cchMatchingPath] = '\0';
941 /* If the matched isn't a file, roll match back to the prior slash */
942 if (subreq->finfo.filetype == APR_NOFILE) {
943 while (info->cchMatchingPath && info->cchMatchingURL) {
944 if (info->lpszPath[info->cchMatchingPath - 1] == '/')
946 --info->cchMatchingPath;
947 --info->cchMatchingURL;
951 /* Paths returned with back slashes */
952 for (test_uri = info->lpszPath; *test_uri; ++test_uri)
953 if (*test_uri == '/')
956 /* is a combination of:
957 * HSE_URL_FLAGS_READ 0x001 Allow read
958 * HSE_URL_FLAGS_WRITE 0x002 Allow write
959 * HSE_URL_FLAGS_EXECUTE 0x004 Allow execute
960 * HSE_URL_FLAGS_SSL 0x008 Require SSL
961 * HSE_URL_FLAGS_DONT_CACHE 0x010 Don't cache (VRoot only)
962 * HSE_URL_FLAGS_NEGO_CERT 0x020 Allow client SSL cert
963 * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert
964 * HSE_URL_FLAGS_MAP_CERT 0x080 Map client SSL cert to account
965 * HSE_URL_FLAGS_SSL128 0x100 Require 128-bit SSL cert
966 * HSE_URL_FLAGS_SCRIPT 0x200 Allow script execution
968 * XxX: As everywhere, EXEC flags could use some work...
969 * and this could go further with more flags, as desired.
971 info->dwFlags = (subreq->finfo.protection & APR_UREAD ? 0x001 : 0)
972 | (subreq->finfo.protection & APR_UWRITE ? 0x002 : 0)
973 | (subreq->finfo.protection & APR_UEXECUTE ? 0x204 : 0);
977 case 1014: /* HSE_REQ_ABORTIVE_CLOSE */
978 if (cid->sconf->LogNotSupported)
979 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
980 "ISAPI ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE"
981 " is not supported: %s", r->filename);
982 SetLastError(ERROR_INVALID_PARAMETER);
985 case 1015: /* HSE_REQ_GET_CERT_INFO_EX Added in ISAPI 4.0 */
986 if (cid->sconf->LogNotSupported)
987 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
988 "ISAPI ServerSupportFunction "
989 "HSE_REQ_GET_CERT_INFO_EX "
990 "is not supported: %s", r->filename);
991 SetLastError(ERROR_INVALID_PARAMETER);
994 case 1016: /* HSE_REQ_SEND_RESPONSE_HEADER_EX Added in ISAPI 4.0 */
996 LPHSE_SEND_HEADER_EX_INFO shi
997 = (LPHSE_SEND_HEADER_EX_INFO) lpvBuffer;
998 /* XXX: ignore shi->fKeepConn? We shouldn't need the advise */
999 /* r->connection->keepalive = shi->fKeepConn; */
1000 return SendResponseHeaderEx(cid, shi->pszStatus, shi->pszHeader,
1001 shi->cchStatus, shi->cchHeader);
1004 case 1017: /* HSE_REQ_CLOSE_CONNECTION Added after ISAPI 4.0 */
1005 if (cid->sconf->LogNotSupported)
1006 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
1007 "ISAPI ServerSupportFunction "
1008 "HSE_REQ_CLOSE_CONNECTION "
1009 "is not supported: %s", r->filename);
1010 SetLastError(ERROR_INVALID_PARAMETER);
1013 case 1018: /* HSE_REQ_IS_CONNECTED Added after ISAPI 4.0 */
1014 /* Returns True if client is connected c.f. MSKB Q188346
1015 * XXX: That statement is very ambigious... assuming the
1016 * identical return mechanism as HSE_REQ_IS_KEEP_CONN.
1018 *((LPBOOL) lpvBuffer) = (r->connection->aborted == 0);
1021 case 1020: /* HSE_REQ_EXTENSION_TRIGGER Added after ISAPI 4.0 */
1022 /* Undocumented - defined by the Microsoft Jan '00 Platform SDK
1024 if (cid->sconf->LogNotSupported)
1025 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
1026 "ISAPI ServerSupportFunction "
1027 "HSE_REQ_EXTENSION_TRIGGER "
1028 "is not supported: %s", r->filename);
1029 SetLastError(ERROR_INVALID_PARAMETER);
1033 if (cid->sconf->LogNotSupported)
1034 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
1035 "ISAPI ServerSupportFunction (%d) not supported: "
1036 "%s", dwHSERequest, r->filename);
1037 SetLastError(ERROR_INVALID_PARAMETER);
1043 * Command handler for the ISAPIReadAheadBuffer directive, which is TAKE1
1045 static const char *isapi_cmd_readaheadbuffer(cmd_parms *cmd, void *config,
1048 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1053 if (((val = strtol(arg, (char **) &scan, 10)) <= 0) || *scan)
1054 return "ISAPIReadAheadBuffer must be a legitimate value.";
1056 sconf->ReadAheadBuffer = val;
1061 * Command handler for the ISAPIReadAheadBuffer directive, which is TAKE1
1063 static const char *isapi_cmd_lognotsupported(cmd_parms *cmd, void *config,
1066 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1069 if (strcasecmp(arg, "on") == 0) {
1070 sconf->LogNotSupported = -1;
1072 else if (strcasecmp(arg, "off") == 0) {
1073 sconf->LogNotSupported = 0;
1076 return "ISAPILogNotSupported must be on or off";
1081 static const char *isapi_cmd_appendlogtoerrors(cmd_parms *cmd, void *config,
1084 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1087 if (strcasecmp(arg, "on") == 0) {
1088 sconf->AppendLogToErrors = -1;
1090 else if (strcasecmp(arg, "off") == 0) {
1091 sconf->AppendLogToErrors = 0;
1094 return "ISAPIAppendLogToErrors must be on or off";
1099 static const char *isapi_cmd_appendlogtoquery(cmd_parms *cmd, void *config,
1102 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1105 if (strcasecmp(arg, "on") == 0) {
1106 sconf->AppendLogToQuery = -1;
1108 else if (strcasecmp(arg, "off") == 0) {
1109 sconf->AppendLogToQuery = 0;
1112 return "ISAPIAppendLogToQuery must be on or off";
1117 static const char *isapi_cmd_cachefile(cmd_parms *cmd, void *dummy,
1118 const char *filename)
1121 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1123 isapi_loaded *isa, **newisa;
1128 fspec = ap_os_case_canonical_filename(cmd->pool, filename);
1129 if (apr_stat(&tmp, fspec, cmd->temp_pool) != APR_SUCCESS) {
1130 ap_log_error(APLOG_MARK, APLOG_WARNING, errno, cmd->server,
1131 "ISAPI: unable to stat(%s), skipping", filename);
1134 if (tmp.filetype != APR_REG) {
1135 ap_log_error(APLOG_MARK, APLOG_WARNING, errno, cmd->server,
1136 "ISAPI: %s isn't a regular file, skipping", filename);
1140 /* Load the extention as cached (passing sconf) */
1141 rv = isapi_load(cmd->pool, sconf, NULL, fspec, &isa);
1142 if (rv != APR_SUCCESS) {
1143 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server,
1144 "ISAPI: unable to cache %s, skipping", filename);
1148 /* Add to cached list of loaded modules */
1149 newisa = apr_push_array(sconf->loaded);
1155 static void isapi_hooks(void)
1157 ap_hook_post_config(isapi_post_config, NULL, NULL, AP_HOOK_MIDDLE);
1160 static const command_rec isapi_cmds[] = {
1161 AP_INIT_TAKE1("ISAPIReadAheadBuffer", isapi_cmd_readaheadbuffer, NULL, RSRC_CONF,
1162 "Maximum bytes to initially pass to the ISAPI handler"),
1163 AP_INIT_TAKE1("ISAPILogNotSupported", isapi_cmd_lognotsupported, NULL, RSRC_CONF,
1164 "Log requests not supported by the ISAPI server"),
1165 AP_INIT_TAKE1("ISAPIAppendLogToErrors", isapi_cmd_appendlogtoerrors, NULL, RSRC_CONF,
1166 "Send all Append Log requests to the error log"),
1167 AP_INIT_TAKE1("ISAPIAppendLogToQuery", isapi_cmd_appendlogtoquery, NULL, RSRC_CONF,
1168 "Append Log requests are concatinated to the query args"),
1169 AP_INIT_ITERATE("ISAPICacheFile", isapi_cmd_cachefile, NULL, RSRC_CONF,
1170 "Cache the specified ISAPI extension in-process"),
1174 handler_rec isapi_handlers[] = {
1175 { "isapi-isa", isapi_handler },
1179 module isapi_module = {
1180 STANDARD20_MODULE_STUFF,
1181 NULL, /* create per-dir config */
1182 NULL, /* merge per-dir config */
1183 create_isapi_server_config, /* server config */
1184 NULL, /* merge server config */
1185 isapi_cmds, /* command apr_table_t */
1186 isapi_handlers, /* handlers */
1187 isapi_hooks /* register hooks */