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 
65 module AP_MODULE_DECLARE_DATA isapi_module;
66 
67 #define ISAPI_UNDEF -1
68 
69 /* Our isapi per-dir config structure */
70 typedef 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 
78 typedef struct isapi_loaded isapi_loaded;
79 
80 apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r,
81                           const char *fpath, isapi_loaded** isa);
82 
create_isapi_dir_config(apr_pool_t * p,char * dummy)83 static 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 
merge_isapi_dir_configs(apr_pool_t * p,void * base_,void * add_)96 static 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 
isapi_cmd_cachefile(cmd_parms * cmd,void * dummy,const char * filename)121 static 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 
166 static 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 */
199 static 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 */
206 struct 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 
isapi_unload(isapi_loaded * isa,int force)220 static 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 
cleanup_isapi(void * isa_)244 static 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 
isapi_load(apr_pool_t * p,server_rec * s,isapi_loaded * isa)254 static 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 
isapi_lookup(apr_pool_t * p,server_rec * s,request_rec * r,const char * fpath,isapi_loaded ** isa)326 apr_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 */
476 struct 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 
regfnGetServerVariable(isapi_cid * cid,char * variable_name,void * buf_ptr,apr_uint32_t * buf_size)488 static 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 
regfnReadClient(isapi_cid * cid,void * buf_data,apr_uint32_t * buf_size)590 static 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  */
send_response_header(isapi_cid * cid,const char * stat,const char * head,apr_size_t statlen,apr_size_t headlen)625 static 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 
regfnWriteClient(isapi_cid * cid,void * buf_ptr,apr_uint32_t * size_arg,apr_uint32_t flags)807 static 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 
regfnServerSupportFunction(isapi_cid * cid,apr_uint32_t HSE_code,void * buf_ptr,apr_uint32_t * buf_size,apr_uint32_t * data_type)863 static 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 equivalant 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 
isapi_handler(request_rec * r)1407 static 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 
isapi_pre_config(apr_pool_t * pconf,apr_pool_t * plog,apr_pool_t * ptemp)1684 static 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 
isapi_hooks(apr_pool_t * cont)1712 static 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 
1718 AP_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