1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * mod_isapi.c - Internet Server Application (ISA) module for Apache
19 * by Alexei Kosut <akosut@apache.org>, significant overhauls and
20 * redesign by William Rowe <wrowe@covalent.net>, and hints from many
21 * other developer/users who have hit on specific flaws.
22 *
23 * This module implements the ISAPI Handler architecture, allowing
24 * Apache to load Internet Server Applications (ISAPI extensions),
25 * similar to the support in IIS, Zope, O'Reilly's WebSite and others.
26 *
27 * It is a complete implementation of the ISAPI 2.0 specification,
28 * except for "Microsoft extensions" to the API which provide
29 * asynchronous I/O.  It is further extended to include additional
30 * "Microsoft extentions" through IIS 5.0, with some deficiencies
31 * where one-to-one mappings don't exist.
32 *
33 * Refer to /manual/mod/mod_isapi.html for additional details on
34 * configuration and use, but check this source for specific support
35 * of the API,
36 */
37
38#include "ap_config.h"
39#include "httpd.h"
40#include "http_config.h"
41#include "http_core.h"
42#include "http_protocol.h"
43#include "http_request.h"
44#include "http_log.h"
45#include "util_script.h"
46#include "mod_core.h"
47#include "apr_lib.h"
48#include "apr_strings.h"
49#include "apr_portable.h"
50#include "apr_buckets.h"
51#include "apr_thread_mutex.h"
52#include "apr_thread_rwlock.h"
53#include "apr_hash.h"
54#include "mod_isapi.h"
55
56/* Retry frequency for a failed-to-load isapi .dll */
57#define ISAPI_RETRY apr_time_from_sec(30)
58
59/**********************************************************
60 *
61 *  ISAPI Module Configuration
62 *
63 **********************************************************/
64
65module AP_MODULE_DECLARE_DATA isapi_module;
66
67#define ISAPI_UNDEF -1
68
69/* Our isapi per-dir config structure */
70typedef struct isapi_dir_conf {
71    int read_ahead_buflen;
72    int log_unsupported;
73    int log_to_errlog;
74    int log_to_query;
75    int fake_async;
76} isapi_dir_conf;
77
78typedef struct isapi_loaded isapi_loaded;
79
80apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r,
81                          const char *fpath, isapi_loaded** isa);
82
83static void *create_isapi_dir_config(apr_pool_t *p, char *dummy)
84{
85    isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf));
86
87    dir->read_ahead_buflen = ISAPI_UNDEF;
88    dir->log_unsupported   = ISAPI_UNDEF;
89    dir->log_to_errlog     = ISAPI_UNDEF;
90    dir->log_to_query      = ISAPI_UNDEF;
91    dir->fake_async        = ISAPI_UNDEF;
92
93    return dir;
94}
95
96static void *merge_isapi_dir_configs(apr_pool_t *p, void *base_, void *add_)
97{
98    isapi_dir_conf *base = (isapi_dir_conf *) base_;
99    isapi_dir_conf *add = (isapi_dir_conf *) add_;
100    isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf));
101
102    dir->read_ahead_buflen = (add->read_ahead_buflen == ISAPI_UNDEF)
103                                ? base->read_ahead_buflen
104                                 : add->read_ahead_buflen;
105    dir->log_unsupported   = (add->log_unsupported == ISAPI_UNDEF)
106                                ? base->log_unsupported
107                                 : add->log_unsupported;
108    dir->log_to_errlog     = (add->log_to_errlog == ISAPI_UNDEF)
109                                ? base->log_to_errlog
110                                 : add->log_to_errlog;
111    dir->log_to_query      = (add->log_to_query == ISAPI_UNDEF)
112                                ? base->log_to_query
113                                 : add->log_to_query;
114    dir->fake_async        = (add->fake_async == ISAPI_UNDEF)
115                                ? base->fake_async
116                                 : add->fake_async;
117
118    return dir;
119}
120
121static const char *isapi_cmd_cachefile(cmd_parms *cmd, void *dummy,
122                                       const char *filename)
123{
124    isapi_loaded *isa;
125    apr_finfo_t tmp;
126    apr_status_t rv;
127    char *fspec;
128
129    /* ### Just an observation ... it would be terribly cool to be
130     * able to use this per-dir, relative to the directory block being
131     * defined.  The hash result remains global, but shorthand of
132     * <Directory "c:/webapps/isapi">
133     *     ISAPICacheFile myapp.dll anotherapp.dll thirdapp.dll
134     * </Directory>
135     * would be very convienent.
136     */
137    fspec = ap_server_root_relative(cmd->pool, filename);
138    if (!fspec) {
139        ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server, APLOGNO(02103)
140                     "invalid module path, skipping %s", filename);
141        return NULL;
142    }
143    if ((rv = apr_stat(&tmp, fspec, APR_FINFO_TYPE,
144                      cmd->temp_pool)) != APR_SUCCESS) {
145        ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server, APLOGNO(02104)
146                     "unable to stat, skipping %s", fspec);
147        return NULL;
148    }
149    if (tmp.filetype != APR_REG) {
150        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(02105)
151                     "not a regular file, skipping %s", fspec);
152        return NULL;
153    }
154
155    /* Load the extention as cached (with null request_rec) */
156    rv = isapi_lookup(cmd->pool, cmd->server, NULL, fspec, &isa);
157    if (rv != APR_SUCCESS) {
158        ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server, APLOGNO(02106)
159                     "unable to cache, skipping %s", fspec);
160        return NULL;
161    }
162
163    return NULL;
164}
165
166static const command_rec isapi_cmds[] = {
167    AP_INIT_TAKE1("ISAPIReadAheadBuffer", ap_set_int_slot,
168        (void *)APR_OFFSETOF(isapi_dir_conf, read_ahead_buflen),
169        OR_FILEINFO, "Maximum client request body to initially pass to the"
170                     " ISAPI handler (default: 49152)"),
171    AP_INIT_FLAG("ISAPILogNotSupported", ap_set_flag_slot,
172        (void *)APR_OFFSETOF(isapi_dir_conf, log_unsupported),
173        OR_FILEINFO, "Log requests not supported by the ISAPI server"
174                     " on or off (default: off)"),
175    AP_INIT_FLAG("ISAPIAppendLogToErrors", ap_set_flag_slot,
176        (void *)APR_OFFSETOF(isapi_dir_conf, log_to_errlog),
177        OR_FILEINFO, "Send all Append Log requests to the error log"
178                     " on or off (default: off)"),
179    AP_INIT_FLAG("ISAPIAppendLogToQuery", ap_set_flag_slot,
180        (void *)APR_OFFSETOF(isapi_dir_conf, log_to_query),
181        OR_FILEINFO, "Append Log requests are concatinated to the query args"
182                     " on or off (default: on)"),
183    AP_INIT_FLAG("ISAPIFakeAsync", ap_set_flag_slot,
184        (void *)APR_OFFSETOF(isapi_dir_conf, fake_async),
185        OR_FILEINFO, "Fake Asynchronous support for isapi callbacks"
186                     " on or off [Experimental] (default: off)"),
187    AP_INIT_ITERATE("ISAPICacheFile", isapi_cmd_cachefile, NULL,
188        RSRC_CONF, "Cache the specified ISAPI extension in-process"),
189    {NULL}
190};
191
192/**********************************************************
193 *
194 *  ISAPI Module Cache handling section
195 *
196 **********************************************************/
197
198/* Our isapi global config values */
199static struct isapi_global_conf {
200    apr_pool_t         *pool;
201    apr_thread_mutex_t *lock;
202    apr_hash_t         *hash;
203} loaded;
204
205/* Our loaded isapi module description structure */
206struct isapi_loaded {
207    const char          *filename;
208    apr_thread_rwlock_t *in_progress;
209    apr_status_t         last_load_rv;
210    apr_time_t           last_load_time;
211    apr_dso_handle_t    *handle;
212    HSE_VERSION_INFO    *isapi_version;
213    apr_uint32_t         report_version;
214    apr_uint32_t         timeout;
215    PFN_GETEXTENSIONVERSION GetExtensionVersion;
216    PFN_HTTPEXTENSIONPROC   HttpExtensionProc;
217    PFN_TERMINATEEXTENSION  TerminateExtension;
218};
219
220static apr_status_t isapi_unload(isapi_loaded *isa, int force)
221{
222    /* All done with the DLL... get rid of it...
223     *
224     * If optionally cached, and we weren't asked to force the unload,
225     * pass HSE_TERM_ADVISORY_UNLOAD, and if it returns 1, unload,
226     * otherwise, leave it alone (it didn't choose to cooperate.)
227     */
228    if (!isa->handle) {
229        return APR_SUCCESS;
230    }
231    if (isa->TerminateExtension) {
232        if (force) {
233            (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD);
234        }
235        else if (!(*isa->TerminateExtension)(HSE_TERM_ADVISORY_UNLOAD)) {
236            return APR_EGENERAL;
237        }
238    }
239    apr_dso_unload(isa->handle);
240    isa->handle = NULL;
241    return APR_SUCCESS;
242}
243
244static apr_status_t cleanup_isapi(void *isa_)
245{
246    isapi_loaded* isa = (isapi_loaded*) isa_;
247
248    /* We must force the module to unload, we are about
249     * to lose the isapi structure's allocation entirely.
250     */
251    return isapi_unload(isa, 1);
252}
253
254static apr_status_t isapi_load(apr_pool_t *p, server_rec *s, isapi_loaded *isa)
255{
256    apr_status_t rv;
257
258    isa->isapi_version = apr_pcalloc(p, sizeof(HSE_VERSION_INFO));
259
260    /* TODO: These aught to become overrideable, so that we
261     * assure a given isapi can be fooled into behaving well.
262     *
263     * The tricky bit, they aren't really a per-dir sort of
264     * config, they will always be constant across every
265     * reference to the .dll no matter what context (vhost,
266     * location, etc) they apply to.
267     */
268    isa->report_version = 0x500; /* Revision 5.0 */
269    isa->timeout = 300 * 1000000; /* microsecs, not used */
270
271    rv = apr_dso_load(&isa->handle, isa->filename, p);
272    if (rv)
273    {
274        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02107)
275                     "failed to load %s", isa->filename);
276        isa->handle = NULL;
277        return rv;
278    }
279
280    rv = apr_dso_sym((void**)&isa->GetExtensionVersion, isa->handle,
281                     "GetExtensionVersion");
282    if (rv)
283    {
284        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02108)
285                     "missing GetExtensionVersion() in %s",
286                     isa->filename);
287        apr_dso_unload(isa->handle);
288        isa->handle = NULL;
289        return rv;
290    }
291
292    rv = apr_dso_sym((void**)&isa->HttpExtensionProc, isa->handle,
293                     "HttpExtensionProc");
294    if (rv)
295    {
296        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02109)
297                     "missing HttpExtensionProc() in %s",
298                     isa->filename);
299        apr_dso_unload(isa->handle);
300        isa->handle = NULL;
301        return rv;
302    }
303
304    /* TerminateExtension() is an optional interface */
305    rv = apr_dso_sym((void**)&isa->TerminateExtension, isa->handle,
306                     "TerminateExtension");
307    apr_set_os_error(0);
308
309    /* Run GetExtensionVersion() */
310    if (!(isa->GetExtensionVersion)(isa->isapi_version)) {
311        apr_status_t rv = apr_get_os_error();
312        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02110)
313                     "failed call to GetExtensionVersion() in %s",
314                     isa->filename);
315        apr_dso_unload(isa->handle);
316        isa->handle = NULL;
317        return rv;
318    }
319
320    apr_pool_cleanup_register(p, isa, cleanup_isapi,
321                              apr_pool_cleanup_null);
322
323    return APR_SUCCESS;
324}
325
326apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r,
327                          const char *fpath, isapi_loaded** isa)
328{
329    apr_status_t rv;
330    const char *key;
331
332    if ((rv = apr_thread_mutex_lock(loaded.lock)) != APR_SUCCESS) {
333        return rv;
334    }
335
336    *isa = apr_hash_get(loaded.hash, fpath, APR_HASH_KEY_STRING);
337
338    if (*isa) {
339
340        /* If we find this lock exists, use a set-aside copy of gainlock
341         * to avoid race conditions on NULLing the in_progress variable
342         * when the load has completed.  Release the global isapi hash
343         * lock so other requests can proceed, then rdlock for completion
344         * of loading our desired dll or wrlock if we would like to retry
345         * loading the dll (because last_load_rv failed and retry is up.)
346         */
347        apr_thread_rwlock_t *gainlock = (*isa)->in_progress;
348
349        /* gainlock is NULLed after the module loads successfully.
350         * This free-threaded module can be used without any locking.
351         */
352        if (!gainlock) {
353            rv = (*isa)->last_load_rv;
354            apr_thread_mutex_unlock(loaded.lock);
355            return rv;
356        }
357
358
359        if ((*isa)->last_load_rv == APR_SUCCESS) {
360            apr_thread_mutex_unlock(loaded.lock);
361            if ((rv = apr_thread_rwlock_rdlock(gainlock))
362                    != APR_SUCCESS) {
363                return rv;
364            }
365            rv = (*isa)->last_load_rv;
366            apr_thread_rwlock_unlock(gainlock);
367            return rv;
368        }
369
370        if (apr_time_now() > (*isa)->last_load_time + ISAPI_RETRY) {
371
372            /* Remember last_load_time before releasing the global
373             * hash lock to avoid colliding with another thread
374             * that hit this exception at the same time as our
375             * retry attempt, since we unlock the global mutex
376             * before attempting a write lock for this module.
377             */
378            apr_time_t check_time = (*isa)->last_load_time;
379            apr_thread_mutex_unlock(loaded.lock);
380
381            if ((rv = apr_thread_rwlock_wrlock(gainlock))
382                    != APR_SUCCESS) {
383                return rv;
384            }
385
386            /* If last_load_time is unchanged, we still own this
387             * retry, otherwise presume another thread provided
388             * our retry (for good or ill).  Relock the global
389             * hash for updating last_load_ vars, so their update
390             * is always atomic to the global lock.
391             */
392            if (check_time == (*isa)->last_load_time) {
393
394                rv = isapi_load(loaded.pool, s, *isa);
395
396                apr_thread_mutex_lock(loaded.lock);
397                (*isa)->last_load_rv = rv;
398                (*isa)->last_load_time = apr_time_now();
399                apr_thread_mutex_unlock(loaded.lock);
400            }
401            else {
402                rv = (*isa)->last_load_rv;
403            }
404            apr_thread_rwlock_unlock(gainlock);
405
406            return rv;
407        }
408
409        /* We haven't hit timeup on retry, let's grab the last_rv
410         * within the hash mutex before unlocking.
411         */
412        rv = (*isa)->last_load_rv;
413        apr_thread_mutex_unlock(loaded.lock);
414
415        return rv;
416    }
417
418    /* If the module was not found, it's time to create a hash key entry
419     * before releasing the hash lock to avoid multiple threads from
420     * loading the same module.
421     */
422    key = apr_pstrdup(loaded.pool, fpath);
423    *isa = apr_pcalloc(loaded.pool, sizeof(isapi_loaded));
424    (*isa)->filename = key;
425    if (r) {
426        /* A mutex that exists only long enough to attempt to
427         * load this isapi dll, the release this module to all
428         * other takers that came along during the one-time
429         * load process.  Short lifetime for this lock would
430         * be great, however, using r->pool is nasty if those
431         * blocked on the lock haven't all unlocked before we
432         * attempt to destroy.  A nastier race condition than
433         * I want to deal with at this moment...
434         */
435        apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool);
436        apr_thread_rwlock_wrlock((*isa)->in_progress);
437    }
438
439    apr_hash_set(loaded.hash, key, APR_HASH_KEY_STRING, *isa);
440
441    /* Now attempt to load the isapi on our own time,
442     * allow other isapi processing to resume.
443     */
444    apr_thread_mutex_unlock(loaded.lock);
445
446    rv = isapi_load(loaded.pool, s, *isa);
447    (*isa)->last_load_time = apr_time_now();
448    (*isa)->last_load_rv = rv;
449
450    if (r && (rv == APR_SUCCESS)) {
451        /* Let others who are blocked on this particular
452         * module resume their requests, for better or worse.
453         */
454        apr_thread_rwlock_t *unlock = (*isa)->in_progress;
455        (*isa)->in_progress = NULL;
456        apr_thread_rwlock_unlock(unlock);
457    }
458    else if (!r && (rv != APR_SUCCESS)) {
459        /* We must leave a rwlock around for requests to retry
460         * loading this dll after timeup... since we were in
461         * the setup code we had avoided creating this lock.
462         */
463        apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool);
464    }
465
466    return (*isa)->last_load_rv;
467}
468
469/**********************************************************
470 *
471 *  ISAPI Module request callbacks section
472 *
473 **********************************************************/
474
475/* Our "Connection ID" structure */
476struct isapi_cid {
477    EXTENSION_CONTROL_BLOCK *ecb;
478    isapi_dir_conf           dconf;
479    isapi_loaded            *isa;
480    request_rec             *r;
481    int                      headers_set;
482    int                      response_sent;
483    PFN_HSE_IO_COMPLETION    completion;
484    void                    *completion_arg;
485    apr_thread_mutex_t      *completed;
486};
487
488static int APR_THREAD_FUNC regfnGetServerVariable(isapi_cid    *cid,
489                                                  char         *variable_name,
490                                                  void         *buf_ptr,
491                                                  apr_uint32_t *buf_size)
492{
493    request_rec *r = cid->r;
494    const char *result;
495    char *buf_data = (char*)buf_ptr;
496    apr_uint32_t len;
497
498    if (!strcmp(variable_name, "ALL_HTTP"))
499    {
500        /* crlf delimited, colon split, comma separated and
501         * null terminated list of HTTP_ vars
502         */
503        const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
504        const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
505        int i;
506
507        for (len = 0, i = 0; i < arr->nelts; i++) {
508            if (!strncmp(elts[i].key, "HTTP_", 5)) {
509                len += strlen(elts[i].key) + strlen(elts[i].val) + 3;
510            }
511        }
512
513        if (*buf_size < len + 1) {
514            *buf_size = len + 1;
515            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
516            return 0;
517        }
518
519        for (i = 0; i < arr->nelts; i++) {
520            if (!strncmp(elts[i].key, "HTTP_", 5)) {
521                strcpy(buf_data, elts[i].key);
522                buf_data += strlen(elts[i].key);
523                *(buf_data++) = ':';
524                strcpy(buf_data, elts[i].val);
525                buf_data += strlen(elts[i].val);
526                *(buf_data++) = '\r';
527                *(buf_data++) = '\n';
528            }
529        }
530
531        *(buf_data++) = '\0';
532        *buf_size = len + 1;
533        return 1;
534    }
535
536    if (!strcmp(variable_name, "ALL_RAW"))
537    {
538        /* crlf delimited, colon split, comma separated and
539         * null terminated list of the raw request header
540         */
541        const apr_array_header_t *arr = apr_table_elts(r->headers_in);
542        const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
543        int i;
544
545        for (len = 0, i = 0; i < arr->nelts; i++) {
546            len += strlen(elts[i].key) + strlen(elts[i].val) + 4;
547        }
548
549        if (*buf_size < len + 1) {
550            *buf_size = len + 1;
551            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
552            return 0;
553        }
554
555        for (i = 0; i < arr->nelts; i++) {
556            strcpy(buf_data, elts[i].key);
557            buf_data += strlen(elts[i].key);
558            *(buf_data++) = ':';
559            *(buf_data++) = ' ';
560            strcpy(buf_data, elts[i].val);
561            buf_data += strlen(elts[i].val);
562            *(buf_data++) = '\r';
563            *(buf_data++) = '\n';
564        }
565        *(buf_data++) = '\0';
566        *buf_size = len + 1;
567        return 1;
568    }
569
570    /* Not a special case */
571    result = apr_table_get(r->subprocess_env, variable_name);
572
573    if (result) {
574        len = strlen(result);
575        if (*buf_size < len + 1) {
576            *buf_size = len + 1;
577            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
578            return 0;
579        }
580        strcpy(buf_data, result);
581        *buf_size = len + 1;
582        return 1;
583    }
584
585    /* Not Found */
586    apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_INDEX));
587    return 0;
588}
589
590static int APR_THREAD_FUNC regfnReadClient(isapi_cid    *cid,
591                                           void         *buf_data,
592                                           apr_uint32_t *buf_size)
593{
594    request_rec *r = cid->r;
595    apr_uint32_t read = 0;
596    int res = 0;
597
598    if (r->remaining < *buf_size) {
599        *buf_size = (apr_size_t)r->remaining;
600    }
601
602    while (read < *buf_size &&
603           ((res = ap_get_client_block(r, (char*)buf_data + read,
604                                       *buf_size - read)) > 0)) {
605        read += res;
606    }
607
608    *buf_size = read;
609    if (res < 0) {
610        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_READ_FAULT));
611    }
612    return (res >= 0);
613}
614
615/* Common code invoked for both HSE_REQ_SEND_RESPONSE_HEADER and
616 * the newer HSE_REQ_SEND_RESPONSE_HEADER_EX ServerSupportFunction(s)
617 * as well as other functions that write responses and presume that
618 * the support functions above are optional.
619 *
620 * Other callers trying to split headers and body bytes should pass
621 * head/headlen alone (leaving stat/statlen NULL/0), so that they
622 * get a proper count of bytes consumed.  The argument passed to stat
623 * isn't counted as the head bytes are.
624 */
625static apr_ssize_t send_response_header(isapi_cid *cid,
626                                        const char *stat,
627                                        const char *head,
628                                        apr_size_t statlen,
629                                        apr_size_t headlen)
630{
631    int head_present = 1;
632    int termarg;
633    int res;
634    int old_status;
635    const char *termch;
636    apr_size_t ate = 0;
637
638    if (!head || headlen == 0 || !*head) {
639        head = stat;
640        stat = NULL;
641        headlen = statlen;
642        statlen = 0;
643        head_present = 0; /* Don't eat the header */
644    }
645
646    if (!stat || statlen == 0 || !*stat) {
647        if (head && headlen && *head && ((stat = memchr(head, '\r', headlen))
648                                      || (stat = memchr(head, '\n', headlen))
649                                      || (stat = memchr(head, '\0', headlen))
650                                      || (stat = head + headlen))) {
651            statlen = stat - head;
652            if (memchr(head, ':', statlen)) {
653                stat = "Status: 200 OK";
654                statlen = strlen(stat);
655            }
656            else {
657                const char *flip = head;
658                head = stat;
659                stat = flip;
660                headlen -= statlen;
661                ate += statlen;
662                if (*head == '\r' && headlen)
663                    ++head, --headlen, ++ate;
664                if (*head == '\n' && headlen)
665                    ++head, --headlen, ++ate;
666            }
667        }
668    }
669
670    if (stat && (statlen > 0) && *stat) {
671        char *newstat;
672        if (!apr_isdigit(*stat)) {
673            const char *stattok = stat;
674            int toklen = statlen;
675            while (toklen && *stattok && !apr_isspace(*stattok)) {
676                ++stattok; --toklen;
677            }
678            while (toklen && apr_isspace(*stattok)) {
679                ++stattok; --toklen;
680            }
681            /* Now decide if we follow the xxx message
682             * or the http/x.x xxx message format
683             */
684            if (toklen && apr_isdigit(*stattok)) {
685                statlen = toklen;
686                stat = stattok;
687            }
688        }
689        newstat = apr_palloc(cid->r->pool, statlen + 9);
690        strcpy(newstat, "Status: ");
691        apr_cpystrn(newstat + 8, stat, statlen + 1);
692        stat = newstat;
693        statlen += 8;
694    }
695
696    if (!head || headlen == 0 || !*head) {
697        head = "\r\n";
698        headlen = 2;
699    }
700    else
701    {
702        if (head[headlen - 1] && head[headlen]) {
703            /* Whoops... not NULL terminated */
704            head = apr_pstrndup(cid->r->pool, head, headlen);
705        }
706    }
707
708    /* Seems IIS does not enforce the requirement for \r\n termination
709     * on HSE_REQ_SEND_RESPONSE_HEADER, but we won't panic...
710     * ap_scan_script_header_err_strs handles this aspect for us.
711     *
712     * Parse them out, or die trying
713     */
714    old_status = cid->r->status;
715
716    if (stat) {
717        res = ap_scan_script_header_err_strs_ex(cid->r, NULL,
718                APLOG_MODULE_INDEX, &termch, &termarg, stat, head, NULL);
719    }
720    else {
721        res = ap_scan_script_header_err_strs_ex(cid->r, NULL,
722                APLOG_MODULE_INDEX, &termch, &termarg, head, NULL);
723    }
724
725    /* Set our status. */
726    if (res) {
727        /* This is an immediate error result from the parser
728         */
729        cid->r->status = res;
730        cid->r->status_line = ap_get_status_line(cid->r->status);
731        cid->ecb->dwHttpStatusCode = cid->r->status;
732    }
733    else if (cid->r->status) {
734        /* We have a status in r->status, so let's just use it.
735         * This is likely to be the Status: parsed above, and
736         * may also be a delayed error result from the parser.
737         * If it was filled in, status_line should also have
738         * been filled in.
739         */
740        cid->ecb->dwHttpStatusCode = cid->r->status;
741    }
742    else if (cid->ecb->dwHttpStatusCode
743              && cid->ecb->dwHttpStatusCode != HTTP_OK) {
744        /* Now we fall back on dwHttpStatusCode if it appears
745         * ap_scan_script_header fell back on the default code.
746         * Any other results set dwHttpStatusCode to the decoded
747         * status value.
748         */
749        cid->r->status = cid->ecb->dwHttpStatusCode;
750        cid->r->status_line = ap_get_status_line(cid->r->status);
751    }
752    else if (old_status) {
753        /* Well... either there is no dwHttpStatusCode or it's HTTP_OK.
754         * In any case, we don't have a good status to return yet...
755         * Perhaps the one we came in with will be better. Let's use it,
756         * if we were given one (note this is a pendantic case, it would
757         * normally be covered above unless the scan script code unset
758         * the r->status). Should there be a check here as to whether
759         * we are setting a valid response code?
760         */
761        cid->r->status = old_status;
762        cid->r->status_line = ap_get_status_line(cid->r->status);
763        cid->ecb->dwHttpStatusCode = cid->r->status;
764    }
765    else {
766        /* None of dwHttpStatusCode, the parser's r->status nor the
767         * old value of r->status were helpful, and nothing was decoded
768         * from Status: string passed to us.  Let's just say HTTP_OK
769         * and get the data out, this was the isapi dev's oversight.
770         */
771        cid->r->status = HTTP_OK;
772        cid->r->status_line = ap_get_status_line(cid->r->status);
773        cid->ecb->dwHttpStatusCode = cid->r->status;
774        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, cid->r, APLOGNO(02111)
775                "Could not determine HTTP response code; using %d",
776                cid->r->status);
777    }
778
779    if (cid->r->status == HTTP_INTERNAL_SERVER_ERROR) {
780        return -1;
781    }
782
783    /* If only Status was passed, we consumed nothing
784     */
785    if (!head_present)
786        return 0;
787
788    cid->headers_set = 1;
789
790    /* If all went well, tell the caller we consumed the headers complete
791     */
792    if (!termch)
793        return(ate + headlen);
794
795    /* Any data left must be sent directly by the caller, all we
796     * give back is the size of the headers we consumed (which only
797     * happens if the parser got to the head arg, which varies based
798     * on whether we passed stat+head to scan, or only head.
799     */
800    if (termch && (termarg == (stat ? 1 : 0))
801               && head_present && head + headlen > termch) {
802        return ate + termch - head;
803    }
804    return ate;
805}
806
807static int APR_THREAD_FUNC regfnWriteClient(isapi_cid    *cid,
808                                            void         *buf_ptr,
809                                            apr_uint32_t *size_arg,
810                                            apr_uint32_t  flags)
811{
812    request_rec *r = cid->r;
813    conn_rec *c = r->connection;
814    apr_uint32_t buf_size = *size_arg;
815    char *buf_data = (char*)buf_ptr;
816    apr_bucket_brigade *bb;
817    apr_bucket *b;
818    apr_status_t rv = APR_SUCCESS;
819
820    if (!cid->headers_set) {
821        /* It appears that the foxisapi module and other clients
822         * presume that WriteClient("headers\n\nbody") will work.
823         * Parse them out, or die trying.
824         */
825        apr_ssize_t ate;
826        ate = send_response_header(cid, NULL, buf_data, 0, buf_size);
827        if (ate < 0) {
828            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
829            return 0;
830        }
831
832        buf_data += ate;
833        buf_size -= ate;
834    }
835
836    if (buf_size) {
837        bb = apr_brigade_create(r->pool, c->bucket_alloc);
838        b = apr_bucket_transient_create(buf_data, buf_size, c->bucket_alloc);
839        APR_BRIGADE_INSERT_TAIL(bb, b);
840        b = apr_bucket_flush_create(c->bucket_alloc);
841        APR_BRIGADE_INSERT_TAIL(bb, b);
842        rv = ap_pass_brigade(r->output_filters, bb);
843        cid->response_sent = 1;
844        if (rv != APR_SUCCESS)
845            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
846                          "WriteClient ap_pass_brigade failed: %s",
847                          r->filename);
848    }
849
850    if ((flags & HSE_IO_ASYNC) && cid->completion) {
851        if (rv == APR_SUCCESS) {
852            cid->completion(cid->ecb, cid->completion_arg,
853                            *size_arg, ERROR_SUCCESS);
854        }
855        else {
856            cid->completion(cid->ecb, cid->completion_arg,
857                            *size_arg, ERROR_WRITE_FAULT);
858        }
859    }
860    return (rv == APR_SUCCESS);
861}
862
863static int APR_THREAD_FUNC regfnServerSupportFunction(isapi_cid    *cid,
864                                                      apr_uint32_t  HSE_code,
865                                                      void         *buf_ptr,
866                                                      apr_uint32_t *buf_size,
867                                                      apr_uint32_t *data_type)
868{
869    request_rec *r = cid->r;
870    conn_rec *c = r->connection;
871    char *buf_data = (char*)buf_ptr;
872    request_rec *subreq;
873    apr_status_t rv;
874
875    switch (HSE_code) {
876    case HSE_REQ_SEND_URL_REDIRECT_RESP:
877        /* Set the status to be returned when the HttpExtensionProc()
878         * is done.
879         * WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP
880         *          and HSE_REQ_SEND_URL as equivalent per the Jan 2000 SDK.
881         *          They most definitely are not, even in their own samples.
882         */
883        apr_table_set (r->headers_out, "Location", buf_data);
884        cid->r->status = cid->ecb->dwHttpStatusCode = HTTP_MOVED_TEMPORARILY;
885        cid->r->status_line = ap_get_status_line(cid->r->status);
886        cid->headers_set = 1;
887        return 1;
888
889    case HSE_REQ_SEND_URL:
890        /* Soak up remaining input */
891        if (r->remaining > 0) {
892            char argsbuffer[HUGE_STRING_LEN];
893            while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN));
894        }
895
896        /* Reset the method to GET */
897        r->method = "GET";
898        r->method_number = M_GET;
899
900        /* Don't let anyone think there's still data */
901        apr_table_unset(r->headers_in, "Content-Length");
902
903        /* AV fault per PR3598 - redirected path is lost! */
904        buf_data = apr_pstrdup(r->pool, (char*)buf_data);
905        ap_internal_redirect(buf_data, r);
906        return 1;
907
908    case HSE_REQ_SEND_RESPONSE_HEADER:
909    {
910        /* Parse them out, or die trying */
911        apr_size_t statlen = 0, headlen = 0;
912        apr_ssize_t ate;
913        if (buf_data)
914            statlen = strlen((char*) buf_data);
915        if (data_type)
916            headlen = strlen((char*) data_type);
917        ate = send_response_header(cid, (char*) buf_data,
918                                   (char*) data_type,
919                                   statlen, headlen);
920        if (ate < 0) {
921            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
922            return 0;
923        }
924        else if ((apr_size_t)ate < headlen) {
925            apr_bucket_brigade *bb;
926            apr_bucket *b;
927            bb = apr_brigade_create(cid->r->pool, c->bucket_alloc);
928            b = apr_bucket_transient_create((char*) data_type + ate,
929                                           headlen - ate, c->bucket_alloc);
930            APR_BRIGADE_INSERT_TAIL(bb, b);
931            b = apr_bucket_flush_create(c->bucket_alloc);
932            APR_BRIGADE_INSERT_TAIL(bb, b);
933            rv = ap_pass_brigade(cid->r->output_filters, bb);
934            cid->response_sent = 1;
935            if (rv != APR_SUCCESS)
936                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
937                              "ServerSupport function "
938                              "HSE_REQ_SEND_RESPONSE_HEADER "
939                              "ap_pass_brigade failed: %s", r->filename);
940            return (rv == APR_SUCCESS);
941        }
942        /* Deliberately hold off sending 'just the headers' to begin to
943         * accumulate the body and speed up the overall response, or at
944         * least wait for the end the session.
945         */
946        return 1;
947    }
948
949    case HSE_REQ_DONE_WITH_SESSION:
950        /* Signal to resume the thread completing this request,
951         * leave it to the pool cleanup to dispose of our mutex.
952         */
953        if (cid->completed) {
954            (void)apr_thread_mutex_unlock(cid->completed);
955            return 1;
956        }
957        else if (cid->dconf.log_unsupported) {
958            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
959                          "ServerSupportFunction "
960                          "HSE_REQ_DONE_WITH_SESSION is not supported: %s",
961                          r->filename);
962        }
963        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
964        return 0;
965
966    case HSE_REQ_MAP_URL_TO_PATH:
967    {
968        /* Map a URL to a filename */
969        char *file = (char *)buf_data;
970        apr_uint32_t len;
971        subreq = ap_sub_req_lookup_uri(
972                     apr_pstrndup(cid->r->pool, file, *buf_size), r, NULL);
973
974        if (!subreq->filename) {
975            ap_destroy_sub_req(subreq);
976            return 0;
977        }
978
979        len = (apr_uint32_t)strlen(r->filename);
980
981        if ((subreq->finfo.filetype == APR_DIR)
982              && (!subreq->path_info)
983              && (file[len - 1] != '/'))
984            file = apr_pstrcat(cid->r->pool, subreq->filename, "/", NULL);
985        else
986            file = apr_pstrcat(cid->r->pool, subreq->filename,
987                                              subreq->path_info, NULL);
988
989        ap_destroy_sub_req(subreq);
990
991#ifdef WIN32
992        /* We need to make this a real Windows path name */
993        apr_filepath_merge(&file, "", file, APR_FILEPATH_NATIVE, r->pool);
994#endif
995
996        *buf_size = apr_cpystrn(buf_data, file, *buf_size) - buf_data;
997
998        return 1;
999    }
1000
1001    case HSE_REQ_GET_SSPI_INFO:
1002        if (cid->dconf.log_unsupported)
1003            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1004                           "ServerSupportFunction HSE_REQ_GET_SSPI_INFO "
1005                           "is not supported: %s", r->filename);
1006        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1007        return 0;
1008
1009    case HSE_APPEND_LOG_PARAMETER:
1010        /* Log buf_data, of buf_size bytes, in the URI Query (cs-uri-query) field
1011         */
1012        apr_table_set(r->notes, "isapi-parameter", (char*) buf_data);
1013        if (cid->dconf.log_to_query) {
1014            if (r->args)
1015                r->args = apr_pstrcat(r->pool, r->args, (char*) buf_data, NULL);
1016            else
1017                r->args = apr_pstrdup(r->pool, (char*) buf_data);
1018        }
1019        if (cid->dconf.log_to_errlog)
1020            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
1021                          "%s: %s", cid->r->filename,
1022                          (char*) buf_data);
1023        return 1;
1024
1025    case HSE_REQ_IO_COMPLETION:
1026        /* Emulates a completion port...  Record callback address and
1027         * user defined arg, we will call this after any async request
1028         * (e.g. transmitfile) as if the request executed async.
1029         * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call
1030         * to HSE_REQ_IO_COMPLETION, and buf_data may be set to NULL.
1031         */
1032        if (cid->dconf.fake_async) {
1033            cid->completion = (PFN_HSE_IO_COMPLETION) buf_data;
1034            cid->completion_arg = (void *) data_type;
1035            return 1;
1036        }
1037        if (cid->dconf.log_unsupported)
1038            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1039                      "ServerSupportFunction HSE_REQ_IO_COMPLETION "
1040                      "is not supported: %s", r->filename);
1041        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1042        return 0;
1043
1044    case HSE_REQ_TRANSMIT_FILE:
1045    {
1046        /* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND)
1047         */
1048        HSE_TF_INFO *tf = (HSE_TF_INFO*)buf_data;
1049        apr_uint32_t sent = 0;
1050        apr_ssize_t ate = 0;
1051        apr_bucket_brigade *bb;
1052        apr_bucket *b;
1053        apr_file_t *fd;
1054        apr_off_t fsize;
1055
1056        if (!cid->dconf.fake_async && (tf->dwFlags & HSE_IO_ASYNC)) {
1057            if (cid->dconf.log_unsupported)
1058                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1059                         "ServerSupportFunction HSE_REQ_TRANSMIT_FILE "
1060                         "as HSE_IO_ASYNC is not supported: %s", r->filename);
1061            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1062            return 0;
1063        }
1064
1065        /* Presume the handle was opened with the CORRECT semantics
1066         * for TransmitFile
1067         */
1068        if ((rv = apr_os_file_put(&fd, &tf->hFile,
1069                                  APR_READ | APR_XTHREAD, r->pool))
1070                != APR_SUCCESS) {
1071            return 0;
1072        }
1073        if (tf->BytesToWrite) {
1074            fsize = tf->BytesToWrite;
1075        }
1076        else {
1077            apr_finfo_t fi;
1078            if (apr_file_info_get(&fi, APR_FINFO_SIZE, fd) != APR_SUCCESS) {
1079                apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1080                return 0;
1081            }
1082            fsize = fi.size - tf->Offset;
1083        }
1084
1085        /* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */
1086        bb = apr_brigade_create(r->pool, c->bucket_alloc);
1087
1088        /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the
1089         * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any
1090         * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag,
1091         * you must have done so.  They document that the pHead headers
1092         * option is valid only for HSE_IO_SEND_HEADERS - we are a bit
1093         * more flexible and assume with the flag, pHead are the
1094         * response headers, and without, pHead simply contains text
1095         * (handled after this case).
1096         */
1097        if ((tf->dwFlags & HSE_IO_SEND_HEADERS) && tf->pszStatusCode) {
1098            ate = send_response_header(cid, tf->pszStatusCode,
1099                                            (char*)tf->pHead,
1100                                            strlen(tf->pszStatusCode),
1101                                            tf->HeadLength);
1102        }
1103        else if (!cid->headers_set && tf->pHead && tf->HeadLength
1104                                   && *(char*)tf->pHead) {
1105            ate = send_response_header(cid, NULL, (char*)tf->pHead,
1106                                            0, tf->HeadLength);
1107            if (ate < 0)
1108            {
1109                apr_brigade_destroy(bb);
1110                apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1111                return 0;
1112            }
1113        }
1114
1115        if (tf->pHead && (apr_size_t)ate < tf->HeadLength) {
1116            b = apr_bucket_transient_create((char*)tf->pHead + ate,
1117                                            tf->HeadLength - ate,
1118                                            c->bucket_alloc);
1119            APR_BRIGADE_INSERT_TAIL(bb, b);
1120            sent = tf->HeadLength;
1121        }
1122
1123        sent += (apr_uint32_t)fsize;
1124        apr_brigade_insert_file(bb, fd, tf->Offset, fsize, r->pool);
1125
1126        if (tf->pTail && tf->TailLength) {
1127            sent += tf->TailLength;
1128            b = apr_bucket_transient_create((char*)tf->pTail,
1129                                            tf->TailLength, c->bucket_alloc);
1130            APR_BRIGADE_INSERT_TAIL(bb, b);
1131        }
1132
1133        b = apr_bucket_flush_create(c->bucket_alloc);
1134        APR_BRIGADE_INSERT_TAIL(bb, b);
1135        rv = ap_pass_brigade(r->output_filters, bb);
1136        cid->response_sent = 1;
1137        if (rv != APR_SUCCESS)
1138            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
1139                          "ServerSupport function "
1140                          "HSE_REQ_TRANSMIT_FILE "
1141                          "ap_pass_brigade failed: %s", r->filename);
1142
1143        /* Use tf->pfnHseIO + tf->pContext, or if NULL, then use cid->fnIOComplete
1144         * pass pContect to the HseIO callback.
1145         */
1146        if (tf->dwFlags & HSE_IO_ASYNC) {
1147            if (tf->pfnHseIO) {
1148                if (rv == APR_SUCCESS) {
1149                    tf->pfnHseIO(cid->ecb, tf->pContext,
1150                                 ERROR_SUCCESS, sent);
1151                }
1152                else {
1153                    tf->pfnHseIO(cid->ecb, tf->pContext,
1154                                 ERROR_WRITE_FAULT, sent);
1155                }
1156            }
1157            else if (cid->completion) {
1158                if (rv == APR_SUCCESS) {
1159                    cid->completion(cid->ecb, cid->completion_arg,
1160                                    sent, ERROR_SUCCESS);
1161                }
1162                else {
1163                    cid->completion(cid->ecb, cid->completion_arg,
1164                                    sent, ERROR_WRITE_FAULT);
1165                }
1166            }
1167        }
1168        return (rv == APR_SUCCESS);
1169    }
1170
1171    case HSE_REQ_REFRESH_ISAPI_ACL:
1172        if (cid->dconf.log_unsupported)
1173            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1174                          "ServerSupportFunction "
1175                          "HSE_REQ_REFRESH_ISAPI_ACL "
1176                          "is not supported: %s", r->filename);
1177        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1178        return 0;
1179
1180    case HSE_REQ_IS_KEEP_CONN:
1181        *((int *)buf_data) = (r->connection->keepalive == AP_CONN_KEEPALIVE);
1182        return 1;
1183
1184    case HSE_REQ_ASYNC_READ_CLIENT:
1185    {
1186        apr_uint32_t read = 0;
1187        int res = 0;
1188        if (!cid->dconf.fake_async) {
1189            if (cid->dconf.log_unsupported)
1190                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1191                            "asynchronous I/O not supported: %s",
1192                            r->filename);
1193            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1194            return 0;
1195        }
1196
1197        if (r->remaining < *buf_size) {
1198            *buf_size = (apr_size_t)r->remaining;
1199        }
1200
1201        while (read < *buf_size &&
1202            ((res = ap_get_client_block(r, (char*)buf_data + read,
1203                                        *buf_size - read)) > 0)) {
1204            read += res;
1205        }
1206
1207        if ((*data_type & HSE_IO_ASYNC) && cid->completion) {
1208            /* XXX: Many authors issue their next HSE_REQ_ASYNC_READ_CLIENT
1209             * within the completion logic.  An example is MS's own PSDK
1210             * sample web/iis/extensions/io/ASyncRead.  This potentially
1211             * leads to stack exhaustion.  To refactor, the notification
1212             * logic needs to move to isapi_handler() - differentiating
1213             * the cid->completed event with a new flag to indicate
1214             * an async-notice versus the async request completed.
1215             */
1216            if (res >= 0) {
1217                cid->completion(cid->ecb, cid->completion_arg,
1218                                read, ERROR_SUCCESS);
1219            }
1220            else {
1221                cid->completion(cid->ecb, cid->completion_arg,
1222                                read, ERROR_READ_FAULT);
1223            }
1224        }
1225        return (res >= 0);
1226    }
1227
1228    case HSE_REQ_GET_IMPERSONATION_TOKEN:  /* Added in ISAPI 4.0 */
1229        if (cid->dconf.log_unsupported)
1230            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1231                          "ServerSupportFunction "
1232                          "HSE_REQ_GET_IMPERSONATION_TOKEN "
1233                          "is not supported: %s", r->filename);
1234        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1235        return 0;
1236
1237    case HSE_REQ_MAP_URL_TO_PATH_EX:
1238    {
1239        /* Map a URL to a filename */
1240        HSE_URL_MAPEX_INFO *info = (HSE_URL_MAPEX_INFO*)data_type;
1241        char* test_uri = apr_pstrndup(r->pool, (char *)buf_data, *buf_size);
1242
1243        subreq = ap_sub_req_lookup_uri(test_uri, r, NULL);
1244        info->cchMatchingURL = strlen(test_uri);
1245        info->cchMatchingPath = apr_cpystrn(info->lpszPath, subreq->filename,
1246                                      sizeof(info->lpszPath)) - info->lpszPath;
1247
1248        /* Mapping started with assuming both strings matched.
1249         * Now roll on the path_info as a mismatch and handle
1250         * terminating slashes for directory matches.
1251         */
1252        if (subreq->path_info && *subreq->path_info) {
1253            apr_cpystrn(info->lpszPath + info->cchMatchingPath,
1254                        subreq->path_info,
1255                        sizeof(info->lpszPath) - info->cchMatchingPath);
1256            info->cchMatchingURL -= strlen(subreq->path_info);
1257            if (subreq->finfo.filetype == APR_DIR
1258                 && info->cchMatchingPath < sizeof(info->lpszPath) - 1) {
1259                /* roll forward over path_info's first slash */
1260                ++info->cchMatchingPath;
1261                ++info->cchMatchingURL;
1262            }
1263        }
1264        else if (subreq->finfo.filetype == APR_DIR
1265                 && info->cchMatchingPath < sizeof(info->lpszPath) - 1) {
1266            /* Add a trailing slash for directory */
1267            info->lpszPath[info->cchMatchingPath++] = '/';
1268            info->lpszPath[info->cchMatchingPath] = '\0';
1269        }
1270
1271        /* If the matched isn't a file, roll match back to the prior slash */
1272        if (subreq->finfo.filetype == APR_NOFILE) {
1273            while (info->cchMatchingPath && info->cchMatchingURL) {
1274                if (info->lpszPath[info->cchMatchingPath - 1] == '/')
1275                    break;
1276                --info->cchMatchingPath;
1277                --info->cchMatchingURL;
1278            }
1279        }
1280
1281        /* Paths returned with back slashes */
1282        for (test_uri = info->lpszPath; *test_uri; ++test_uri)
1283            if (*test_uri == '/')
1284                *test_uri = '\\';
1285
1286        /* is a combination of:
1287         * HSE_URL_FLAGS_READ         0x001 Allow read
1288         * HSE_URL_FLAGS_WRITE        0x002 Allow write
1289         * HSE_URL_FLAGS_EXECUTE      0x004 Allow execute
1290         * HSE_URL_FLAGS_SSL          0x008 Require SSL
1291         * HSE_URL_FLAGS_DONT_CACHE   0x010 Don't cache (VRoot only)
1292         * HSE_URL_FLAGS_NEGO_CERT    0x020 Allow client SSL cert
1293         * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert
1294         * HSE_URL_FLAGS_MAP_CERT     0x080 Map client SSL cert to account
1295         * HSE_URL_FLAGS_SSL128       0x100 Require 128-bit SSL cert
1296         * HSE_URL_FLAGS_SCRIPT       0x200 Allow script execution
1297         *
1298         * XxX: As everywhere, EXEC flags could use some work...
1299         *      and this could go further with more flags, as desired.
1300         */
1301        info->dwFlags = (subreq->finfo.protection & APR_UREAD    ? 0x001 : 0)
1302                      | (subreq->finfo.protection & APR_UWRITE   ? 0x002 : 0)
1303                      | (subreq->finfo.protection & APR_UEXECUTE ? 0x204 : 0);
1304        return 1;
1305    }
1306
1307    case HSE_REQ_ABORTIVE_CLOSE:
1308        if (cid->dconf.log_unsupported)
1309            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1310                          "ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE"
1311                          " is not supported: %s", r->filename);
1312        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1313        return 0;
1314
1315    case HSE_REQ_GET_CERT_INFO_EX:  /* Added in ISAPI 4.0 */
1316        if (cid->dconf.log_unsupported)
1317            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1318                          "ServerSupportFunction "
1319                          "HSE_REQ_GET_CERT_INFO_EX "
1320                          "is not supported: %s", r->filename);
1321        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1322        return 0;
1323
1324    case HSE_REQ_SEND_RESPONSE_HEADER_EX:  /* Added in ISAPI 4.0 */
1325    {
1326        HSE_SEND_HEADER_EX_INFO *shi = (HSE_SEND_HEADER_EX_INFO*)buf_data;
1327
1328        /*  Ignore shi->fKeepConn - we don't want the advise
1329         */
1330        apr_ssize_t ate = send_response_header(cid, shi->pszStatus,
1331                                               shi->pszHeader,
1332                                               shi->cchStatus,
1333                                               shi->cchHeader);
1334        if (ate < 0) {
1335            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1336            return 0;
1337        }
1338        else if ((apr_size_t)ate < shi->cchHeader) {
1339            apr_bucket_brigade *bb;
1340            apr_bucket *b;
1341            bb = apr_brigade_create(cid->r->pool, c->bucket_alloc);
1342            b = apr_bucket_transient_create(shi->pszHeader + ate,
1343                                            shi->cchHeader - ate,
1344                                            c->bucket_alloc);
1345            APR_BRIGADE_INSERT_TAIL(bb, b);
1346            b = apr_bucket_flush_create(c->bucket_alloc);
1347            APR_BRIGADE_INSERT_TAIL(bb, b);
1348            rv = ap_pass_brigade(cid->r->output_filters, bb);
1349            cid->response_sent = 1;
1350            if (rv != APR_SUCCESS)
1351                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
1352                              "ServerSupport function "
1353                              "HSE_REQ_SEND_RESPONSE_HEADER_EX "
1354                              "ap_pass_brigade failed: %s", r->filename);
1355            return (rv == APR_SUCCESS);
1356        }
1357        /* Deliberately hold off sending 'just the headers' to begin to
1358         * accumulate the body and speed up the overall response, or at
1359         * least wait for the end the session.
1360         */
1361        return 1;
1362    }
1363
1364    case HSE_REQ_CLOSE_CONNECTION:  /* Added after ISAPI 4.0 */
1365        if (cid->dconf.log_unsupported)
1366            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1367                          "ServerSupportFunction "
1368                          "HSE_REQ_CLOSE_CONNECTION "
1369                          "is not supported: %s", r->filename);
1370        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1371        return 0;
1372
1373    case HSE_REQ_IS_CONNECTED:  /* Added after ISAPI 4.0 */
1374        /* Returns True if client is connected c.f. MSKB Q188346
1375         * assuming the identical return mechanism as HSE_REQ_IS_KEEP_CONN
1376         */
1377        *((int *)buf_data) = (r->connection->aborted == 0);
1378        return 1;
1379
1380    case HSE_REQ_EXTENSION_TRIGGER:  /* Added after ISAPI 4.0 */
1381        /*  Undocumented - defined by the Microsoft Jan '00 Platform SDK
1382         */
1383        if (cid->dconf.log_unsupported)
1384            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1385                          "ServerSupportFunction "
1386                          "HSE_REQ_EXTENSION_TRIGGER "
1387                          "is not supported: %s", r->filename);
1388        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1389        return 0;
1390
1391    default:
1392        if (cid->dconf.log_unsupported)
1393            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1394                          "ServerSupportFunction (%d) not supported: "
1395                          "%s", HSE_code, r->filename);
1396        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1397        return 0;
1398    }
1399}
1400
1401/**********************************************************
1402 *
1403 *  ISAPI Module request invocation section
1404 *
1405 **********************************************************/
1406
1407static apr_status_t isapi_handler (request_rec *r)
1408{
1409    isapi_dir_conf *dconf;
1410    apr_table_t *e;
1411    apr_status_t rv;
1412    isapi_loaded *isa;
1413    isapi_cid *cid;
1414    const char *val;
1415    apr_uint32_t read;
1416    int res;
1417
1418    if(strcmp(r->handler, "isapi-isa")
1419        && strcmp(r->handler, "isapi-handler")) {
1420        /* Hang on to the isapi-isa for compatibility with older docs
1421         * (wtf did '-isa' mean in the first place?) but introduce
1422         * a newer and clearer "isapi-handler" name.
1423         */
1424        return DECLINED;
1425    }
1426    dconf = ap_get_module_config(r->per_dir_config, &isapi_module);
1427    e = r->subprocess_env;
1428
1429    /* Use similar restrictions as CGIs
1430     *
1431     * If this fails, it's pointless to load the isapi dll.
1432     */
1433    if (!(ap_allow_options(r) & OPT_EXECCGI)) {
1434        return HTTP_FORBIDDEN;
1435    }
1436    if (r->finfo.filetype == APR_NOFILE) {
1437        return HTTP_NOT_FOUND;
1438    }
1439    if (r->finfo.filetype != APR_REG) {
1440        return HTTP_FORBIDDEN;
1441    }
1442    if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
1443        r->path_info && *r->path_info) {
1444        /* default to accept */
1445        return HTTP_NOT_FOUND;
1446    }
1447
1448    if (isapi_lookup(r->pool, r->server, r, r->filename, &isa)
1449           != APR_SUCCESS) {
1450        return HTTP_INTERNAL_SERVER_ERROR;
1451    }
1452    /* Set up variables */
1453    ap_add_common_vars(r);
1454    ap_add_cgi_vars(r);
1455    apr_table_setn(e, "UNMAPPED_REMOTE_USER", "REMOTE_USER");
1456    if ((val = apr_table_get(e, "HTTPS")) && (strcmp(val, "on") == 0))
1457        apr_table_setn(e, "SERVER_PORT_SECURE", "1");
1458    else
1459        apr_table_setn(e, "SERVER_PORT_SECURE", "0");
1460    apr_table_setn(e, "URL", r->uri);
1461
1462    /* Set up connection structure and ecb,
1463     * NULL or zero out most fields.
1464     */
1465    cid = apr_pcalloc(r->pool, sizeof(isapi_cid));
1466
1467    /* Fixup defaults for dconf */
1468    cid->dconf.read_ahead_buflen = (dconf->read_ahead_buflen == ISAPI_UNDEF)
1469                                     ? 49152 : dconf->read_ahead_buflen;
1470    cid->dconf.log_unsupported   = (dconf->log_unsupported == ISAPI_UNDEF)
1471                                     ? 0 : dconf->log_unsupported;
1472    cid->dconf.log_to_errlog     = (dconf->log_to_errlog == ISAPI_UNDEF)
1473                                     ? 0 : dconf->log_to_errlog;
1474    cid->dconf.log_to_query      = (dconf->log_to_query == ISAPI_UNDEF)
1475                                     ? 1 : dconf->log_to_query;
1476    cid->dconf.fake_async        = (dconf->fake_async == ISAPI_UNDEF)
1477                                     ? 0 : dconf->fake_async;
1478
1479    cid->ecb = apr_pcalloc(r->pool, sizeof(EXTENSION_CONTROL_BLOCK));
1480    cid->ecb->ConnID = cid;
1481    cid->isa = isa;
1482    cid->r = r;
1483    r->status = 0;
1484
1485    cid->ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
1486    cid->ecb->dwVersion = isa->report_version;
1487    cid->ecb->dwHttpStatusCode = 0;
1488    strcpy(cid->ecb->lpszLogData, "");
1489    /* TODO: are copies really needed here?
1490     */
1491    cid->ecb->lpszMethod = (char*) r->method;
1492    cid->ecb->lpszQueryString = (char*) apr_table_get(e, "QUERY_STRING");
1493    cid->ecb->lpszPathInfo = (char*) apr_table_get(e, "PATH_INFO");
1494    cid->ecb->lpszPathTranslated = (char*) apr_table_get(e, "PATH_TRANSLATED");
1495    cid->ecb->lpszContentType = (char*) apr_table_get(e, "CONTENT_TYPE");
1496
1497    /* Set up the callbacks */
1498    cid->ecb->GetServerVariable = regfnGetServerVariable;
1499    cid->ecb->WriteClient = regfnWriteClient;
1500    cid->ecb->ReadClient = regfnReadClient;
1501    cid->ecb->ServerSupportFunction = regfnServerSupportFunction;
1502
1503    /* Set up client input */
1504    res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
1505    if (res) {
1506        return res;
1507    }
1508
1509    if (ap_should_client_block(r)) {
1510        /* Time to start reading the appropriate amount of data,
1511         * and allow the administrator to tweak the number
1512         */
1513        if (r->remaining) {
1514            cid->ecb->cbTotalBytes = (apr_size_t)r->remaining;
1515            if (cid->ecb->cbTotalBytes > (apr_uint32_t)cid->dconf.read_ahead_buflen)
1516                cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen;
1517            else
1518                cid->ecb->cbAvailable = cid->ecb->cbTotalBytes;
1519        }
1520        else
1521        {
1522            cid->ecb->cbTotalBytes = 0xffffffff;
1523            cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen;
1524        }
1525
1526        cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1);
1527
1528        read = 0;
1529        while (read < cid->ecb->cbAvailable &&
1530               ((res = ap_get_client_block(r, (char*)cid->ecb->lpbData + read,
1531                                        cid->ecb->cbAvailable - read)) > 0)) {
1532            read += res;
1533        }
1534
1535        if (res < 0) {
1536            return HTTP_INTERNAL_SERVER_ERROR;
1537        }
1538
1539        /* Although it's not to spec, IIS seems to null-terminate
1540         * its lpdData string. So we will too.
1541         */
1542        if (res == 0)
1543            cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read;
1544        else
1545            cid->ecb->cbAvailable = read;
1546        cid->ecb->lpbData[read] = '\0';
1547    }
1548    else {
1549        cid->ecb->cbTotalBytes = 0;
1550        cid->ecb->cbAvailable = 0;
1551        cid->ecb->lpbData = NULL;
1552    }
1553
1554    /* To emulate async behavior...
1555     *
1556     * We create a cid->completed mutex and lock on it so that the
1557     * app can believe is it running async.
1558     *
1559     * This request completes upon a notification through
1560     * ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION), which
1561     * unlocks this mutex.  If the HttpExtensionProc() returns
1562     * HSE_STATUS_PENDING, we will attempt to gain this lock again
1563     * which may *only* happen once HSE_REQ_DONE_WITH_SESSION has
1564     * unlocked the mutex.
1565     */
1566    if (cid->dconf.fake_async) {
1567        rv = apr_thread_mutex_create(&cid->completed,
1568                                     APR_THREAD_MUTEX_UNNESTED,
1569                                     r->pool);
1570        if (cid->completed && (rv == APR_SUCCESS)) {
1571            rv = apr_thread_mutex_lock(cid->completed);
1572        }
1573
1574        if (!cid->completed || (rv != APR_SUCCESS)) {
1575            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02112)
1576                          "Failed to create completion mutex");
1577            return HTTP_INTERNAL_SERVER_ERROR;
1578        }
1579    }
1580
1581    /* All right... try and run the sucker */
1582    rv = (*isa->HttpExtensionProc)(cid->ecb);
1583
1584    /* Check for a log message - and log it */
1585    if (cid->ecb->lpszLogData && *cid->ecb->lpszLogData)
1586        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02113)
1587                      "%s: %s", r->filename, cid->ecb->lpszLogData);
1588
1589    switch(rv) {
1590        case 0:  /* Strange, but MS isapi accepts this as success */
1591        case HSE_STATUS_SUCCESS:
1592        case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
1593            /* Ignore the keepalive stuff; Apache handles it just fine without
1594             * the ISAPI Handler's "advice".
1595             * Per Microsoft: "In IIS versions 4.0 and later, the return
1596             * values HSE_STATUS_SUCCESS and HSE_STATUS_SUCCESS_AND_KEEP_CONN
1597             * are functionally identical: Keep-Alive connections are
1598             * maintained, if supported by the client."
1599             * ... so we were pat all this time
1600             */
1601            break;
1602
1603        case HSE_STATUS_PENDING:
1604            /* emulating async behavior...
1605             */
1606            if (cid->completed) {
1607                /* The completion port was locked prior to invoking
1608                 * HttpExtensionProc().  Once we can regain the lock,
1609                 * when ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION)
1610                 * is called by the extension to release the lock,
1611                 * we may finally destroy the request.
1612                 */
1613                (void)apr_thread_mutex_lock(cid->completed);
1614                break;
1615            }
1616            else if (cid->dconf.log_unsupported) {
1617                 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02114)
1618                               "asynch I/O result HSE_STATUS_PENDING "
1619                               "from HttpExtensionProc() is not supported: %s",
1620                               r->filename);
1621                 r->status = HTTP_INTERNAL_SERVER_ERROR;
1622            }
1623            break;
1624
1625        case HSE_STATUS_ERROR:
1626            /* end response if we have yet to do so.
1627             */
1628            ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r, APLOGNO(02115)
1629                          "HSE_STATUS_ERROR result from "
1630                          "HttpExtensionProc(): %s", r->filename);
1631            r->status = HTTP_INTERNAL_SERVER_ERROR;
1632            break;
1633
1634        default:
1635            ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r, APLOGNO(02116)
1636                          "unrecognized result code %d "
1637                          "from HttpExtensionProc(): %s ",
1638                          rv, r->filename);
1639            r->status = HTTP_INTERNAL_SERVER_ERROR;
1640            break;
1641    }
1642
1643    /* Flush the response now, including headers-only responses */
1644    if (cid->headers_set || cid->response_sent) {
1645        conn_rec *c = r->connection;
1646        apr_bucket_brigade *bb;
1647        apr_bucket *b;
1648        apr_status_t rv;
1649
1650        bb = apr_brigade_create(r->pool, c->bucket_alloc);
1651        b = apr_bucket_eos_create(c->bucket_alloc);
1652        APR_BRIGADE_INSERT_TAIL(bb, b);
1653        rv = ap_pass_brigade(r->output_filters, bb);
1654        cid->response_sent = 1;
1655
1656        if (rv != APR_SUCCESS) {
1657            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02117)
1658                          "ap_pass_brigade failed to "
1659                          "complete the response: %s ", r->filename);
1660        }
1661
1662        return OK; /* NOT r->status, even if it has changed. */
1663    }
1664
1665    /* As the client returned no error, and if we did not error out
1666     * ourselves, trust dwHttpStatusCode to say something relevant.
1667     */
1668    if (!ap_is_HTTP_SERVER_ERROR(r->status) && cid->ecb->dwHttpStatusCode) {
1669        r->status = cid->ecb->dwHttpStatusCode;
1670    }
1671
1672    /* For all missing-response situations simply return the status,
1673     * and let the core respond to the client.
1674     */
1675    return r->status;
1676}
1677
1678/**********************************************************
1679 *
1680 *  ISAPI Module Setup Hooks
1681 *
1682 **********************************************************/
1683
1684static int isapi_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
1685{
1686    apr_status_t rv;
1687
1688    apr_pool_create_ex(&loaded.pool, pconf, NULL, NULL);
1689    if (!loaded.pool) {
1690        ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, NULL, APLOGNO(02118)
1691                     "could not create the isapi cache pool");
1692        return APR_EGENERAL;
1693    }
1694
1695    loaded.hash = apr_hash_make(loaded.pool);
1696    if (!loaded.hash) {
1697        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(02119)
1698                     "Failed to create module cache");
1699        return APR_EGENERAL;
1700    }
1701
1702    rv = apr_thread_mutex_create(&loaded.lock, APR_THREAD_MUTEX_DEFAULT,
1703                                 loaded.pool);
1704    if (rv != APR_SUCCESS) {
1705        ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
1706                     "Failed to create module cache lock");
1707        return rv;
1708    }
1709    return OK;
1710}
1711
1712static void isapi_hooks(apr_pool_t *cont)
1713{
1714    ap_hook_pre_config(isapi_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
1715    ap_hook_handler(isapi_handler, NULL, NULL, APR_HOOK_MIDDLE);
1716}
1717
1718AP_DECLARE_MODULE(isapi) = {
1719   STANDARD20_MODULE_STUFF,
1720   create_isapi_dir_config,     /* create per-dir config */
1721   merge_isapi_dir_configs,     /* merge per-dir config */
1722   NULL,                        /* server config */
1723   NULL,                        /* merge server config */
1724   isapi_cmds,                  /* command apr_table_t */
1725   isapi_hooks                  /* register hooks */
1726};
1727