mod_isapi.c revision 678a15e91d6a44569c956445442731bb64a98a63
6201N/A/* Licensed to the Apache Software Foundation (ASF) under one or more 6201N/A * contributor license agreements. See the NOTICE file distributed with 6201N/A * this work for additional information regarding copyright ownership. 6201N/A * The ASF licenses this file to You under the Apache License, Version 2.0 6201N/A * (the "License"); you may not use this file except in compliance with 6201N/A * the License. You may obtain a copy of the License at 6201N/A * Unless required by applicable law or agreed to in writing, software 6201N/A * distributed under the License is distributed on an "AS IS" BASIS, 6201N/A * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6201N/A * See the License for the specific language governing permissions and 6201N/A * limitations under the License. 6201N/A * by Alexei Kosut <akosut@apache.org>, significant overhauls and 6201N/A * redesign by William Rowe <wrowe@covalent.net>, and hints from many 6201N/A * This module implements the ISAPI Handler architecture, allowing 6201N/A * Apache to load Internet Server Applications (ISAPI extensions), 6201N/A * similar to the support in IIS, Zope, O'Reilly's WebSite and others. 6201N/A * It is a complete implementation of the ISAPI 2.0 specification, 6201N/A * except for "Microsoft extensions" to the API which provide 6201N/A * asynchronous I/O. It is further extended to include additional 6201N/A * "Microsoft extentions" through IIS 5.0, with some deficiencies 6201N/A * where one-to-one mappings don't exist. 6201N/A * configuration and use, but check this source for specific support 6201N/A/* Retry frequency for a failed-to-load isapi .dll */ 6201N/A/********************************************************** 6201N/A * ISAPI Module Configuration 6201N/A **********************************************************/ /* Our isapi per-dir config structure */ /* ### Just an observation ... it would be terribly cool to be * able to use this per-dir, relative to the directory block being * defined. The hash result remains global, but shorthand of * ISAPICacheFile myapp.dll anotherapp.dll thirdapp.dll * would be very convienent. "invalid module path, skipping %s",
filename);
"unable to stat, skipping %s",
fspec);
"not a regular file, skipping %s",
fspec);
/* Load the extention as cached (with null request_rec) */ "unable to cache, skipping %s",
fspec);
OR_FILEINFO,
"Maximum client request body to initially pass to the" " ISAPI handler (default: 49152)"),
OR_FILEINFO,
"Log requests not supported by the ISAPI server" " on or off (default: off)"),
OR_FILEINFO,
"Send all Append Log requests to the error log" " on or off (default: off)"),
OR_FILEINFO,
"Append Log requests are concatinated to the query args" " on or off (default: on)"),
OR_FILEINFO,
"Fake Asynchronous support for isapi callbacks" " on or off [Experimental] (default: off)"),
RSRC_CONF,
"Cache the specified ISAPI extension in-process"),
/********************************************************** * ISAPI Module Cache handling section **********************************************************/ /* Our isapi global config values */ /* Our loaded isapi module description structure */ /* All done with the DLL... get rid of it... * If optionally cached, and we weren't asked to force the unload, * pass HSE_TERM_ADVISORY_UNLOAD, and if it returns 1, unload, * otherwise, leave it alone (it didn't choose to cooperate.) /* We must force the module to unload, we are about * to lose the isapi structure's allocation entirely. /* TODO: These aught to become overrideable, so that we * assure a given isapi can be fooled into behaving well. * The tricky bit, they aren't really a per-dir sort of * config, they will always be constant across every * reference to the .dll no matter what context (vhost, * location, etc) they apply to. isa->
timeout =
300 *
1000000;
/* microsecs, not used */ "missing GetExtensionVersion() in %s",
"missing HttpExtensionProc() in %s",
/* TerminateExtension() is an optional interface */ /* Run GetExtensionVersion() */ "failed call to GetExtensionVersion() in %s",
/* If we find this lock exists, use a set-aside copy of gainlock * to avoid race conditions on NULLing the in_progress variable * when the load has completed. Release the global isapi hash * lock so other requests can proceed, then rdlock for completion * of loading our desired dll or wrlock if we would like to retry * loading the dll (because last_load_rv failed and retry is up.) /* gainlock is NULLed after the module loads successfully. * This free-threaded module can be used without any locking. /* Remember last_load_time before releasing the global * hash lock to avoid colliding with another thread * that hit this exception at the same time as our * retry attempt, since we unlock the global mutex * before attempting a write lock for this module. /* If last_load_time is unchanged, we still own this * retry, otherwise presume another thread provided * our retry (for good or ill). Relock the global * hash for updating last_load_ vars, so their update * is always atomic to the global lock. /* We haven't hit timeup on retry, let's grab the last_rv * within the hash mutex before unlocking. /* If the module was not found, it's time to create a hash key entry * before releasing the hash lock to avoid multiple threads from * loading the same module. /* A mutex that exists only long enough to attempt to * load this isapi dll, the release this module to all * other takers that came along during the one-time * load process. Short lifetime for this lock would * be great, however, using r->pool is nasty if those * blocked on the lock haven't all unlocked before we * attempt to destroy. A nastier race condition than * I want to deal with at this moment... /* Now attempt to load the isapi on our own time, * allow other isapi processing to resume. /* Let others who are blocked on this particular * module resume their requests, for better or worse. /* We must leave a rwlock around for requests to retry * loading this dll after timeup... since we were in * the setup code we had avoided creating this lock. /********************************************************** * ISAPI Module request callbacks section **********************************************************/ /* Our "Connection ID" structure */ /* crlf delimited, colon split, comma separated and * null terminated list of HTTP_ vars /* crlf delimited, colon split, comma separated and * null terminated list of the raw request header /* Common code invoked for both HSE_REQ_SEND_RESPONSE_HEADER and * the newer HSE_REQ_SEND_RESPONSE_HEADER_EX ServerSupportFunction(s) * as well as other functions that write responses and presume that * the support functions above are optional. * Other callers trying to split headers and body bytes should pass * get a proper count of bytes consumed. The argument passed to stat * isn't counted as the head bytes are. /* Now decide if we follow the xxx message * or the http/x.x xxx message format /* Whoops... not NULL terminated */ /* Seems IIS does not enforce the requirement for \r\n termination * on HSE_REQ_SEND_RESPONSE_HEADER, but we won't panic... * ap_scan_script_header_err_strs handles this aspect for us. * Parse them out, or die trying /* This is an immediate error result from the parser /* We have a status in r->status, so let's just use it. * This is likely to be the Status: parsed above, and * may also be a delayed error result from the parser. * If it was filled in, status_line should also have /* Now we fall back on dwHttpStatusCode if it appears * ap_scan_script_header fell back on the default code. * Any other results set dwHttpStatusCode to the decoded /* Well... either there is no dwHttpStatusCode or it's HTTP_OK. * In any case, we don't have a good status to return yet... * Perhaps the one we came in with will be better. Let's use it, * if we were given one (note this is a pendantic case, it would * normally be covered above unless the scan script code unset * the r->status). Should there be a check here as to whether * we are setting a valid response code? /* None of dwHttpStatusCode, the parser's r->status nor the * old value of r->status were helpful, and nothing was decoded * from Status: string passed to us. Let's just say HTTP_OK * and get the data out, this was the isapi dev's oversight. "Could not determine HTTP response code; using %d",
/* If only Status was passed, we consumed nothing /* If all went well, tell the caller we consumed the headers complete /* Any data left must be sent directly by the caller, all we * give back is the size of the headers we consumed (which only * happens if the parser got to the head arg, which varies based * on whether we passed stat+head to scan, or only head. /* It appears that the foxisapi module and other clients * presume that WriteClient("headers\n\nbody") will work. * Parse them out, or die trying. "WriteClient ap_pass_brigade failed: %s",
/* Set the status to be returned when the HttpExtensionProc() * WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP * and HSE_REQ_SEND_URL as equivalant per the Jan 2000 SDK. * They most definately are not, even in their own samples. /* Soak up remaining input */ /* Reset the method to GET */ /* Don't let anyone think there's still data */ /* AV fault per PR3598 - redirected path is lost! */ /* Parse them out, or die trying */ "ServerSupport function " "HSE_REQ_SEND_RESPONSE_HEADER " "ap_pass_brigade failed: %s", r->
filename);
/* Deliberately hold off sending 'just the headers' to begin to * accumulate the body and speed up the overall response, or at * least wait for the end the session. /* Signal to resume the thread completing this request, * leave it to the pool cleanup to dispose of our mutex. "HSE_REQ_DONE_WITH_SESSION is not supported: %s",
/* Map a URL to a filename */ /* We need to make this a real Windows path name */ "ServerSupportFunction HSE_REQ_GET_SSPI_INFO " /* Log buf_data, of buf_size bytes, in the URI Query (cs-uri-query) field /* Emulates a completion port... Record callback address and * user defined arg, we will call this after any async request * (e.g. transmitfile) as if the request executed async. * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call * to HSE_REQ_IO_COMPLETION, and buf_data may be set to NULL. "ServerSupportFunction HSE_REQ_IO_COMPLETION " /* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND) "ServerSupportFunction HSE_REQ_TRANSMIT_FILE " "as HSE_IO_ASYNC is not supported: %s", r->
filename);
/* Presume the handle was opened with the CORRECT semantics /* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */ /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag, * you must have done so. They document that the pHead headers * option is valid only for HSE_IO_SEND_HEADERS - we are a bit * more flexible and assume with the flag, pHead are the * response headers, and without, pHead simply contains text * (handled after this case). "ServerSupport function " "ap_pass_brigade failed: %s", r->
filename);
/* Use tf->pfnHseIO + tf->pContext, or if NULL, then use cid->fnIOComplete * pass pContect to the HseIO callback. "HSE_REQ_REFRESH_ISAPI_ACL " "asynchronous I/O not supported: %s",
/* XXX: Many authors issue their next HSE_REQ_ASYNC_READ_CLIENT * within the completion logic. An example is MS's own PSDK * leads to stack exhaustion. To refactor, the notification * logic needs to move to isapi_handler() - differentiating * the cid->completed event with a new flag to indicate * an async-notice versus the async request completed. "HSE_REQ_GET_IMPERSONATION_TOKEN " /* Map a URL to a filename */ /* Mapping started with assuming both strings matched. * Now roll on the path_info as a mismatch and handle * terminating slashes for directory matches. /* roll forward over path_info's first slash */ /* Add a trailing slash for directory */ /* If the matched isn't a file, roll match back to the prior slash */ /* Paths returned with back slashes */ * HSE_URL_FLAGS_READ 0x001 Allow read * HSE_URL_FLAGS_WRITE 0x002 Allow write * HSE_URL_FLAGS_EXECUTE 0x004 Allow execute * HSE_URL_FLAGS_SSL 0x008 Require SSL * HSE_URL_FLAGS_DONT_CACHE 0x010 Don't cache (VRoot only) * HSE_URL_FLAGS_NEGO_CERT 0x020 Allow client SSL cert * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert * HSE_URL_FLAGS_MAP_CERT 0x080 Map client SSL cert to account * HSE_URL_FLAGS_SSL128 0x100 Require 128-bit SSL cert * HSE_URL_FLAGS_SCRIPT 0x200 Allow script execution * XxX: As everywhere, EXEC flags could use some work... * and this could go further with more flags, as desired. "ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE" "HSE_REQ_GET_CERT_INFO_EX " /* Ignore shi->fKeepConn - we don't want the advise "ServerSupport function " "HSE_REQ_SEND_RESPONSE_HEADER_EX " "ap_pass_brigade failed: %s", r->
filename);
/* Deliberately hold off sending 'just the headers' to begin to * accumulate the body and speed up the overall response, or at * least wait for the end the session. "HSE_REQ_CLOSE_CONNECTION " /* Returns True if client is connected c.f. MSKB Q188346 * assuming the identical return mechanism as HSE_REQ_IS_KEEP_CONN /* Undocumented - defined by the Microsoft Jan '00 Platform SDK "HSE_REQ_EXTENSION_TRIGGER " "ServerSupportFunction (%d) not supported: " /********************************************************** * ISAPI Module request invocation section **********************************************************/ /* Hang on to the isapi-isa for compatibility with older docs * (wtf did '-isa' mean in the first place?) but introduce * a newer and clearer "isapi-handler" name. /* Use similar restrictions as CGIs * If this fails, it's pointless to load the isapi dll. /* Set up connection structure and ecb, * NULL or zero out most fields. /* Fixup defaults for dconf */ /* TODO: are copies really needed here? /* Set up the callbacks */ /* Set up client input */ /* Time to start reading the appropriate amount of data, * and allow the administrator to tweak the number /* Although it's not to spec, IIS seems to null-terminate * its lpdData string. So we will too. /* To emulate async behavior... * We create a cid->completed mutex and lock on it so that the * app can believe is it running async. * This request completes upon a notification through * ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION), which * unlocks this mutex. If the HttpExtensionProc() returns * HSE_STATUS_PENDING, we will attempt to gain this lock again * which may *only* happen once HSE_REQ_DONE_WITH_SESSION has "Failed to create completion mutex");
/* All right... try and run the sucker */ /* Check for a log message - and log it */ case 0:
/* Strange, but MS isapi accepts this as success */ /* Ignore the keepalive stuff; Apache handles it just fine without * the ISAPI Handler's "advice". * Per Microsoft: "In IIS versions 4.0 and later, the return * values HSE_STATUS_SUCCESS and HSE_STATUS_SUCCESS_AND_KEEP_CONN * are functionally identical: Keep-Alive connections are * maintained, if supported by the client." * ... so we were pat all this time /* emulating async behavior... /* The completion port was locked prior to invoking * HttpExtensionProc(). Once we can regain the lock, * when ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION) * is called by the extension to release the lock, * we may finally destroy the request. "asynch I/O result HSE_STATUS_PENDING " "from HttpExtensionProc() is not supported: %s",
/* end response if we have yet to do so. "HSE_STATUS_ERROR result from " "HttpExtensionProc(): %s", r->
filename);
"unrecognized result code %d " "from HttpExtensionProc(): %s ",
/* Flush the response now, including headers-only responses */ "ap_pass_brigade failed to " "complete the response: %s ", r->
filename);
return OK;
/* NOT r->status, even if it has changed. */ /* As the client returned no error, and if we did not error out * ourselves, trust dwHttpStatusCode to say something relevant. /* For all missing-response situations simply return the status, * and let the core respond to the client. /********************************************************** * ISAPI Module Setup Hooks **********************************************************/ "could not create the isapi cache pool");
"Failed to create module cache");
"Failed to create module cache lock");
NULL,
/* server config */ NULL,
/* merge server config */