mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1984 lines
48 KiB
1984 lines
48 KiB
/*++
|
|
|
|
Copyright (c) 1994 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
httpext.cxx
|
|
|
|
Abstract:
|
|
|
|
This module contains the Microsoft HTTP server extension module
|
|
|
|
Author:
|
|
|
|
John Ludeman (johnl) 09-Oct-1994
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "w3p.hxx"
|
|
#include <iis2pext.h>
|
|
#pragma warning( disable:4509 ) // nonstandard extension: SEH with destructors
|
|
|
|
//
|
|
// Private constants.
|
|
//
|
|
|
|
#define GATEWAY_VERSION "HTTP-SE/2.0"
|
|
|
|
//
|
|
// This is the time to wait for all extensions to get out during shutdown
|
|
// (in seconds). Note the service controller will blow away the service
|
|
// before this timeout period expires
|
|
//
|
|
|
|
#define BGI_EXIT_PERIOD 900
|
|
|
|
//
|
|
// Flags in the _dwFlags field of the SE_INFO extension context
|
|
//
|
|
|
|
#define SE_PRIV_FLAG_SENDING_URL 0x00000001
|
|
#define SE_PRIV_FLAG_IN_CALLBACK 0x00000002
|
|
#define SE_PRIV_FLAG_KEEP_CONN 0x00000004
|
|
|
|
//
|
|
// Lock and unlock routines
|
|
//
|
|
|
|
#define LockExtensionList() EnterCriticalSection( &csExtLock )
|
|
#define UnlockExtensionList() LeaveCriticalSection( &csExtLock )
|
|
|
|
//
|
|
// If the request doesn't specify an entry point, default to using
|
|
// this
|
|
//
|
|
|
|
#define SE_DEFAULT_ENTRY "HttpExtensionProc"
|
|
#define SE_INIT_ENTRY "GetExtensionVersion"
|
|
#define SE_TERM_ENTRY "TerminateExtension"
|
|
|
|
//
|
|
// Private globals.
|
|
//
|
|
|
|
//
|
|
// List of all load extensions
|
|
//
|
|
|
|
LIST_ENTRY ExtensionHead;
|
|
BOOL fExtInitialized = FALSE;
|
|
BOOL fCacheExtensions = TRUE;
|
|
|
|
//
|
|
// Extension list lock
|
|
//
|
|
|
|
CRITICAL_SECTION csExtLock;
|
|
|
|
//
|
|
// Generic mapping for Application access check
|
|
//
|
|
|
|
GENERIC_MAPPING FileGenericMapping =
|
|
{
|
|
FILE_GENERIC_READ,
|
|
FILE_GENERIC_WRITE,
|
|
FILE_GENERIC_EXECUTE,
|
|
FILE_ALL_ACCESS
|
|
};
|
|
|
|
//
|
|
// Extension cache list entry
|
|
//
|
|
|
|
class HTTP_EXT
|
|
{
|
|
public:
|
|
|
|
HTTP_EXT( const CHAR * pszModuleName,
|
|
HMODULE hMod,
|
|
PFN_HTTPEXTENSIONPROC pfnEntryPoint,
|
|
PFN_TERMINATEEXTENSION pfnTerminate )
|
|
: _strModuleName( pszModuleName ),
|
|
_hMod ( hMod ),
|
|
_pfnEntryPoint( pfnEntryPoint ),
|
|
_pfnTerminate ( pfnTerminate ),
|
|
_hLastUser ( NULL ),
|
|
_fIsValid ( FALSE )
|
|
{
|
|
//
|
|
// We expect the first call to GetFileSecurity to fail
|
|
//
|
|
|
|
if ( _strModuleName.IsValid() &&
|
|
LoadAcl() )
|
|
{
|
|
_fIsValid = TRUE;
|
|
}
|
|
}
|
|
|
|
~HTTP_EXT()
|
|
{
|
|
if ( _pfnTerminate )
|
|
{
|
|
_pfnTerminate( HSE_TERM_MUST_UNLOAD );
|
|
}
|
|
|
|
if ( _hMod )
|
|
FreeLibrary( _hMod );
|
|
}
|
|
|
|
BOOL LoadAcl( VOID )
|
|
{
|
|
DWORD cbSecDesc = _buffSD.QuerySize();
|
|
|
|
//
|
|
// Force an access check on the next request
|
|
//
|
|
|
|
SetLastSuccessfulUser( NULL );
|
|
|
|
if ( GetFileSecurity( QueryModuleName(),
|
|
(OWNER_SECURITY_INFORMATION |
|
|
GROUP_SECURITY_INFORMATION |
|
|
DACL_SECURITY_INFORMATION),
|
|
NULL,
|
|
0,
|
|
&cbSecDesc ))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
TryAgain:
|
|
if ( !_buffSD.Resize( cbSecDesc ) ||
|
|
!GetFileSecurity( QueryModuleName(),
|
|
(OWNER_SECURITY_INFORMATION |
|
|
GROUP_SECURITY_INFORMATION |
|
|
DACL_SECURITY_INFORMATION),
|
|
_buffSD.QueryPtr(),
|
|
cbSecDesc,
|
|
&cbSecDesc ))
|
|
{
|
|
//
|
|
// A new ACL may have been written since we checked the old
|
|
// one, so try it again
|
|
//
|
|
|
|
if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
goto TryAgain;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL AccessCheck( HANDLE hImpersonation )
|
|
{
|
|
DWORD dwGrantedAccess;
|
|
BYTE PrivSet[400];
|
|
DWORD cbPrivilegeSet = sizeof(PrivSet);
|
|
BOOL fAccessGranted;
|
|
|
|
if ( !::AccessCheck( QuerySecDesc(),
|
|
hImpersonation,
|
|
FILE_GENERIC_EXECUTE,
|
|
&FileGenericMapping,
|
|
(PRIVILEGE_SET *) &PrivSet,
|
|
&cbPrivilegeSet,
|
|
&dwGrantedAccess,
|
|
&fAccessGranted ) ||
|
|
!fAccessGranted )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL IsValid( VOID ) const
|
|
{ return _fIsValid; }
|
|
|
|
PFN_HTTPEXTENSIONPROC QueryEntryPoint( VOID ) const
|
|
{ return _pfnEntryPoint; }
|
|
|
|
PSECURITY_DESCRIPTOR QuerySecDesc( VOID ) const
|
|
{ return (PSECURITY_DESCRIPTOR) _buffSD.QueryPtr(); }
|
|
|
|
const CHAR * QueryModuleName( VOID ) const
|
|
{ return _strModuleName.QueryStr(); }
|
|
|
|
HMODULE QueryHMod( VOID ) const
|
|
{ return _hMod; }
|
|
|
|
HANDLE QueryLastSuccessfulUser( VOID ) const
|
|
{ return _hLastUser; }
|
|
|
|
VOID SetLastSuccessfulUser( HANDLE hLastUser )
|
|
{ _hLastUser = hLastUser; }
|
|
|
|
LIST_ENTRY _ListEntry;
|
|
|
|
private:
|
|
|
|
STR _strModuleName;
|
|
HMODULE _hMod;
|
|
PFN_HTTPEXTENSIONPROC _pfnEntryPoint;
|
|
PFN_TERMINATEEXTENSION _pfnTerminate;
|
|
BOOL _fIsValid;
|
|
BUFFER _buffSD; // Security descriptor on the DLL
|
|
HANDLE _hLastUser; // Last successful access
|
|
};
|
|
|
|
//
|
|
// Private prototypes
|
|
//
|
|
|
|
extern "C" {
|
|
dllexp
|
|
BOOL
|
|
SEGetEntryPoint(
|
|
const CHAR * pchModuleName,
|
|
HANDLE hImpersonation,
|
|
PFN_HTTPEXTENSIONPROC * ppfnSEProc,
|
|
HMODULE * phMod
|
|
);
|
|
}
|
|
|
|
BOOL
|
|
WINAPI
|
|
GetServerVariable(
|
|
HCONN hConn,
|
|
LPSTR lpszVariableName,
|
|
LPVOID lpvBuffer,
|
|
LPDWORD lpdwSize
|
|
);
|
|
|
|
BOOL
|
|
WINAPI
|
|
WriteClient(
|
|
HCONN hConn,
|
|
LPVOID Buffer,
|
|
LPDWORD lpdwBytes,
|
|
DWORD dwReserved
|
|
);
|
|
|
|
BOOL
|
|
WINAPI
|
|
ReadClient(
|
|
HCONN hConn,
|
|
LPVOID Buffer,
|
|
LPDWORD lpdwBytes
|
|
);
|
|
|
|
BOOL
|
|
WINAPI
|
|
ServerSupportFunction(
|
|
HCONN hConn,
|
|
DWORD dwRequest,
|
|
LPVOID lpvBuffer,
|
|
LPDWORD lpdwSize,
|
|
LPDWORD lpdwDataType
|
|
);
|
|
|
|
BOOL
|
|
HseBuildRawHeaders(
|
|
IN PARAM_LIST * pHeaderList,
|
|
OUT LPSTR lpvBuffer,
|
|
IN OUT LPDWORD lpdwSize
|
|
);
|
|
|
|
/*****************************************************************/
|
|
|
|
BOOL
|
|
HTTP_REQUEST::ProcessBGI(
|
|
const STR & strPath,
|
|
const STR & strWorkingDir,
|
|
BOOL * pfHandled,
|
|
BOOL * pfFinished
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This method handles a gateway request to a server extension DLL
|
|
|
|
Arguments:
|
|
|
|
strPath - Fully qualified path to DLL
|
|
strWorkingDir - Fully qualified path to the webroot extension is in
|
|
pfHandled - Indicates we handled this request
|
|
pfFinished - Indicates no further processing is required
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
PFN_HTTPEXTENSIONPROC pfnSEProc;
|
|
int ret;
|
|
|
|
//
|
|
// Fill out the HTTP Server Extension structure
|
|
//
|
|
|
|
if ( !GetInfo( "REQUEST_METHOD",
|
|
&_SeInfo._strMethod ) ||
|
|
!GetInfo( "QUERY_STRING",
|
|
&_SeInfo._strQuery ) ||
|
|
!GetInfo( "PATH_INFO",
|
|
&_SeInfo._strPathInfo ) ||
|
|
!GetInfo( "CONTENT_TYPE",
|
|
&_SeInfo._strContentType ) ||
|
|
!GetInfo( "PATH_TRANSLATED",
|
|
&_SeInfo._strPathTrans ))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
_SeInfo.ecb.cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
|
|
_SeInfo.ecb.dwVersion = MAKELONG( HSE_VERSION_MINOR, HSE_VERSION_MAJOR );
|
|
_SeInfo.ecb.ConnID = (HCONN) &_SeInfo;
|
|
_SeInfo.ecb.dwHttpStatusCode = HT_OK;
|
|
_SeInfo.ecb.lpszLogData[0] = '\0';
|
|
_SeInfo.ecb.lpszMethod = _SeInfo._strMethod.QueryStr();
|
|
_SeInfo.ecb.lpszQueryString = _SeInfo._strQuery.QueryStr();
|
|
_SeInfo.ecb.lpszPathInfo = _SeInfo._strPathInfo.QueryStr();
|
|
_SeInfo.ecb.lpszPathTranslated = _SeInfo._strPathTrans.QueryStr();
|
|
_SeInfo.ecb.lpszContentType = _SeInfo._strContentType.QueryStr();
|
|
_SeInfo.ecb.cbAvailable = QueryGatewayDataCB();
|
|
_SeInfo.ecb.cbTotalBytes = QueryClientContentLength();
|
|
|
|
//
|
|
// Clients can send more bytes then are indicated in their
|
|
// Content-Length header. Adjust byte counts so they match
|
|
//
|
|
|
|
if ( _SeInfo.ecb.cbAvailable > _SeInfo.ecb.cbTotalBytes )
|
|
{
|
|
_SeInfo.ecb.cbAvailable = _SeInfo.ecb.cbTotalBytes;
|
|
}
|
|
|
|
_SeInfo.ecb.GetServerVariable= GetServerVariable;
|
|
_SeInfo.ecb.WriteClient = WriteClient;
|
|
_SeInfo.ecb.ReadClient = ReadClient;
|
|
_SeInfo.ecb.ServerSupportFunction = ServerSupportFunction;
|
|
|
|
_SeInfo._dwFlags = SE_PRIV_FLAG_IN_CALLBACK;
|
|
_SeInfo._pRequest = this;
|
|
|
|
_SeInfo._pfnHseIO = NULL;
|
|
_SeInfo._pvHseIOContext = NULL;
|
|
_SeInfo._fOutstandingIO = FALSE;
|
|
_SeInfo._cbLastAsyncIO = 0;
|
|
|
|
//
|
|
// The ref count is two because the pending return and done with session
|
|
// request need to decrement it before we cleanup. It's only
|
|
// used if pending is returned
|
|
//
|
|
|
|
_SeInfo._cRef = 2;
|
|
|
|
if ( _SeInfo.ecb.cbAvailable )
|
|
_SeInfo.ecb.lpbData = QueryGatewayData();
|
|
else
|
|
_SeInfo.ecb.lpbData = NULL;
|
|
|
|
if ( !ImpersonateUser() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !SEGetEntryPoint( strPath.QueryStr(),
|
|
QueryImpersonationHandle(),
|
|
&pfnSEProc,
|
|
&_SeInfo._hMod ))
|
|
{
|
|
RevertUser();
|
|
|
|
if ( GetLastError() == ERROR_ACCESS_DENIED )
|
|
{
|
|
SetDeniedFlags( SF_DENIED_RESOURCE );
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
Reference();
|
|
|
|
//
|
|
// Update the statistics counters
|
|
//
|
|
|
|
INCREMENT_COUNTER( TotalBGIRequests );
|
|
INCREMENT_COUNTER( CurrentBGIRequests );
|
|
|
|
if ( W3Stats.CurrentBGIRequests > W3Stats.MaxBGIRequests )
|
|
{
|
|
LockStatistics();
|
|
|
|
if ( W3Stats.CurrentBGIRequests > W3Stats.MaxBGIRequests )
|
|
W3Stats.MaxBGIRequests = W3Stats.CurrentBGIRequests;
|
|
|
|
UnlockStatistics();
|
|
}
|
|
|
|
//
|
|
// Temporarily allow the number of pool threads to grow while we're in
|
|
// the extension
|
|
//
|
|
|
|
AtqSetInfo( AtqIncMaxPoolThreads, 0 );
|
|
|
|
#ifdef CHICAGO
|
|
//
|
|
// No to SEH for Win 95
|
|
//
|
|
|
|
ret = pfnSEProc( &psei->ecb );
|
|
#else
|
|
|
|
//
|
|
// Protect the call to the server extension so we don't hose the
|
|
// server
|
|
//
|
|
|
|
__try {
|
|
ret = pfnSEProc( &_SeInfo.ecb );
|
|
}
|
|
__except ( EXCEPTION_EXECUTE_HANDLER )
|
|
{
|
|
const CHAR * apsz[1];
|
|
|
|
apsz[0] = strPath.QueryStr();
|
|
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"\n\n[ProcessBGI] Exception occurred calling %s\n",
|
|
strPath.QueryStr() ));
|
|
|
|
g_pTsvcInfo->LogEvent( W3_EVENT_EXTENSION_EXCEPTION,
|
|
1,
|
|
apsz,
|
|
0 );
|
|
|
|
ret = HSE_STATUS_ERROR;
|
|
}
|
|
#endif // CHICAGO
|
|
|
|
AtqSetInfo( AtqDecMaxPoolThreads, 0 );
|
|
|
|
RevertUser();
|
|
|
|
_SeInfo._dwFlags &= ~SE_PRIV_FLAG_IN_CALLBACK;
|
|
|
|
switch ( ret )
|
|
{
|
|
case HSE_STATUS_PENDING:
|
|
|
|
IF_DEBUG( BGI )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[ProcessBGI] Client returned pending\n"));
|
|
}
|
|
|
|
//
|
|
// Note we don't dereference the connection unless the client has
|
|
// already indicated they are done with the session
|
|
//
|
|
|
|
if ( !InterlockedDecrement( (LONG *) &_SeInfo._cRef ) )
|
|
{
|
|
if ( _SeInfo._dwFlags & SE_PRIV_FLAG_KEEP_CONN )
|
|
ret = HSE_STATUS_SUCCESS_AND_KEEP_CONN;
|
|
|
|
goto FreeContext;
|
|
}
|
|
|
|
*pfFinished = FALSE;
|
|
|
|
break;
|
|
|
|
case HSE_STATUS_ERROR:
|
|
case HSE_STATUS_SUCCESS:
|
|
|
|
//
|
|
// Clear the keep-connection flag if the extension isn't expecting
|
|
// the connect to be kept open
|
|
//
|
|
|
|
SetKeepConn( FALSE );
|
|
|
|
//
|
|
// Fall through
|
|
//
|
|
|
|
case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
|
|
default:
|
|
|
|
FreeContext:
|
|
|
|
//
|
|
// If the client sent an URL, then we have an async request outstanding
|
|
// that will clean things up, otherwise there's nothing more we need
|
|
// to do
|
|
//
|
|
|
|
if ( !(_SeInfo._dwFlags & SE_PRIV_FLAG_SENDING_URL) )
|
|
{
|
|
*pfFinished = TRUE;
|
|
}
|
|
|
|
//
|
|
// If we're not caching the extensions, unload it now
|
|
//
|
|
|
|
if ( !fCacheExtensions )
|
|
{
|
|
PFN_TERMINATEEXTENSION pfnTerminate;
|
|
|
|
pfnTerminate = (PFN_TERMINATEEXTENSION) GetProcAddress(
|
|
_SeInfo._hMod,
|
|
SE_TERM_ENTRY );
|
|
|
|
if ( pfnTerminate )
|
|
{
|
|
pfnTerminate( HSE_TERM_MUST_UNLOAD );
|
|
}
|
|
|
|
TCP_REQUIRE( FreeLibrary( _SeInfo._hMod ) );
|
|
}
|
|
|
|
DECREMENT_COUNTER( CurrentBGIRequests );
|
|
|
|
//
|
|
// If we're not sending an URL and the dll does not indicate
|
|
// they support open TCP sessions and the client supports
|
|
// open TCP sessions then disconnect now
|
|
//
|
|
|
|
if ( !(_SeInfo._dwFlags & SE_PRIV_FLAG_SENDING_URL) &&
|
|
!(ret == HSE_STATUS_SUCCESS_AND_KEEP_CONN &&
|
|
IsKeepConnSet()))
|
|
{
|
|
//
|
|
// We'll eventually do a shutdown() which is important due to
|
|
// the POST issue of sending an extra CR/LF
|
|
//
|
|
|
|
SetKeepConn( FALSE );
|
|
}
|
|
|
|
if ( ret != HSE_STATUS_ERROR && ret != HSE_STATUS_PENDING )
|
|
{
|
|
TCP_ASSERT( strlen( _SeInfo.ecb.lpszLogData ) + 1 <=
|
|
sizeof(_SeInfo.ecb.lpszLogData ));
|
|
|
|
//
|
|
// Append the log info to the parameters portion of the logfile
|
|
//
|
|
|
|
AppendLogParameter( _SeInfo.ecb.lpszLogData );
|
|
}
|
|
|
|
//
|
|
// This is a little bit bogus, we're not translating the
|
|
// win32 error code based on the HTTP status code
|
|
//
|
|
|
|
SetLogStatus( _SeInfo.ecb.dwHttpStatusCode, NO_ERROR );
|
|
|
|
DereferenceConn( QueryClientConn() );
|
|
break;
|
|
}
|
|
|
|
*pfHandled = TRUE;
|
|
return TRUE;
|
|
|
|
ErrorExit:
|
|
return FALSE;
|
|
}
|
|
|
|
/*****************************************************************/
|
|
|
|
dllexp
|
|
BOOL
|
|
SEGetEntryPoint(
|
|
const CHAR * pchModuleName,
|
|
HANDLE hImpersonation,
|
|
PFN_HTTPEXTENSIONPROC * ppfnSEProc,
|
|
HMODULE * phMod
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Retrieves an extension's DLL entry point
|
|
|
|
The appropriate user should be impersonated before calling this function
|
|
|
|
Arguments:
|
|
|
|
pchModuleName - Extension DLL module name
|
|
hImpersonation - Impersonation token of user making this call
|
|
ppfnSEProc - Receives pointer to extension DLL
|
|
phMod - Pointer to module handle
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE on error
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY * pEntry;
|
|
HTTP_EXT * pExtension;
|
|
PFN_GETEXTENSIONVERSION pfnGetExtVer;
|
|
PFN_TERMINATEEXTENSION pfnTerminate;
|
|
HSE_VERSION_INFO ExtensionVersion;
|
|
BOOL fRet = TRUE;
|
|
|
|
IF_DEBUG( BGI )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[SEGetEntryPoint] Loading %s\n",
|
|
pchModuleName ));
|
|
|
|
}
|
|
|
|
//
|
|
// Check cache to see if DLL is already loaded
|
|
//
|
|
|
|
LockExtensionList();
|
|
|
|
for ( pEntry = ExtensionHead.Flink;
|
|
pEntry != &ExtensionHead;
|
|
pEntry = pEntry->Flink )
|
|
{
|
|
pExtension = CONTAINING_RECORD( pEntry, HTTP_EXT, _ListEntry );
|
|
|
|
if ( !_stricmp( pchModuleName,
|
|
pExtension->QueryModuleName() ))
|
|
{
|
|
//
|
|
// Already Loaded, get the cached info and do the access check
|
|
//
|
|
|
|
*ppfnSEProc = pExtension->QueryEntryPoint();
|
|
*phMod = pExtension->QueryHMod();
|
|
|
|
//
|
|
// Optimize for the anonymous user and only do the access
|
|
// check if this is a different user then the last successful
|
|
// user
|
|
//
|
|
|
|
if ( hImpersonation != pExtension->QueryLastSuccessfulUser() )
|
|
{
|
|
if ( fRet = pExtension->AccessCheck( hImpersonation ) )
|
|
{
|
|
pExtension->SetLastSuccessfulUser( hImpersonation );
|
|
}
|
|
}
|
|
|
|
UnlockExtensionList();
|
|
return fRet;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Not loaded
|
|
//
|
|
|
|
*phMod = LoadLibraryEx( pchModuleName,
|
|
NULL,
|
|
LOAD_WITH_ALTERED_SEARCH_PATH );
|
|
|
|
if ( !*phMod )
|
|
{
|
|
UnlockExtensionList();
|
|
|
|
if ( GetLastError() != ERROR_ACCESS_DENIED )
|
|
{
|
|
const CHAR * apsz[1];
|
|
|
|
apsz[0] = pchModuleName;
|
|
|
|
g_pTsvcInfo->LogEvent( W3_EVENT_EXTENSION_LOAD_FAILED,
|
|
1,
|
|
apsz,
|
|
GetLastError() );
|
|
}
|
|
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[SEGetEntryPoint] LoadLibrary failed with error %d\n",
|
|
GetLastError()));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// check machine type from header
|
|
//
|
|
|
|
LPBYTE pImg = (LPBYTE)*phMod;
|
|
|
|
// skip possible DOS header
|
|
|
|
if ( ((IMAGE_DOS_HEADER*)pImg)->e_magic == IMAGE_DOS_SIGNATURE )
|
|
pImg += ((IMAGE_DOS_HEADER*)pImg)->e_lfanew;
|
|
|
|
// test only if NT header detected
|
|
|
|
if ( *(DWORD*)pImg == IMAGE_NT_SIGNATURE
|
|
&& ( ((IMAGE_FILE_HEADER*)(pImg+sizeof(DWORD)))->Machine
|
|
< USER_SHARED_DATA->ImageNumberLow
|
|
|| ((IMAGE_FILE_HEADER*)(pImg+sizeof(DWORD)))->Machine
|
|
> USER_SHARED_DATA->ImageNumberHigh ) )
|
|
{
|
|
UnlockExtensionList();
|
|
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[SEGetEntryPoint] LoadLibrary loaded bad format exe type %d, valid range %d-%d\n",
|
|
((IMAGE_FILE_HEADER*)(pImg+sizeof(DWORD)))->Machine, USER_SHARED_DATA->ImageNumberLow, USER_SHARED_DATA->ImageNumberHigh
|
|
));
|
|
|
|
SetLastError( ERROR_BAD_EXE_FORMAT );
|
|
FreeLibrary( *phMod );
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Retrieve the entry point
|
|
//
|
|
|
|
*ppfnSEProc = (PFN_HTTPEXTENSIONPROC) GetProcAddress(
|
|
*phMod,
|
|
SE_DEFAULT_ENTRY );
|
|
|
|
pfnGetExtVer = (PFN_GETEXTENSIONVERSION) GetProcAddress(
|
|
*phMod,
|
|
SE_INIT_ENTRY );
|
|
|
|
//
|
|
// Revert our security context here so GetExtensionVersion is called in
|
|
// the system context
|
|
//
|
|
|
|
RevertToSelf();
|
|
|
|
if ( !*ppfnSEProc ||
|
|
!pfnGetExtVer ||
|
|
!pfnGetExtVer( &ExtensionVersion ))
|
|
{
|
|
UnlockExtensionList();
|
|
FreeLibrary( *phMod );
|
|
return FALSE;
|
|
}
|
|
|
|
pfnTerminate = (PFN_TERMINATEEXTENSION) GetProcAddress(
|
|
*phMod,
|
|
SE_TERM_ENTRY );
|
|
|
|
if ( fCacheExtensions )
|
|
{
|
|
pExtension = new HTTP_EXT( pchModuleName,
|
|
*phMod,
|
|
*ppfnSEProc,
|
|
pfnTerminate );
|
|
|
|
if ( !pExtension )
|
|
{
|
|
if ( pfnTerminate )
|
|
{
|
|
pfnTerminate( HSE_TERM_MUST_UNLOAD );
|
|
}
|
|
|
|
UnlockExtensionList();
|
|
|
|
FreeLibrary( *phMod );
|
|
return FALSE;
|
|
}
|
|
|
|
InsertHeadList( &ExtensionHead,
|
|
&pExtension->_ListEntry );
|
|
|
|
pExtension->SetLastSuccessfulUser( hImpersonation );
|
|
}
|
|
|
|
UnlockExtensionList();
|
|
|
|
//
|
|
// Re-impersonate before returning
|
|
//
|
|
|
|
if ( !ImpersonateLoggedOnUser( hImpersonation ))
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[SEGetEntryPoint] Re-impersonation failed, error %d\n",
|
|
GetLastError() ));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[SEGetEntryPoint] Loaded extension %s, description \"%s\"\n",
|
|
pchModuleName,
|
|
ExtensionVersion.lpszExtensionDesc ));
|
|
|
|
return fRet;
|
|
}
|
|
|
|
BOOL
|
|
RefreshISAPIAcl(
|
|
const CHAR * pchDLL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function reloads the ACL on an ISAPI Application .dll after that
|
|
.dll has already been loaded.
|
|
|
|
Arguments:
|
|
|
|
hConn - Connection context (pointer to SE_INFO)
|
|
dwHSERequest - Request type
|
|
lpvBuffer - Buffer for request
|
|
lpdwSize -
|
|
lpdwDataType
|
|
|
|
Return Value:
|
|
|
|
TRUE on success, FALSE on failure
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY * pEntry;
|
|
HTTP_EXT * pExtension;
|
|
BOOL fRet = TRUE;
|
|
|
|
IF_DEBUG( BGI )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[RefreshISAPIAcl] Rereading ACL for %s\n",
|
|
pchDLL ));
|
|
|
|
}
|
|
|
|
//
|
|
// Check cache to see if the DLL is loaded
|
|
//
|
|
|
|
LockExtensionList();
|
|
|
|
for ( pEntry = ExtensionHead.Flink;
|
|
pEntry != &ExtensionHead;
|
|
pEntry = pEntry->Flink )
|
|
{
|
|
pExtension = CONTAINING_RECORD( pEntry, HTTP_EXT, _ListEntry );
|
|
|
|
if ( !_stricmp( pchDLL,
|
|
pExtension->QueryModuleName() ))
|
|
{
|
|
//
|
|
// Force an access check on the next request with the new ACL
|
|
//
|
|
|
|
pExtension->SetLastSuccessfulUser( NULL );
|
|
|
|
fRet = pExtension->LoadAcl();
|
|
|
|
UnlockExtensionList();
|
|
return fRet;
|
|
}
|
|
}
|
|
|
|
UnlockExtensionList();
|
|
|
|
SetLastError( ERROR_FILE_NOT_FOUND );
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
/*****************************************************************/
|
|
|
|
BOOL
|
|
WINAPI
|
|
ServerSupportFunction(
|
|
HCONN hConn,
|
|
DWORD dwHSERequest,
|
|
LPVOID pData,
|
|
LPDWORD lpdwSize,
|
|
LPDWORD lpdwDataType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This method handles a gateway request to a server extension DLL
|
|
|
|
Arguments:
|
|
|
|
hConn - Connection context (pointer to SE_INFO)
|
|
dwHSERequest - Request type
|
|
lpvBuffer - Buffer for request
|
|
lpdwSize -
|
|
lpdwDataType
|
|
|
|
Return Value:
|
|
|
|
TRUE on success, FALSE on failure
|
|
|
|
--*/
|
|
{
|
|
HTTP_REQUEST * pRequest;
|
|
STR str;
|
|
STR strURL;
|
|
DWORD cb;
|
|
SE_INFO * psei = (SE_INFO *) hConn;
|
|
EXTENSION_CONTROL_BLOCK * pecb = (EXTENSION_CONTROL_BLOCK *) hConn;
|
|
BOOL fKeepConn;
|
|
BOOL fSt;
|
|
BOOL fReturn = TRUE;
|
|
|
|
//
|
|
// Check for valid parameters
|
|
//
|
|
|
|
if ( !pecb || pecb->cbSize != sizeof(EXTENSION_CONTROL_BLOCK) )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[ServerSupportFunction: Extension passed invalid parameters\r\n"));
|
|
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
pRequest = psei->_pRequest;
|
|
|
|
if ( !pRequest->QueryClientConn()->CheckSignature() )
|
|
{
|
|
TCP_ASSERT( FALSE );
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Handle the server extension's request
|
|
//
|
|
|
|
switch ( dwHSERequest )
|
|
{
|
|
|
|
//
|
|
// IO Completion routine is provided.
|
|
//
|
|
|
|
case HSE_REQ_IO_COMPLETION:
|
|
|
|
//
|
|
// Should I check for parameter validity or that it is not being set
|
|
// again?
|
|
// NYI
|
|
//
|
|
|
|
if ( pData != NULL) {
|
|
|
|
//
|
|
// Set the callback function
|
|
//
|
|
|
|
psei->_pfnHseIO = (PFN_HSE_IO_COMPLETION ) pData;
|
|
}
|
|
|
|
psei->_pvHseIOContext = (PVOID ) lpdwDataType;
|
|
break;
|
|
|
|
|
|
//
|
|
// TransmitFile() IO is requested.
|
|
//
|
|
|
|
case HSE_REQ_TRANSMIT_FILE:
|
|
{
|
|
// pData = pHseTfInfo
|
|
|
|
LPHSE_TF_INFO pHseTf = (LPHSE_TF_INFO ) pData;
|
|
DWORD dwFlags = IO_FLAG_ASYNC;
|
|
PVOID pHead = NULL;
|
|
DWORD cbHead = 0;
|
|
DWORD cbToSend;
|
|
|
|
|
|
//
|
|
// It is unlikely that ISAPI applications will post
|
|
// multiple outstanding IOs. However we have the state
|
|
// _fOutstandingIO to secure ourselves against such cases.
|
|
// I have not used any critical sections to protect against
|
|
// multiple threads for performance reasons.
|
|
// Today we support only Async IO transfers
|
|
//
|
|
|
|
|
|
if ( pHseTf == NULL || psei->_fOutstandingIO ||
|
|
(pHseTf->dwFlags & HSE_IO_ASYNC == 0)) {
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return ( FALSE);
|
|
}
|
|
|
|
if ( pHseTf->hFile == INVALID_HANDLE_VALUE) {
|
|
|
|
SetLastError( ERROR_INVALID_HANDLE);
|
|
return (FALSE);
|
|
}
|
|
|
|
//
|
|
// Should I check for parameter validity or
|
|
// that it is not being set again?
|
|
// NYI
|
|
//
|
|
|
|
if ( pHseTf->pfnHseIO != NULL) {
|
|
|
|
//
|
|
// Set the callback function. Override old one
|
|
//
|
|
|
|
psei->_pfnHseIO = pHseTf->pfnHseIO;
|
|
}
|
|
|
|
if ( NULL == psei->_pfnHseIO) {
|
|
|
|
// No callback specified. return error
|
|
SetLastError( ERROR_INVALID_PARAMETER);
|
|
return (FALSE);
|
|
}
|
|
|
|
psei->_fOutstandingIO = TRUE;
|
|
|
|
if ( pHseTf->pContext != NULL) {
|
|
|
|
// Override the old context
|
|
|
|
psei->_pvHseIOContext = pHseTf->pContext;
|
|
}
|
|
|
|
//
|
|
// If we need to do the end of request notification, then don't
|
|
// disconnect the socket after the TransmitFile()
|
|
//
|
|
|
|
if ( !(dwAllNotifFlags & SF_NOTIFY_END_OF_REQUEST) &&
|
|
pHseTf->dwFlags & HSE_IO_DISCONNECT_AFTER_SEND) {
|
|
|
|
// suggests fast close & reuse of data socket
|
|
dwFlags |= (TF_DISCONNECT | TF_REUSE_SOCKET);
|
|
}
|
|
|
|
//
|
|
// Check if ISAPI requests us to send headers
|
|
// If we need to send headers, we will call upon BuildHttpHeader()
|
|
// to construct a custom header for the client.
|
|
// It is the application's responsibility not to use
|
|
// HSE_REQ_SEND_RESOPNSE_HEADER if it chooses to use
|
|
// the header option HSE_IO_SEND_HEADERS.
|
|
//
|
|
|
|
if ( pHseTf->dwFlags & HSE_IO_SEND_HEADERS) {
|
|
|
|
BOOL fFinished = FALSE;
|
|
|
|
//
|
|
// Format the header using the pszStatusCode and
|
|
// extra headers specified in pHseTf->pHead.
|
|
//
|
|
|
|
DBG_ASSERT( psei->_pRequest);
|
|
|
|
if ( !psei->_pRequest->BuildHttpHeader( &fFinished,
|
|
(CHAR * )
|
|
pHseTf->pszStatusCode,
|
|
(CHAR * )
|
|
pHseTf->pHead )
|
|
) {
|
|
|
|
psei->_fOutstandingIO = FALSE;
|
|
return ( FALSE);
|
|
}
|
|
|
|
pHead = psei->_pRequest->QueryRespBuf();
|
|
cbHead = psei->_pRequest->QueryRespBufCB();
|
|
} else {
|
|
|
|
pHead = pHseTf->pHead;
|
|
cbHead = pHseTf->HeadLength;
|
|
}
|
|
|
|
//
|
|
// Setup stage for and execute TransmitFile operation
|
|
//
|
|
|
|
//
|
|
// 1. Set Request state to be async IO from ISAPI client
|
|
// 2. Submit Async IOP
|
|
// 3. return to the ISAPI application
|
|
//
|
|
|
|
pRequest->SetState( HTR_GATEWAY_ASYNC_IO);
|
|
|
|
//
|
|
// If a size of zero was passed in, get the size now so we can
|
|
// record the number of bytes to complete the IO with
|
|
//
|
|
|
|
if ( !pHseTf->BytesToWrite )
|
|
{
|
|
BY_HANDLE_FILE_INFORMATION hfi;
|
|
|
|
if ( !GetFileInformationByHandle( pHseTf->hFile, &hfi ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( hfi.nFileSizeHigh )
|
|
{
|
|
psei->_fOutstandingIO = FALSE;
|
|
SetLastError( ERROR_NOT_SUPPORTED );
|
|
return FALSE;
|
|
}
|
|
|
|
cbToSend = hfi.nFileSizeLow;
|
|
}
|
|
else
|
|
{
|
|
cbToSend = pHseTf->BytesToWrite;
|
|
}
|
|
|
|
psei->_cbLastAsyncIO = cbToSend;
|
|
|
|
fReturn = pRequest->TransmitFileEx(pHseTf->hFile,
|
|
pHseTf->Offset,
|
|
pHseTf->BytesToWrite,
|
|
dwFlags,
|
|
pHead,
|
|
cbHead,
|
|
pHseTf->pTail,
|
|
pHseTf->TailLength
|
|
);
|
|
|
|
if ( !fReturn) {
|
|
|
|
pRequest->SetState( HTR_DOVERB);
|
|
psei->_fOutstandingIO = FALSE;
|
|
}
|
|
|
|
break;
|
|
} // case HSE_REQ_TRANSMIT_FILE:
|
|
|
|
|
|
//
|
|
// Send an URL redirect message to the browser client
|
|
//
|
|
|
|
case HSE_REQ_SEND_URL_REDIRECT_RESP:
|
|
|
|
if ( !strURL.Copy( (CHAR *) pData ) ||
|
|
!pRequest->BuildURLMovedResponse( pRequest->QueryRespBuf(),
|
|
&strURL ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Use send so the request is synchronous
|
|
//
|
|
|
|
if ( !pRequest->WriteFile( pRequest->QueryRespBufPtr(),
|
|
pRequest->QueryRespBufCB(),
|
|
&cb,
|
|
IO_FLAG_SYNC ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
//
|
|
// Send the contents of the URL to the browser client
|
|
//
|
|
|
|
case HSE_REQ_SEND_URL:
|
|
|
|
//
|
|
// Since the send operation is async, we can do this
|
|
// only if there is not outstanding IO operation
|
|
//
|
|
|
|
if ( psei->_fOutstandingIO) {
|
|
|
|
SetLastError( ERROR_INVALID_PARAMETER);
|
|
return (FALSE);
|
|
}
|
|
|
|
//
|
|
// The URL is sent back async. so we need to know *not*
|
|
// to disconnect the client on return from the server
|
|
// extension dll
|
|
//
|
|
|
|
psei->_dwFlags |= SE_PRIV_FLAG_SENDING_URL;
|
|
if ( !strURL.Copy( (CHAR *) pData ) ||
|
|
!pRequest->ReprocessURL( strURL.QueryStr(),
|
|
HTV_GET ))
|
|
{
|
|
psei->_dwFlags &= ~SE_PRIV_FLAG_SENDING_URL;
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
//
|
|
// Send the contents of the URL to the browser client
|
|
//
|
|
|
|
case HSE_REQ_SEND_URL_EX:
|
|
|
|
//
|
|
// The URL is sent back async. so we need to know *not*
|
|
// to disconnect the client on return from the server
|
|
// extension dll
|
|
//
|
|
|
|
if ( !pRequest->OnVerb( (CHAR*)lpdwDataType ) )
|
|
return FALSE;
|
|
|
|
psei->_dwFlags |= SE_PRIV_FLAG_SENDING_URL;
|
|
if ( !strURL.Copy( (CHAR *) pData ) ||
|
|
!pRequest->ReprocessURL( strURL.QueryStr(),
|
|
pRequest->QueryVerb() ))
|
|
{
|
|
psei->_dwFlags &= ~SE_PRIV_FLAG_SENDING_URL;
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
//
|
|
// Build the typical server response headers for the extension DLL
|
|
//
|
|
|
|
case HSE_REQ_SEND_RESPONSE_HEADER:
|
|
|
|
//
|
|
// Temporarily turn off the KeepConn flag so the keep connection
|
|
// header won't be sent out. If the client wants the connection
|
|
// kept alive, they should supply the header themselves (ugly)
|
|
//
|
|
|
|
fKeepConn = pRequest->IsKeepConnSet();
|
|
pRequest->SetKeepConn( FALSE );
|
|
|
|
if ( pData && !strncmp( (PCSTR)pData, "401 ", sizeof("401 ")-1 ) )
|
|
{
|
|
pRequest->SetDeniedFlags( SF_DENIED_APPLICATION );
|
|
pRequest->SetAuthenticationRequested( TRUE );
|
|
}
|
|
|
|
fSt = pRequest->SendHeader( TRUE,
|
|
(CHAR *) pData,
|
|
((CHAR *) lpdwDataType) ?
|
|
((CHAR *) lpdwDataType) : "\r\n" );
|
|
|
|
pRequest->SetKeepConn( fKeepConn );
|
|
|
|
if ( !fSt )
|
|
return FALSE;
|
|
break;
|
|
|
|
//
|
|
// This is an async callback from the extension dll indicating
|
|
// they are done with the socket
|
|
//
|
|
|
|
case HSE_REQ_DONE_WITH_SESSION:
|
|
|
|
//
|
|
// Remember if the client wanted to keep the session open
|
|
//
|
|
|
|
if ( pData &&
|
|
*((DWORD *) pData) == HSE_STATUS_SUCCESS_AND_KEEP_CONN )
|
|
{
|
|
psei->_dwFlags |= SE_PRIV_FLAG_KEEP_CONN;
|
|
}
|
|
else
|
|
{
|
|
pRequest->SetKeepConn( FALSE );
|
|
}
|
|
|
|
//
|
|
// A multi-threaded extension may indicate they
|
|
// are done before returning pending, check for that
|
|
// here and just remember if this is the case.
|
|
//
|
|
|
|
if ( InterlockedDecrement( (LONG *) &psei->_cRef ))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if ( !fCacheExtensions )
|
|
{
|
|
PFN_TERMINATEEXTENSION pfnTerminate;
|
|
|
|
pfnTerminate = (PFN_TERMINATEEXTENSION) GetProcAddress(
|
|
psei->_hMod,
|
|
SE_TERM_ENTRY );
|
|
|
|
if ( pfnTerminate )
|
|
{
|
|
pfnTerminate( HSE_TERM_MUST_UNLOAD );
|
|
}
|
|
|
|
TCP_REQUIRE( FreeLibrary( psei->_hMod ) );
|
|
}
|
|
|
|
DECREMENT_COUNTER( CurrentBGIRequests );
|
|
|
|
TCP_ASSERT( strlen( psei->ecb.lpszLogData ) + 1 <=
|
|
sizeof(psei->ecb.lpszLogData ));
|
|
|
|
//
|
|
// Append the log info to the parameters portion of the logfile
|
|
//
|
|
|
|
pRequest->AppendLogParameter( psei->ecb.lpszLogData );
|
|
pRequest->SetLogStatus( psei->ecb.dwHttpStatusCode, NO_ERROR );
|
|
|
|
pRequest->WriteLogRecord();
|
|
|
|
//
|
|
// If we're not sending an URL and (the client dll doesn't want the
|
|
// connection left open or the web client didn't negotiate it open or
|
|
// we failed to reset things up then disconnect
|
|
//
|
|
|
|
if ( !(psei->_dwFlags & SE_PRIV_FLAG_SENDING_URL) &&
|
|
( (pData &&
|
|
*((DWORD *) pData) != HSE_STATUS_SUCCESS_AND_KEEP_CONN ||
|
|
!pRequest->IsKeepConnSet()) ||
|
|
!pRequest->QueryClientConn()->OnSessionStartup()) )
|
|
{
|
|
pRequest->Disconnect( 0, NO_ERROR, TRUE );
|
|
}
|
|
|
|
DereferenceConn( pRequest->QueryClientConn() );
|
|
|
|
break;
|
|
|
|
//
|
|
// These are Microsoft specific extensions
|
|
//
|
|
|
|
case HSE_REQ_MAP_URL_TO_PATH:
|
|
|
|
if ( !pRequest->LookupVirtualRoot( &str,
|
|
(CHAR *) pData,
|
|
&cb,
|
|
NULL,
|
|
FALSE,
|
|
NULL ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Include one byte for the null terminator
|
|
//
|
|
|
|
cb = str.QueryCB();
|
|
|
|
if ( *lpdwSize < cb++ )
|
|
{
|
|
*lpdwSize = cb;
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return FALSE;
|
|
}
|
|
|
|
*lpdwSize = cb;
|
|
|
|
memcpy( pData,
|
|
str.QueryStr(),
|
|
cb );
|
|
|
|
break;
|
|
|
|
case HSE_REQ_GET_CERT_INFO:
|
|
|
|
//
|
|
// Retrieves the SSPI context and credential handles, only used if
|
|
// using an SSPI package
|
|
//
|
|
|
|
if ( pRequest->QuerySslCtxtHandle() )
|
|
{
|
|
memcpy( pData,
|
|
pRequest->QuerySslCtxtHandle(),
|
|
sizeof( CtxtHandle ));
|
|
}
|
|
else
|
|
{
|
|
memset( pData, 0, sizeof( CtxtHandle ));
|
|
}
|
|
break;
|
|
|
|
case HSE_REQ_GET_SSPI_INFO:
|
|
|
|
//
|
|
// Retrieves the SSPI context and credential handles, only used if
|
|
// using an SSPI package
|
|
//
|
|
|
|
if ( pRequest->IsClearTextPassword() )
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
if ( pRequest->QueryAuthenticationObj()->QueryCtxtHandle() )
|
|
{
|
|
memcpy( pData,
|
|
pRequest->QueryAuthenticationObj()->QueryCtxtHandle(),
|
|
sizeof( CtxtHandle ));
|
|
}
|
|
else
|
|
{
|
|
memset( pData, 0, sizeof( CtxtHandle ));
|
|
}
|
|
|
|
if ( pRequest->QueryAuthenticationObj()->QueryCredHandle() )
|
|
{
|
|
memcpy( lpdwDataType,
|
|
pRequest->QueryAuthenticationObj()->QueryCredHandle(),
|
|
sizeof( CredHandle ));
|
|
}
|
|
else
|
|
{
|
|
memset( lpdwDataType, 0, sizeof( CredHandle ));
|
|
}
|
|
|
|
break;
|
|
|
|
case HSE_APPEND_LOG_PARAMETER:
|
|
return pRequest->AppendLogParameter( (LPSTR)pData );
|
|
break;
|
|
|
|
//
|
|
// This requests forces the server to re-read the ACL on the ISAPI .dll
|
|
//
|
|
|
|
case HSE_REQ_REFRESH_ISAPI_ACL:
|
|
|
|
return RefreshISAPIAcl( (LPSTR) pData );
|
|
|
|
//
|
|
// These are private services
|
|
//
|
|
|
|
case HSE_PRIV_REQ_TSVCINFO:
|
|
*((TSVC_INFO **)pData) = g_pTsvcInfo;
|
|
break;
|
|
|
|
case HSE_PRIV_REQ_HTTP_REQUEST:
|
|
*((HTTP_REQUEST **)pData) = psei->_pRequest;
|
|
break;
|
|
|
|
default:
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
return (fReturn);
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
GetServerVariable(
|
|
HCONN hConn,
|
|
LPSTR lpszVariableName,
|
|
LPVOID lpvBuffer,
|
|
LPDWORD lpdwSize
|
|
)
|
|
{
|
|
HTTP_REQUEST * pRequest;
|
|
SE_INFO * psei = (SE_INFO *) hConn;
|
|
STR str;
|
|
DWORD cb;
|
|
EXTENSION_CONTROL_BLOCK * pecb = (EXTENSION_CONTROL_BLOCK *) hConn;
|
|
BOOL fFound;
|
|
|
|
//
|
|
// Check for valid parameters
|
|
//
|
|
|
|
if ( !pecb || pecb->cbSize != sizeof(EXTENSION_CONTROL_BLOCK) )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[GetServerVariable] Extension passed invalid parameters\r\n"));
|
|
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
pRequest = psei->_pRequest;
|
|
|
|
TCP_ASSERT( pRequest->QueryClientConn()->CheckSignature() );
|
|
|
|
|
|
if ( !strcmp( "ALL_RAW", lpszVariableName)) {
|
|
|
|
//
|
|
// Probably the proxy is making the request
|
|
// Get the raw list of headers
|
|
//
|
|
|
|
return ( HseBuildRawHeaders( pRequest->QueryHeaderList(),
|
|
(LPSTR ) lpvBuffer, lpdwSize)
|
|
);
|
|
}
|
|
|
|
//
|
|
// Get the requested variable and copy it into the supplied buffer
|
|
//
|
|
|
|
if ( !pRequest->GetInfo( lpszVariableName,
|
|
&str,
|
|
&fFound ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !fFound )
|
|
{
|
|
SetLastError( ERROR_INVALID_INDEX );
|
|
return FALSE;
|
|
}
|
|
|
|
cb = str.QueryCB() + sizeof(CHAR);
|
|
|
|
if ( cb > *lpdwSize )
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
*lpdwSize = cb;
|
|
return FALSE;
|
|
}
|
|
|
|
*lpdwSize = cb;
|
|
|
|
memcpy( lpvBuffer,
|
|
str.QueryStr(),
|
|
cb );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
WINAPI
|
|
WriteClient(
|
|
HCONN hConn,
|
|
LPVOID Buffer,
|
|
LPDWORD lpdwBytes,
|
|
DWORD dwReserved
|
|
)
|
|
{
|
|
HTTP_REQUEST * pRequest;
|
|
SE_INFO * psei = (SE_INFO *) hConn;
|
|
EXTENSION_CONTROL_BLOCK * pecb = (EXTENSION_CONTROL_BLOCK *) hConn;
|
|
BOOL fReturn = FALSE;
|
|
|
|
|
|
//
|
|
// Check for valid parameters
|
|
//
|
|
|
|
if ( !pecb || pecb->cbSize != sizeof(EXTENSION_CONTROL_BLOCK) )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[WriteClient] Extension passed invalid parameters\r\n"));
|
|
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
pRequest = psei->_pRequest;
|
|
|
|
//
|
|
// Ignore zero length sends
|
|
//
|
|
|
|
if ( !*lpdwBytes )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
TCP_ASSERT( pRequest->QueryClientConn()->CheckSignature() );
|
|
|
|
//
|
|
// If callback function exists and flags indicate Async IO, do it.
|
|
// Also there should be no outstanding Async IO operation.
|
|
//
|
|
|
|
if ( dwReserved & HSE_IO_ASYNC &&
|
|
psei->_pfnHseIO != NULL &&
|
|
!psei->_fOutstandingIO ) {
|
|
|
|
//
|
|
// 1. Set Request state to be async IO from ISAPI client
|
|
// 2. Submit Async IOP
|
|
// 3. return to the ISAPI application
|
|
//
|
|
|
|
psei->_fOutstandingIO = TRUE;
|
|
psei->_cbLastAsyncIO = *lpdwBytes;
|
|
|
|
pRequest->SetState( HTR_GATEWAY_ASYNC_IO);
|
|
|
|
fReturn = pRequest->WriteFile( Buffer,
|
|
*lpdwBytes,
|
|
lpdwBytes,
|
|
IO_FLAG_ASYNC);
|
|
|
|
if ( !fReturn) {
|
|
|
|
pRequest->SetState( HTR_DOVERB);
|
|
psei->_fOutstandingIO = FALSE;
|
|
}
|
|
|
|
} else {
|
|
|
|
fReturn = pRequest->WriteFile( Buffer,
|
|
*lpdwBytes,
|
|
lpdwBytes,
|
|
IO_FLAG_SYNC
|
|
);
|
|
}
|
|
|
|
return ( fReturn);
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
ReadClient(
|
|
HCONN hConn,
|
|
LPVOID Buffer,
|
|
LPDWORD lpdwBytes
|
|
)
|
|
{
|
|
HTTP_REQUEST * pRequest;
|
|
SE_INFO * psei = (SE_INFO *) hConn;
|
|
EXTENSION_CONTROL_BLOCK * pecb = (EXTENSION_CONTROL_BLOCK *) hConn;
|
|
|
|
//
|
|
// Check for valid parameters
|
|
//
|
|
|
|
if ( !pecb || pecb->cbSize != sizeof(EXTENSION_CONTROL_BLOCK) )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[ReadClient] Extension passed invalid parameters\r\n"));
|
|
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
pRequest = psei->_pRequest;
|
|
|
|
TCP_ASSERT( pRequest->QueryClientConn()->CheckSignature() );
|
|
|
|
if ( !pRequest->ReadFile( Buffer,
|
|
*lpdwBytes,
|
|
lpdwBytes,
|
|
IO_FLAG_SYNC ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
APIERR
|
|
InitializeExtensions(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes the extension list cache
|
|
|
|
|
|
Return Value:
|
|
|
|
0 on success, win32 error on failure
|
|
|
|
--*/
|
|
{
|
|
HKEY hkeyParam;
|
|
|
|
InitializeListHead( &ExtensionHead );
|
|
InitializeCriticalSection( &csExtLock );
|
|
|
|
//
|
|
// Check to see if we should cache extensions
|
|
//
|
|
|
|
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
W3_PARAMETERS_KEY,
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
&hkeyParam ) == NO_ERROR )
|
|
{
|
|
fCacheExtensions = !!ReadRegistryDword( hkeyParam,
|
|
W3_CACHE_EXTENSIONS,
|
|
TRUE );
|
|
|
|
RegCloseKey( hkeyParam );
|
|
}
|
|
|
|
fExtInitialized = TRUE;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
VOID
|
|
TerminateExtensions(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Walks list and unloads each extension. No clients should be in an
|
|
extension at this point
|
|
|
|
--*/
|
|
{
|
|
HTTP_EXT * pExt;
|
|
DWORD i;
|
|
|
|
if ( !fExtInitialized )
|
|
return;
|
|
|
|
LockExtensionList();
|
|
|
|
while ( !IsListEmpty( &ExtensionHead ))
|
|
{
|
|
pExt = CONTAINING_RECORD( ExtensionHead.Flink,
|
|
HTTP_EXT,
|
|
_ListEntry );
|
|
|
|
RemoveEntryList( &pExt->_ListEntry );
|
|
delete pExt;
|
|
}
|
|
|
|
UnlockExtensionList();
|
|
|
|
//
|
|
// If there are any extensions, wait for them to go away before
|
|
// continuing
|
|
//
|
|
|
|
for ( i = 0; i < BGI_EXIT_PERIOD && W3Stats.CurrentBGIRequests; i++ )
|
|
{
|
|
Sleep( 1000 );
|
|
}
|
|
|
|
if ( W3Stats.CurrentBGIRequests )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[TerminateExtensions] Warning - Exiting with active extensions! (%d active)\n",
|
|
W3Stats.CurrentBGIRequests ));
|
|
}
|
|
|
|
DeleteCriticalSection( &csExtLock );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
HseBuildRawHeaders( IN PARAM_LIST * pHeaderList,
|
|
OUT LPSTR pchBuffer,
|
|
IN OUT LPDWORD lpcchSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Builds a list of all raw client passed headers in the form of
|
|
|
|
<header>:<blank><field>\n
|
|
<header>:<blank><field>\n
|
|
|
|
Arguments:
|
|
|
|
pHeaderList - List of headers
|
|
pchBuffer - pointer to buffer which will contain generated headers
|
|
lpcchSize - pointer to DWORD containing size of buffer
|
|
It will contain the size of written data on return.
|
|
|
|
Returns:
|
|
TRUE on success and FALSE if failure.
|
|
Error code is set to ERROR_INSUFFICIENT_BUFFER
|
|
if there is not enough buffer.
|
|
|
|
--*/
|
|
{
|
|
VOID * pvCookie = NULL;
|
|
CHAR * pszHeader = NULL;
|
|
CHAR * pszValue = NULL;
|
|
CHAR * pchStart = pchBuffer;
|
|
|
|
DWORD cchReq = 0;
|
|
BOOL fReturn = TRUE;
|
|
|
|
while ( pvCookie = pHeaderList->NextPair( pvCookie,
|
|
&pszHeader,
|
|
&pszValue ))
|
|
{
|
|
DWORD cchHeader = strlen( pszHeader );
|
|
DWORD cchValue;
|
|
|
|
//
|
|
// Ignore "method", "url" and "version"
|
|
//
|
|
|
|
if ( pszHeader[cchHeader - 1] != ':' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// leave the headers in native form i.e. no conversion of '-' to '_'
|
|
//
|
|
|
|
cchValue = strlen(pszValue);
|
|
cchReq += (cchHeader + cchValue + 3); //+3 for blank & \r\n
|
|
|
|
if ( pchStart != NULL && cchReq < *lpcchSize) {
|
|
|
|
memmove( (LPVOID) pchBuffer,
|
|
pszHeader, cchHeader * sizeof(CHAR));
|
|
pchBuffer += cchHeader;
|
|
*pchBuffer++ = ' '; // store a blank
|
|
memmove( (LPVOID) pchBuffer, pszValue, cchValue);
|
|
pchBuffer += cchValue;
|
|
*pchBuffer++ = '\r'; // store a \r
|
|
*pchBuffer++ = '\n'; // store a \n
|
|
|
|
} else {
|
|
|
|
fReturn = FALSE;
|
|
}
|
|
} // while
|
|
|
|
|
|
// Store the size depending upon if buffer was sufficient or not
|
|
|
|
if ( fReturn) {
|
|
|
|
DBG_ASSERT( pchStart != NULL);
|
|
*pchBuffer++ = '\0'; // +1 for '\0'
|
|
*lpcchSize = (pchBuffer - pchStart);
|
|
} else {
|
|
|
|
*lpcchSize = cchReq;
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
|
|
return (fReturn);
|
|
|
|
} // HseBuildRawHeaders()
|
|
|
|
|
|
|
|
HTTP_REQUEST::ProcessAsyncGatewayIO(VOID)
|
|
{
|
|
|
|
DBG_ASSERT( _SeInfo._pfnHseIO != NULL);
|
|
|
|
//
|
|
// 1. Set the State of the request to be DO_VERB
|
|
// 2. Make the ISAPI callback to indicate that the Async IO is completed.
|
|
//
|
|
|
|
_SeInfo._fOutstandingIO = FALSE;
|
|
|
|
SetState( HTR_DOVERB);
|
|
|
|
__try {
|
|
|
|
DWORD cbWritten;
|
|
|
|
//
|
|
// Adjust bytes written for async writes - otherwise filter adjusted
|
|
// bytes show up which confuses some ISAPI Applications
|
|
//
|
|
|
|
if ( _SeInfo._cbLastAsyncIO && QueryIOStatus() == ERROR_SUCCESS )
|
|
{
|
|
cbWritten = _SeInfo._cbLastAsyncIO;
|
|
_SeInfo._cbLastAsyncIO = 0;
|
|
}
|
|
else
|
|
{
|
|
cbWritten = QueryBytesWritten();
|
|
}
|
|
|
|
|
|
(*_SeInfo._pfnHseIO)( &_SeInfo.ecb,
|
|
_SeInfo._pvHseIOContext,
|
|
cbWritten,
|
|
QueryIOStatus()
|
|
);
|
|
|
|
} __except ( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
const CHAR * apsz[1];
|
|
|
|
apsz[0] = _SeInfo.ecb.lpszQueryString;
|
|
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"\n\n[ProcessAsyncGatewayIO] Exception occurred "
|
|
" in calling the callback for %s\n",
|
|
apsz[0]));
|
|
|
|
g_pTsvcInfo->LogEvent( W3_EVENT_EXTENSION_EXCEPTION,
|
|
1,
|
|
apsz,
|
|
0 );
|
|
|
|
return (FALSE); // How does the cleanup occur???
|
|
}
|
|
|
|
return ( TRUE);
|
|
} // ProcessAsyncGatewayIO()
|
|
|