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