]> granicus.if.org Git - apache/blob - os/win32/mod_isapi.c
Here it is, the Win32 part of the big canonical errors patch.
[apache] / os / win32 / mod_isapi.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
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
17  *    distribution.
18  *
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.
25  *
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.
30  *
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.
34  *
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
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
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/>.
53  *
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.
57  */
58
59 /*
60  * mod_isapi.c - Internet Server Application (ISA) module for Apache
61  * by Alexei Kosut <akosut@apache.org>
62  *
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
69  * function as well).
70  *
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
75  * the ISA is in.
76  */
77
78 #include "ap_config.h"
79 #include "httpd.h"
80 #include "http_config.h"
81 #include "http_core.h"
82 #include "http_protocol.h"
83 #include "http_request.h"
84 #include "http_log.h"
85 #include "util_script.h"
86 #include "apr_portable.h"
87 #include "apr_strings.h"
88
89
90 /* We use the exact same header file as the original */
91 #include <HttpExt.h>
92
93 /* TODO: Unknown errors that must be researched for correct codes */
94
95 #define TODO_ERROR 1
96
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
100
101 module isapi_module;
102
103 /* Declare the ISAPI functions */
104
105 BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
106                                LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer);
107 BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpwdwBytes,
108                          DWORD dwReserved);
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);
113
114 /*
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.
119
120     The revisions below may eliminate this artifact.
121 */
122 #pragma optimize("y",off)
123
124 /* Our isapi server config structure */
125
126 typedef struct {
127     HANDLE lock;
128     apr_array_header_t *loaded;
129     DWORD ReadAheadBuffer;
130     int LogNotSupported;
131     int AppendLogToErrors;
132     int AppendLogToQuery;
133 } isapi_server_conf;
134
135 /* Our loaded isapi module description structure */
136
137 typedef struct {
138     const char *filename;
139     HINSTANCE handle;
140     HSE_VERSION_INFO *pVer;
141     PFN_GETEXTENSIONVERSION GetExtensionVersion;
142     PFN_HTTPEXTENSIONPROC   HttpExtensionProc;
143     PFN_TERMINATEEXTENSION  TerminateExtension;
144     int   refcount;
145     DWORD timeout;
146     BOOL  fakeasync;
147     DWORD reportversion;
148 } isapi_loaded;
149
150 /* Our "Connection ID" structure */
151
152 typedef struct {
153     LPEXTENSION_CONTROL_BLOCK ecb;
154     isapi_server_conf *sconf;
155     isapi_loaded *isa;
156     request_rec  *r;
157     PFN_HSE_IO_COMPLETION completion;
158     PVOID  completion_arg;
159     HANDLE complete;
160 } isapi_cid;
161
162 static BOOL isapi_unload(isapi_loaded* isa, int force);
163
164 static apr_status_t cleanup_isapi_server_config(void *sconfv)
165 {
166     isapi_server_conf *sconf = sconfv;
167     size_t n;
168     isapi_loaded **isa;
169  
170     n = sconf->loaded->nelts;
171     isa = (isapi_loaded **)sconf->loaded->elts;
172     while(n--) {
173         if ((*isa)->handle)
174             isapi_unload(*isa, TRUE); 
175         ++isa;
176     }
177     CloseHandle(sconf->lock);
178     return APR_SUCCESS;
179 }
180
181 static void *create_isapi_server_config(apr_pool_t *p, server_rec *s)
182 {
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);
186
187     sconf->ReadAheadBuffer = 49152;
188     sconf->LogNotSupported    = -1;
189     sconf->AppendLogToErrors   = 0;
190     sconf->AppendLogToQuery    = 0;
191
192     apr_register_cleanup(p, sconf, cleanup_isapi_server_config, 
193                                    apr_null_cleanup);
194
195     return sconf;
196 }
197
198 static int compare_loaded(const void *av, const void *bv)
199 {
200     const isapi_loaded **a = av;
201     const isapi_loaded **b = bv;
202
203     return strcmp((*a)->filename, (*b)->filename);
204 }
205
206 static void isapi_post_config(apr_pool_t *p, apr_pool_t *plog,
207                               apr_pool_t *ptemp, server_rec *s)
208 {
209     isapi_server_conf *sconf = ap_get_module_config(s->module_config, 
210                                                     &isapi_module);
211     isapi_loaded **elts = (isapi_loaded **)sconf->loaded->elts;
212     int nelts = sconf->loaded->nelts;
213
214     /* sort the elements of the main_server, by filename */
215     qsort(elts, nelts, sizeof(isapi_loaded*), compare_loaded);
216
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);
220     }
221 }
222
223 static apr_status_t isapi_load(apr_pool_t *p, isapi_server_conf *sconf, 
224                                request_rec *r, const char *fpath, 
225                                isapi_loaded** isa)
226 {
227     isapi_loaded **found = (isapi_loaded **)sconf->loaded->elts;
228     char *fspec;
229     char *ch;
230     int n;
231
232     for (n = 0; n < sconf->loaded->nelts; ++n) {
233         if (strcasecmp(fpath, (*found)->filename) == 0) {
234             break;
235         }
236         ++found;
237     }
238     
239     if (n < sconf->loaded->nelts) 
240     {
241         *isa = *found;
242         if ((*isa)->handle) 
243         {
244             ++(*isa)->refcount;
245             return APR_SUCCESS;
246         }
247         /* Otherwise we fall through and have to reload the resource
248          * into this existing mod_isapi cache bucket.
249          */
250     }
251     else
252     {
253         *isa = apr_pcalloc(p, sizeof(isapi_module));
254         (*isa)->filename = fpath;
255         (*isa)->pVer = apr_pcalloc(p, sizeof(HSE_VERSION_INFO));
256     
257         /* TODO: These need to become overrideable, so that we
258          * assure a given isapi can be fooled into behaving well.
259          */
260         (*isa)->timeout = INFINITE; /* microsecs */
261         (*isa)->fakeasync = TRUE;
262         (*isa)->reportversion = MAKELONG(0, 5); /* Revision 5.0 */
263     }
264         
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.
269      *
270      * Transpose '\' for '/' in the filename.
271      */
272     ch = fspec = apr_pstrdup(p, fpath);
273     while (*ch) {
274         if (*ch == '/')
275             *ch = '\\';
276         ++ch;
277     }
278
279     (*isa)->handle = LoadLibraryEx(fspec, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
280
281     if (!(*isa)->handle)
282     {
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;
287         return rv;
288     }
289
290     if (!((*isa)->GetExtensionVersion = (void *)(GetProcAddress((*isa)->handle,
291                                                       "GetExtensionVersion"))))
292     {
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()",
296                       fpath);
297         FreeLibrary((*isa)->handle);
298         (*isa)->handle = NULL;
299         return rv;
300     }
301
302     if (!((*isa)->HttpExtensionProc = (void *)(GetProcAddress((*isa)->handle,
303                                                        "HttpExtensionProc")))) 
304     {
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()",
308                       fpath);
309         FreeLibrary((*isa)->handle);
310         (*isa)->handle = NULL;
311         return rv;
312     }
313
314     /* TerminateExtension() is an optional interface */
315     (*isa)->TerminateExtension = (void *)(GetProcAddress((*isa)->handle, 
316                                                        "TerminateExtension"));
317     SetLastError(0);
318
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", 
324                       fpath);
325         FreeLibrary((*isa)->handle);
326         (*isa)->handle = NULL;
327         return rv;
328     }
329
330     ++(*isa)->refcount;
331
332     return APR_SUCCESS;
333 }
334
335 static int isapi_unload(isapi_loaded* isa, int force)
336 {
337     /* All done with the DLL... get rid of it...
338      *
339      * If optionally cached, pass HSE_TERM_ADVISORY_UNLOAD,
340      * and if it returns TRUE, unload, otherwise, cache it.
341      */
342     if ((--isa->refcount > 0) && !force)
343         return FALSE;
344     if (isa->TerminateExtension) {
345         if (force)
346             (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD);
347         else if (!(*isa->TerminateExtension)(HSE_TERM_ADVISORY_UNLOAD))
348             return FALSE;
349     }
350     FreeLibrary(isa->handle);
351     isa->handle = NULL;
352     return TRUE;
353 }
354
355 apr_status_t isapi_handler (request_rec *r)
356 {
357     isapi_server_conf *sconf = ap_get_module_config(r->server->module_config, 
358                                                     &isapi_module);
359     apr_table_t *e = r->subprocess_env;
360     apr_status_t rv;
361     isapi_loaded *isa;
362     isapi_cid *cid;
363     DWORD read;
364     int res;
365     
366     /* Use similar restrictions as CGIs
367      *
368      * If this fails, it's pointless to load the isapi dll.
369      */
370     if (!(ap_allow_options(r) & OPT_EXECCGI))
371         return HTTP_FORBIDDEN;
372
373     if (r->finfo.filetype == APR_NOFILE)
374         return HTTP_NOT_FOUND;
375
376     if (r->finfo.filetype != APR_REG)
377         return HTTP_FORBIDDEN;
378
379     /* Load the isapi extention without caching (sconf == NULL) 
380      * but note that we will recover an existing cached module.
381      */
382     if (isapi_load(r->pool, sconf, r, r->filename, &isa) != APR_SUCCESS)
383         return HTTP_INTERNAL_SERVER_ERROR;
384         
385     /* Set up variables */
386     ap_add_common_vars(r);
387     ap_add_cgi_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);
391
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);
395
396     cid->ecb = apr_pcalloc(r->pool, sizeof(struct _EXTENSION_CONTROL_BLOCK));
397     cid->ecb->ConnID = (HCONN)cid;
398     cid->isa = isa;
399     cid->r = r;
400     cid->r->status = 0;
401     cid->complete = NULL;
402     cid->completion = NULL;
403     
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;
423
424     
425     /* Set up client input */
426     rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
427     if (rv) {
428         isapi_unload(isa, FALSE);
429         return HTTP_INTERNAL_SERVER_ERROR; /* XXX: The wrong error */
430     }
431
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.
436          */
437         if (r->remaining) {
438             cid->ecb->cbTotalBytes = r->remaining;
439             if (cid->ecb->cbTotalBytes > cid->sconf->ReadAheadBuffer)
440                 cid->ecb->cbAvailable = cid->sconf->ReadAheadBuffer;
441             else
442                 cid->ecb->cbAvailable = cid->ecb->cbTotalBytes;
443         }
444         else
445         {
446             cid->ecb->cbTotalBytes = 0xffffffff;
447             cid->ecb->cbAvailable = cid->sconf->ReadAheadBuffer;
448         }
449
450         cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1);
451
452         read = 0;
453         while (read < cid->ecb->cbAvailable &&
454                ((res = ap_get_client_block(r, cid->ecb->lpbData + read,
455                                         cid->ecb->cbAvailable - read)) > 0)) {
456             read += res;
457         }
458
459         if (res < 0) {
460             isapi_unload(isa, FALSE);
461             return HTTP_INTERNAL_SERVER_ERROR;
462         }
463
464         /* Although its not to spec, IIS seems to null-terminate
465          * its lpdData string. So we will too.
466          */
467         if (res == 0)
468             cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read;
469         else
470             cid->ecb->cbAvailable = read;
471         cid->ecb->lpbData[read] = '\0';
472     }
473     else {
474         cid->ecb->cbTotalBytes = 0;
475         cid->ecb->cbAvailable = 0;
476         cid->ecb->lpbData = NULL;
477     }
478
479     /* All right... try and run the sucker */
480     rv = (*isa->HttpExtensionProc)(cid->ecb);
481
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);
486
487     switch(rv) {
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
497              */
498             break;
499             
500         case HSE_STATUS_PENDING:    
501             /* emulating async behavior...
502              *
503              * Create a cid->completed event and wait on it for some timeout
504              * so that the app thinks is it running async.
505              *
506              * All async ServerSupportFunction calls will be handled through
507              * the registered IO_COMPLETION hook.
508              */
509             
510             if (!isa->fakeasync) {
511                 if (cid->sconf->LogNotSupported)
512                 {
513                      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
514                                    "ISAPI %s asynch I/O request refused", 
515                                    r->filename);
516                      cid->r->status = HTTP_INTERNAL_SERVER_ERROR;
517                 }
518             }
519             else {
520                 cid->complete = CreateEvent(NULL, FALSE, FALSE, NULL);
521                 if (WaitForSingleObject(cid->complete, isa->timeout)
522                         == WAIT_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
525                      */
526                 }
527             }
528             break;
529
530         case HSE_STATUS_ERROR:    
531             /* end response if we have yet to do so.
532              */
533             cid->r->status = HTTP_INTERNAL_SERVER_ERROR;
534             break;
535
536         default:
537             /* TODO: log unrecognized retval for debugging 
538              */
539             cid->r->status = HTTP_INTERNAL_SERVER_ERROR;
540             break;
541     }
542
543     /* Set the status (for logging) */
544     if (cid->ecb->dwHttpStatusCode) {
545         cid->r->status = cid->ecb->dwHttpStatusCode;
546     }
547
548     /* All done with the DLL... get rid of it... */
549     isapi_unload(isa, FALSE);
550     
551     return OK;          /* NOT r->status, even if it has changed. */
552 }
553 #pragma optimize("",on)
554
555 BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
556                                LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer)
557 {
558     request_rec *r = ((isapi_cid *)hConn)->r;
559     const char *result;
560
561     if (!strcmp(lpszVariableName, "ALL_HTTP")) 
562     {
563         /* lf delimited, colon split, comma seperated and 
564          * null terminated list of HTTP_ vars 
565          */
566         char **env = (char**) apr_table_elts(r->subprocess_env)->elts;
567         int n = apr_table_elts(r->subprocess_env)->nelts;
568         DWORD len = 0;
569         int i = 0;
570
571         while (i < n * 2) {
572             if (!strncmp(env[i], "HTTP_", 5))
573                 len += strlen(env[i]) + strlen(env[i + 1]) + 2;
574             i += 2;
575         }
576   
577         if (*lpdwSizeofBuffer < len + 1) {
578             SetLastError(ERROR_INSUFFICIENT_BUFFER);
579             return FALSE;
580         }
581     
582         i = 0;
583         while (i < n * 2) {
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';
591             }
592             i += 2;
593         }
594         *(((char*)lpvBuffer)++) = '\0';
595         *lpdwSizeofBuffer = len;
596         return TRUE;
597     }
598     else if (!strcmp(lpszVariableName, "ALL_RAW")) 
599     {
600         /* lf delimited, colon split, comma seperated and 
601          * null terminated list of the raw request header
602          */
603         char **raw = (char**) apr_table_elts(r->headers_in)->elts;
604         int n = apr_table_elts(r->headers_in)->nelts;
605         DWORD len = 0;
606         int i = 0;
607
608         while (i < n * 2) {
609             len += strlen(raw[i]) + strlen(raw[i + 1]) + 2;
610             i += 2;
611         }
612   
613         if (*lpdwSizeofBuffer < len + 1) {
614             SetLastError(ERROR_INSUFFICIENT_BUFFER);
615             return FALSE;
616         }
617     
618         i = 0;
619         while (i < n * 2) {
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';
627             i += 2;
628         }
629         *(((char*)lpvBuffer)++) = '\0';
630         *lpdwSizeofBuffer = len;
631         return TRUE;
632     }
633     else {
634         result = apr_table_get(r->subprocess_env, lpszVariableName);
635     }
636
637     if (result) {
638         if (strlen(result) > *lpdwSizeofBuffer) {
639             *lpdwSizeofBuffer = strlen(result);
640             SetLastError(ERROR_INSUFFICIENT_BUFFER);
641             return FALSE;
642         }
643         strncpy(lpvBuffer, result, *lpdwSizeofBuffer);
644         return TRUE;
645     }
646
647     /* Didn't find it - should this be ERROR_NO_DATA? */
648     SetLastError(ERROR_INVALID_INDEX);
649     return FALSE;
650 }
651
652 BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpwdwBytes,
653                          DWORD dwReserved)
654 {
655     request_rec *r = ((isapi_cid *)ConnID)->r;
656     int writ;   /* written, actually, but why shouldn't I make up words? */
657
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",
663                           r->filename);
664         SetLastError(ERROR_INVALID_PARAMETER);
665         return FALSE;
666     }
667
668     if ((writ = ap_rwrite(Buffer, *lpwdwBytes, r)) == EOF) {
669         SetLastError(WSAEDISCON); /* TODO: Find the right error code */
670         return FALSE;
671     }
672
673     *lpwdwBytes = writ;
674     return TRUE;
675 }
676
677 BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize)
678 {
679     request_rec *r = ((isapi_cid *)ConnID)->r;
680     DWORD read = 0;
681     int res;
682
683     if (r->remaining < (long) *lpdwSize)
684         *lpdwSize = r->remaining;
685
686     while (read < *lpdwSize &&
687            ((res = ap_get_client_block(r, (char*)lpvBuffer + read,
688                                        *lpdwSize - read)) > 0)) {
689         if (res < 0) {
690             *lpdwSize = 0;
691             if (!apr_get_os_error())
692                 apr_set_os_error(TODO_ERROR); /* XXX: Find the right error code */
693             return FALSE;
694         }
695
696         read += res;
697     }
698
699     *lpdwSize = read;
700     return TRUE;
701 }
702
703 static BOOL SendResponseHeaderEx(isapi_cid *cid, const char *stat,
704                                  const char *head, size_t statlen,
705                                  size_t headlen)
706 {
707     int termarg;
708     char *termch;
709
710     if (!stat || !*stat) {
711         stat = "Status: 200 OK";
712     }
713     else {
714         char *newstat;
715         if (statlen == 0)
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);
721         stat = newstat;
722     }
723
724     if (!head || !*head) {
725         head = "\r\n";
726     }
727     else if ((headlen >= 0) && head[headlen]) {
728         /* Whoops... not NULL terminated */
729         head = apr_pstrndup(cid->r->pool, head, headlen);
730     }
731
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)
737         return FALSE;
738     
739     /* All the headers should be set now */
740     ap_send_http_header(cid->r);
741
742     /* Any data left should now be sent directly,
743      * it may be raw if headlen was provided.
744      */
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);
750     }
751
752     return TRUE;
753 }
754
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)
759 {
760     isapi_cid *cid = (isapi_cid *)hConn;
761     request_rec *r = cid->r;
762     request_rec *subreq;
763
764     switch (dwHSERequest) {
765     case 1: /* HSE_REQ_SEND_URL_REDIRECT_RESP */
766         /* Set the status to be returned when the HttpExtensionProc()
767          * is done.
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.
771          */
772         apr_table_set (r->headers_out, "Location", lpvBuffer);
773         cid->r->status = cid->ecb->dwHttpStatusCode 
774                                                = HTTP_MOVED_TEMPORARILY;
775         return TRUE;
776
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));
782         }
783
784         /* Reset the method to GET */
785         r->method = apr_pstrdup(r->pool, "GET");
786         r->method_number = M_GET;
787
788         /* Don't let anyone think there's still data */
789         apr_table_unset(r->headers_in, "Content-Length");
790
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);
794         return TRUE;
795
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);
800
801
802         case HSE_REQ_DONE_WITH_SESSION:
803             /* Signal to resume the thread completing this request
804              */
805             if (cid->complete)
806                 SetEvent(cid->complete);
807             return TRUE;
808
809     case 1001: /* HSE_REQ_MAP_URL_TO_PATH */
810     {
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);
814
815         strncpy(file, subreq->filename, *lpdwSize - 1);
816         file[*lpdwSize - 1] = '\0';
817
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) {
822                 file[l] = '\\';
823                 file[l + 1] = '\0';
824             }
825         }
826         return TRUE;
827     }
828
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);
835         return FALSE;
836         
837     case 1003: /* HSE_APPEND_LOG_PARAMETER */
838         /* Log lpvBuffer, of lpdwSize bytes, in the URI Query (cs-uri-query) field
839          */
840         apr_table_set(r->notes, "isapi-parameter", (char*) lpvBuffer);
841         if (cid->sconf->AppendLogToQuery) {
842             if (r->args)
843                 r->args = apr_pstrcat(r->pool, r->args, (char*) lpvBuffer, NULL);
844             else
845                 r->args = apr_pstrdup(r->pool, (char*) lpvBuffer);
846         }
847         if (cid->sconf->AppendLogToErrors)
848             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
849                           "ISAPI %s: %s", cid->r->filename,
850                           (char*) lpvBuffer);
851         return TRUE;
852         
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.
859          */
860         if (!cid->isa->fakeasync)
861             return FALSE;
862         cid->completion = (PFN_HSE_IO_COMPLETION) lpvBuffer;
863         cid->completion_arg = (PVOID) lpdwDataType;
864         return TRUE;
865
866     case 1006: /* HSE_REQ_TRANSMIT_FILE */
867         /* Use TransmitFile... nothing wrong with that :)
868          * Just not quite ready yet...
869          */
870
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", 
874                           r->filename);
875         SetLastError(ERROR_INVALID_PARAMETER);
876         return FALSE;
877             
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);
885         return FALSE;
886
887     case 1008: /* HSE_REQ_IS_KEEP_CONN */
888         *((LPBOOL) lpvBuffer) = (r->connection->keepalive == 1);
889         return TRUE;
890
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", 
895                           r->filename);
896         SetLastError(ERROR_INVALID_PARAMETER);
897         return FALSE;
898
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);
906         return FALSE;
907
908     case 1012: /* HSE_REQ_MAP_URL_TO_PATH_EX */
909     {
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);
913
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.
922          */
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;
932             }
933         }
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';
939         }
940
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] == '/') 
945                     break;
946                 --info->cchMatchingPath;
947                 --info->cchMatchingURL;
948             }
949         }
950         
951         /* Paths returned with back slashes */
952         for (test_uri = info->lpszPath; *test_uri; ++test_uri)
953             if (*test_uri == '/')
954                 *test_uri = '\\';
955         
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
967          *
968          * XxX: As everywhere, EXEC flags could use some work...
969          *      and this could go further with more flags, as desired.
970          */ 
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);
974         return TRUE;
975     }
976
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);
983             return FALSE;
984
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);
992             return FALSE;
993
994     case 1016: /* HSE_REQ_SEND_RESPONSE_HEADER_EX  Added in ISAPI 4.0 */
995     {
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);
1002     }
1003
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);
1011         return FALSE;
1012
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.
1017          */
1018         *((LPBOOL) lpvBuffer) = (r->connection->aborted == 0);
1019         return TRUE;
1020
1021     case 1020: /* HSE_REQ_EXTENSION_TRIGGER  Added after ISAPI 4.0 */
1022         /*  Undocumented - defined by the Microsoft Jan '00 Platform SDK
1023          */
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);
1030         return FALSE;
1031
1032     default:
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);
1038         return FALSE;
1039     }
1040 }
1041
1042 /*
1043  * Command handler for the ISAPIReadAheadBuffer directive, which is TAKE1
1044  */
1045 static const char *isapi_cmd_readaheadbuffer(cmd_parms *cmd, void *config, 
1046                                              char *arg)
1047 {
1048     isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1049                                                    &isapi_module);
1050     char *scan;
1051     long val;
1052
1053     if (((val = strtol(arg, (char **) &scan, 10)) <= 0) || *scan)
1054         return "ISAPIReadAheadBuffer must be a legitimate value.";
1055     
1056     sconf->ReadAheadBuffer = val;
1057     return NULL;
1058 }
1059
1060 /*
1061  * Command handler for the ISAPIReadAheadBuffer directive, which is TAKE1
1062  */
1063 static const char *isapi_cmd_lognotsupported(cmd_parms *cmd, void *config, 
1064                                              char *arg)
1065 {
1066     isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1067                                                &isapi_module);
1068
1069     if (strcasecmp(arg, "on") == 0) {
1070         sconf->LogNotSupported = -1;
1071     }
1072     else if (strcasecmp(arg, "off") == 0) {
1073         sconf->LogNotSupported = 0;
1074     }
1075     else {
1076         return "ISAPILogNotSupported must be on or off";
1077     }
1078     return NULL;
1079 }
1080
1081 static const char *isapi_cmd_appendlogtoerrors(cmd_parms *cmd, void *config, 
1082                                                char *arg)
1083 {
1084     isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1085                                                    &isapi_module);
1086
1087     if (strcasecmp(arg, "on") == 0) {
1088         sconf->AppendLogToErrors = -1;
1089     }
1090     else if (strcasecmp(arg, "off") == 0) {
1091         sconf->AppendLogToErrors = 0;
1092     }
1093     else {
1094         return "ISAPIAppendLogToErrors must be on or off";
1095     }
1096     return NULL;
1097 }
1098
1099 static const char *isapi_cmd_appendlogtoquery(cmd_parms *cmd, void *config, 
1100                                                char *arg)
1101 {
1102     isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1103                                                    &isapi_module);
1104
1105     if (strcasecmp(arg, "on") == 0) {
1106         sconf->AppendLogToQuery = -1;
1107     }
1108     else if (strcasecmp(arg, "off") == 0) {
1109         sconf->AppendLogToQuery = 0;
1110     }
1111     else {
1112         return "ISAPIAppendLogToQuery must be on or off";
1113     }
1114     return NULL;
1115 }
1116
1117 static const char *isapi_cmd_cachefile(cmd_parms *cmd, void *dummy, 
1118                                        const char *filename)
1119
1120 {
1121     isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config, 
1122                                                     &isapi_module);
1123     isapi_loaded *isa, **newisa;
1124     apr_finfo_t tmp;
1125     apr_status_t rv;
1126     char *fspec;
1127     
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);
1132         return NULL;
1133     }
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);
1137         return NULL;
1138     }
1139
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);
1145         return NULL;
1146     }
1147
1148     /* Add to cached list of loaded modules */
1149     newisa = apr_push_array(sconf->loaded);
1150     *newisa = isa;
1151     
1152     return NULL;
1153 }
1154
1155 static void isapi_hooks(void)
1156 {
1157     ap_hook_post_config(isapi_post_config, NULL, NULL, AP_HOOK_MIDDLE);
1158 }
1159
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"),
1171 { NULL }
1172 };
1173
1174 handler_rec isapi_handlers[] = {
1175     { "isapi-isa", isapi_handler },
1176     { NULL}
1177 };
1178
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 */
1188 };