|
|
/**********************************************************************/ /** Microsoft Windows NT **/ /** Copyright(c) Microsoft Corp., 1994 **/ /**********************************************************************/
/*
cgi.cxx
This module contains the gateway interface code for an HTTP request
FILE HISTORY: Johnl 22-Sep-1994 Created MuraliK 22-Jan-1996 Use UNC Impersonation/Revert
*/
#include "w3p.hxx"
#include "issched.hxx"
//
// This is the exit code given to processes that we terminate
//
#define CGI_PREMATURE_DEATH_CODE 0xf1256323
//
// Beyond around this number, CreateProcessXXX return errors
//
#define CGI_COMMAND_LINE_MAXCB 30000
#define ISUNC(a) ((a)[0]=='\\' && (a)[1]=='\\')
//
// The default minimum number of CGI threads we'll allow to
// run at once. This is for compatibility with IIS 3.0 and
// is based on the default number of ATQ threads in 3.0 (4.0
// defaults to four). Note this is only effective if
// UsePoolThreadForCGI is TRUE (default).
//
#define IIS3_MIN_CGI 10
//
// Prototypes
//
BOOL IsCmdExe( const CHAR * pchPath );
//
// Private globals.
//
BOOL fCGIInitialized = FALSE;
LONG g_cMinCGIs = 0;
//
// Controls whether special command characters are allowed in cmd.exe
// requests
//
BOOL fAllowSpecialCharsInShell = FALSE;
typedef struct CgiEnvTableEntry_ { TCHAR* m_pszName; BOOL m_fIsProcessEnv; UINT m_cchNameLen; UINT m_cchToCopy; // will be non zero for var to copy from
// process environment. In this case m_pszName
// points to the environment entry to copy
// ( name + '=' + value + '\0' )
// otherwise this entry is to be accessed
// using GetInfo()
} CgiEnvTableEntry;
//
// Environment variable block used for CGI
//
// best if in alphabetical order ( the env list is easier to read )
// but not mandatory.
// Note that the "" ( accessed as HTTP_ALL ) will be expanded to a list
// of unsorted entries, but this list as a whole will be considered to be
// named "HTTP_ALL" for sorting order.
//
CgiEnvTableEntry CGIEnvTable[] = { {TEXT("AUTH_TYPE"),FALSE}, {TEXT("AUTH_PASSWORD"),FALSE}, {TEXT("AUTH_USER"),FALSE}, {TEXT("ComSpec"),TRUE}, {TEXT("CERT_COOKIE"), FALSE}, {TEXT("CERT_FLAGS"), FALSE}, {TEXT("CERT_ISSUER"), FALSE}, {TEXT("CERT_SERIALNUMBER"), FALSE}, {TEXT("CERT_SUBJECT"), FALSE}, {TEXT("CONTENT_LENGTH"),FALSE}, {TEXT("CONTENT_TYPE"),FALSE}, {TEXT("GATEWAY_INTERFACE"),FALSE}, {TEXT(""),FALSE}, // Means insert all HTTP_ headers here
{TEXT("HTTPS"),FALSE}, {TEXT("HTTPS_KEYSIZE"),FALSE}, {TEXT("HTTPS_SECRETKEYSIZE"),FALSE}, {TEXT("HTTPS_SERVER_ISSUER"),FALSE}, {TEXT("HTTPS_SERVER_SUBJECT"),FALSE}, {TEXT("INSTANCE_ID"),FALSE}, {TEXT("LOCAL_ADDR"),FALSE}, {TEXT("LOGON_USER"),FALSE}, {TEXT("PATH"),TRUE}, {TEXT("PATH_INFO"),FALSE}, {TEXT("PATH_TRANSLATED"),FALSE}, {TEXT("QUERY_STRING"),FALSE}, {TEXT("REMOTE_ADDR"),FALSE}, {TEXT("REMOTE_HOST"),FALSE}, {TEXT("REMOTE_USER"),FALSE}, {TEXT("REQUEST_METHOD"),FALSE}, {TEXT("SCRIPT_NAME"),FALSE}, {TEXT("SERVER_NAME"),FALSE}, {TEXT("SERVER_PORT"),FALSE}, {TEXT("SERVER_PORT_SECURE"),FALSE}, {TEXT("SERVER_PROTOCOL"),FALSE}, {TEXT("SERVER_SOFTWARE"),FALSE}, {TEXT("SystemRoot"),TRUE}, {TEXT("UNMAPPED_REMOTE_USER"),FALSE}, {TEXT("windir"),TRUE}, {NULL,FALSE} };
BOOL fForwardServerEnvironmentBlock = TRUE;
//
// Store environment block for IIS process
//
LPSTR g_pszIisEnv = NULL; CgiEnvTableEntry *g_pEnvEntries = NULL;
VOID WINAPI CGITerminateProcess( PVOID pContext );
extern "C" int __cdecl QsortEnvCmp( const void *pA, const void *pB ) /*++
Routine Description:
Compare CgiEnvTableEntry using their name entry
Arguments:
pA - pointer to 1st entry pB - pointer to 2nd entry
Returns:
-1 if 1st entry comes first in sort order, 0 if identical 1 if 2nd entry comes first
--*/ { LPSTR p1 = ((CgiEnvTableEntry*)pA)->m_pszName; LPSTR p2 = ((CgiEnvTableEntry*)pB)->m_pszName;
if ( ! p1[0] ) { p1 = "HTTP_ALL"; }
if ( ! p2[0] ) { p2 = "HTTP_ALL"; }
return _stricmp( p1, p2 ); }
/*******************************************************************
NAME: CGI_INFO
SYNOPSIS: Simple storage class passed to thread
HISTORY: Johnl 22-Sep-1994 Created
********************************************************************/
class CGI_INFO { public: CGI_INFO( HTTP_REQUEST * pRequest ) : _pRequest ( pRequest ), _cbData ( 0 ), _hStdOut ( NULL ), _hStdIn ( NULL ), _hProcess ( NULL ), _dwSchedCookie ( 0 ), _fServerPoolThread( FALSE ), _pExec ( NULL ) { }
~CGI_INFO( VOID ) {
if ( _hStdOut ) { if ( !::CloseHandle( _hStdOut )) { DBGPRINTF((DBG_CONTEXT, "[~CGI_INFO] CloseHandle failed on StdOut, %d\n", GetLastError())); } }
if ( _hStdIn ) { if ( !::CloseHandle( _hStdIn )) { DBGPRINTF((DBG_CONTEXT, "[~CGI_INFO] CloseHandle failed on StdIn, %d\n", GetLastError())); } }
if ( _hProcess ) { if ( !::CloseHandle( _hProcess )) { DBGPRINTF((DBG_CONTEXT, "[~CGI_INFO] CloseHandle failed on Process, %d\n", GetLastError())); } } DWORD dwCookie = (DWORD) InterlockedExchange( (LPLONG) &_dwSchedCookie, 0 ); if ( dwCookie != 0 ) { RemoveWorkItem( dwCookie ); } }
HTTP_REQUEST * _pRequest; DWORD _dwSchedCookie; // Scheduled callback cookie
BOOL _fServerPoolThread; // Are we running in a server pool
// thread?
//
// Child process
//
HANDLE _hProcess;
//
// Parent's input and output handles and child's process handle
//
HANDLE _hStdOut; HANDLE _hStdIn;
//
// Handles input from CGI (headers and additional data)
//
BUFFER _Buff; UINT _cbData;
LIST_ENTRY _CgiListEntry; static LIST_ENTRY _CgiListHead; static CRITICAL_SECTION _csCgiList;
//
// Execution Descriptor block
//
EXEC_DESCRIPTOR * _pExec; };
//
// Globals
//
CRITICAL_SECTION CGI_INFO::_csCgiList; LIST_ENTRY CGI_INFO::_CgiListHead;
//
// Private prototypes.
//
BOOL ProcessCGIInput( CGI_INFO * pCGIInfo );
BOOL SetupChildEnv( EXEC_DESCRIPTOR * pExec, BUFFER * pBuff );
BOOL SetupChildPipes( STARTUPINFO * pstartupinfo, HANDLE * phParentIn, HANDLE * phParentOut );
BOOL SetupCmdLine( STR * pstrCmdLine, const STR & strParams );
DWORD CGIThread( PVOID Param );
BOOL ProcessCGIInput( CGI_INFO * pCGIInfo, BYTE * buff, DWORD cbRead, BOOL * pfReadHeaders, BOOL * pfDone, BOOL * pfSkipDisconnect, DWORD * pdwHttpStatus );
/*******************************************************************/
APIERR InitializeCGI( VOID ) /*++
Routine Description:
Initialize CGI
Arguments:
None
Return Value:
TRUE if successful, FALSE on error
--*/ { LPVOID pvEnv; UINT cchStr; UINT cchIisEnv; UINT cEnv; INT chScanEndOfName; CgiEnvTableEntry * pCgiEnv; HKEY hkeyParam;
INITIALIZE_CRITICAL_SECTION( &CGI_INFO::_csCgiList ); InitializeListHead( &CGI_INFO::_CgiListHead );
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, W3_PARAMETERS_KEY, 0, KEY_READ, &hkeyParam ) == NO_ERROR ) { fAllowSpecialCharsInShell = !!ReadRegistryDword( hkeyParam, "AllowSpecialCharsInShell", FALSE );
fForwardServerEnvironmentBlock = !!ReadRegistryDword( hkeyParam, "ForwardServerEnvironmentBlock", TRUE );
RegCloseKey( hkeyParam ); }
if ( fForwardServerEnvironmentBlock && (pvEnv = GetEnvironmentStrings()) ) { //
// Compute length of environment block and # of variables
// ( excluding block delimiter )
//
cchIisEnv = 0; cEnv = 0;
while ( cchStr = strlen( ((PSTR)pvEnv) + cchIisEnv ) ) { cchIisEnv += cchStr + 1; ++cEnv; }
++cchIisEnv;
//
// store it
//
if ( (g_pszIisEnv = (LPSTR)LocalAlloc( LMEM_FIXED, cchIisEnv * sizeof(TCHAR))) == NULL ) { return ERROR_NOT_ENOUGH_MEMORY; }
CopyMemory( g_pszIisEnv, pvEnv, cchIisEnv * sizeof(TCHAR) );
FreeEnvironmentStrings( (LPTSTR)pvEnv );
pvEnv = (PVOID)g_pszIisEnv;
if ( g_pEnvEntries = new CgiEnvTableEntry [ cEnv + sizeof(CGIEnvTable)/sizeof(CgiEnvTableEntry) ] ) { cchIisEnv = 0; cEnv = 0;
//
// add process environment to table
//
while ( cchStr = strlen( ((PSTR)pvEnv) + cchIisEnv ) ) { g_pEnvEntries[ cEnv ].m_pszName = ((PSTR)pvEnv) + cchIisEnv; g_pEnvEntries[ cEnv ].m_fIsProcessEnv = TRUE;
// compute length of name : up to '=' char
for ( g_pEnvEntries[ cEnv ].m_cchNameLen = 0 ; ( chScanEndOfName = g_pEnvEntries[ cEnv ].m_pszName [ g_pEnvEntries[ cEnv ].m_cchNameLen ] ) && chScanEndOfName != '=' ; ) { ++g_pEnvEntries[ cEnv ].m_cchNameLen; }
g_pEnvEntries[ cEnv ].m_cchToCopy = cchStr + 1;
cchIisEnv += cchStr + 1; ++cEnv; }
//
// add CGI environment variables to table
//
for ( pCgiEnv = CGIEnvTable ; pCgiEnv->m_pszName ; ++pCgiEnv ) { if ( !pCgiEnv->m_fIsProcessEnv ) { memcpy( g_pEnvEntries + cEnv, pCgiEnv, sizeof(CgiEnvTableEntry) ); g_pEnvEntries[ cEnv ].m_cchNameLen = strlen( pCgiEnv->m_pszName ); g_pEnvEntries[ cEnv ].m_cchToCopy = 0; ++cEnv; } }
//
// add delimiter entry
//
g_pEnvEntries[ cEnv ].m_pszName = NULL;
qsort( g_pEnvEntries, cEnv, sizeof(CgiEnvTableEntry), QsortEnvCmp ); } else { return ERROR_NOT_ENOUGH_MEMORY; } } else { g_pEnvEntries = CGIEnvTable; }
fCGIInitialized = TRUE;
return NO_ERROR;
} // InitializeCGI
VOID TerminateCGI( VOID ) /*++
Routine Description:
Terminate CGI
Arguments:
None
Return Value:
Nothing
--*/ { if ( fCGIInitialized ) { if (g_pszIisEnv != NULL ) { LocalFree( g_pszIisEnv ); g_pszIisEnv = NULL; }
if ( g_pEnvEntries && (g_pEnvEntries != CGIEnvTable) ) { delete [] g_pEnvEntries; }
DeleteCriticalSection( &CGI_INFO::_csCgiList ); }
} // TerminateCGI
VOID KillCGIProcess( VOID ) /*++
Routine Description:
Kill all CGI process
Arguments:
None
Return Value:
Nothing
--*/ { CGI_INFO* pCgi;
if ( fCGIInitialized ) { //
// Kill all outstanding process
//
BOOL bListEmpty = TRUE;
EnterCriticalSection( &CGI_INFO::_csCgiList );
LIST_ENTRY* pEntry;
for ( pEntry = CGI_INFO::_CgiListHead.Flink; pEntry != &CGI_INFO::_CgiListHead ; pEntry = pEntry->Flink ) { pCgi = CONTAINING_RECORD( pEntry, CGI_INFO, CGI_INFO::_CgiListEntry );
bListEmpty = FALSE;
if ( pCgi->_hProcess ) { CGITerminateProcess( pCgi->_hProcess ); } }
LeaveCriticalSection( &CGI_INFO::_csCgiList );
for (int i = 0; !bListEmpty && (i < 5); i++) {
//
// Wait for all threads to complete to avoid
// shutdown timeout problem.
//
Sleep(1000);
EnterCriticalSection( &CGI_INFO::_csCgiList );
if (IsListEmpty(&CGI_INFO::_CgiListHead)) { bListEmpty = TRUE; }
LeaveCriticalSection( &CGI_INFO::_csCgiList ); }
} } // KillCGIProcess
VOID KillCGIInstanceProcs( W3_SERVER_INSTANCE *pw3siInstance ) /*++
Routine Description:
Kill all CGI process
Arguments:
None
Return Value:
Nothing
--*/ { CGI_INFO* pCgi;
if ( fCGIInitialized ) { //
// Kill all outstanding process
//
EnterCriticalSection( &CGI_INFO::_csCgiList );
LIST_ENTRY* pEntry;
for ( pEntry = CGI_INFO::_CgiListHead.Flink; pEntry != &CGI_INFO::_CgiListHead ; pEntry = pEntry->Flink ) { pCgi = CONTAINING_RECORD( pEntry, CGI_INFO, CGI_INFO::_CgiListEntry );
DBG_ASSERT(pCgi->_pExec); DBG_ASSERT(pCgi->_pExec->_pRequest);
if ( (pw3siInstance == pCgi->_pExec->_pRequest->QueryW3Instance()) && pCgi->_hProcess ) { CGITerminateProcess( pCgi->_hProcess ); } }
LeaveCriticalSection( &CGI_INFO::_csCgiList );
} } // KillCGIProcess
BOOL HTTP_REQUEST::ProcessGateway( EXEC_DESCRIPTOR * pExec, BOOL * pfHandled, BOOL * pfFinished, BOOL fTrusted ) /*++
Routine Description:
Prepares for either a CGI call
If the .exe or .dll isn't found, then *pfHandled will be set to FALSE and the request will be processed again with the assumption we just happenned to find a directory with a trailing .exe or .dll.
Arguments:
pExec - Execution descriptor block pfHandled - Indicates if the request was a gateway request pfFinished - Indicates no further processing is required fTrusted - Can this app be trusted to process things on a read only vroot
Return Value:
TRUE if successful, FALSE on error
--*/ { BOOL fRet = TRUE; TCHAR * pch; CHAR tmpStr[MAX_PATH]; STR strWorkingDir(tmpStr,MAX_PATH); DWORD dwMask; INT cStr; BOOL fChild = pExec->IsChild(); DWORD dwAttr; DWORD err;
*pfHandled = FALSE; *pfFinished = FALSE; // ProcessBGI may reset this
if ( !VrootAccessCheck( pExec->_pMetaData, FILE_GENERIC_EXECUTE ) ) { DBGPRINTF(( DBG_CONTEXT, "ACESS_DENIED: User \"%s\" doesn't have EXECUTE permissions for CGI %s\n", _strUserName.QueryStr(), pExec->_pstrPhysicalPath->QueryStr())); SetDeniedFlags( SF_DENIED_RESOURCE ); return FALSE; }
if ( !pExec->_pstrPathInfo->Unescape() ) { return FALSE; }
//
// If this isn't a trusted app, make sure the user has execute on
// this virtual root
//
if ( !(fTrusted && IS_ACCESS_ALLOWED2(pExec, SCRIPT)) && !IS_ACCESS_ALLOWED2(pExec, EXECUTE) ) { *pfHandled = TRUE; if ( fChild ) { SetLastError( ERROR_INVALID_FLAGS ); return FALSE; } else { DBGPRINTF(( DBG_CONTEXT, "ACCESS_DENIED: No EXECUTE permissions on Vroot \"%s\" for CGI %s\n", pExec->_pMetaData->QueryVrPath()->QueryStr(), pExec->_pstrPhysicalPath->QueryStr())); Disconnect( HT_FORBIDDEN, IDS_EXECUTE_ACCESS_DENIED, FALSE, pfFinished ); return TRUE; } }
//
// We need to calculate the number of characters that comprise the
// working directory for the script path (which is the web root)
//
if ( !LookupVirtualRoot( pExec->_pstrPhysicalPath, pExec->_pstrURL->QueryStr(), pExec->_pstrURL->QueryCCH(), NULL, NULL, &dwMask )) { return FALSE; }
LPSTR pszWorkingDir = strrchr( pExec->_pstrPhysicalPath->QueryStr(), '\\' ); if ( pszWorkingDir == NULL ) { return FALSE; } if ( !strWorkingDir.Copy( pExec->_pstrPhysicalPath->QueryStr(), DIFF( pszWorkingDir - pExec->_pstrPhysicalPath->QueryStr() ) + 1)) { return FALSE; }
//
// Keep-alive not supported for CGI
//
if ( !fChild ) { SetKeepConn( FALSE ); }
//
// If this was a mapped script extension expand any parameters
//
if ( !pExec->_pstrGatewayImage->IsEmpty() ) { CHAR tmpStr[MAX_PATH]; CHAR tmpStr2[1024];
STR strDecodedParams(tmpStr,MAX_PATH); STR strCmdLine(tmpStr2,1024);
if ( !SetupCmdLine( &strDecodedParams, *(pExec->_pstrURLParams) ) || !strCmdLine.Resize( pExec->_pstrGatewayImage->QueryCB() + pExec->_pstrPhysicalPath->QueryCB() + strDecodedParams.QueryCB())) { return FALSE; }
if ( IsCmdExe( pExec->_pstrGatewayImage->QueryStr() )) { //
// Make sure the path to the file exists if we're running
// the command interpreter
//
if ( !pExec->ImpersonateUser() ) { return FALSE; }
dwAttr = GetFileAttributes( pExec->_pstrPhysicalPath->QueryStr() ); err = GetLastError();
pExec->RevertUser();
if ( dwAttr == 0xffffffff ) { DBGPRINTF(( DBG_CONTEXT, "[ProcessGateway] Error %d openning batch file %s\n", err, pExec->_pstrPhysicalPath->QueryStr() ) );
if ( !fChild && ( (err == ERROR_FILE_NOT_FOUND) || (err == ERROR_PATH_NOT_FOUND) || (err == ERROR_INVALID_NAME) ) ) { SetState( HTR_DONE, HT_NOT_FOUND, GetLastError() ); Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished ); *pfHandled = TRUE; return TRUE; }
return FALSE; } }
cStr = wsprintf( strCmdLine.QueryStr(), pExec->_pstrGatewayImage->QueryStr(), pExec->_pstrPhysicalPath->QueryStr(), strDecodedParams.QueryStr() );
strCmdLine.SetLen( cStr );
fRet = ProcessCGI( pExec, NULL, &strWorkingDir, pfHandled, pfFinished, &strCmdLine ); } else { fRet = ProcessCGI( pExec, pExec->_pstrPhysicalPath, &strWorkingDir, pfHandled, pfFinished ); }
return fRet; }
/********************************************************************/
BOOL HTTP_REQUEST::ProcessCGI( EXEC_DESCRIPTOR * pExec, const STR * pstrPath, const STR * pstrWorkingDir, BOOL * pfHandled, BOOL * pfFinished, STR * pstrCmdLine ) /*++
Routine Description:
Processes a CGI client request
Arguments:
pExec - Execution descriptor block pstrPath - Fully qualified path to executable (or NULL if the module is contained in pstrCmdLine) strWorkingDir - Working directory for spawned process (generally the web root) pfHandled - Set to TRUE if no further processing is needed pfFinished - Set to TRUE if no further I/O operation for this request pstrCmdLine - Optional command line to use instead of the default
Return Value:
TRUE if successful, FALSE on error
--*/ { STARTUPINFO startupinfo; PROCESS_INFORMATION processinfo; UCHAR tmpBuffer[1500]; BUFFER buffEnv(tmpBuffer,1500); BOOL fRet = FALSE; CGI_INFO * pCGIInfo = NULL; DWORD dwThreadId; HANDLE hThread; CHAR tmpStr[MAX_PATH]; STR strCmdLine(tmpStr,MAX_PATH); DWORD dwFlags = DETACHED_PROCESS; BOOL fChild = pExec->IsChild(); BOOL fIsCmdExe;
*pfHandled = TRUE;
//
// Note we move the module name to the command line so argv
// comes out correctly
//
if ( pstrPath != NULL ) { if ( !strCmdLine.Copy( "\"", sizeof("\"")-1 ) || !strCmdLine.Append( pstrPath->QueryStr() ) || !strCmdLine.Append( "\" ", sizeof("\" ")-1 )) { return FALSE; } }
if ( !SetupChildEnv( pExec, &buffEnv ) || !SetupCmdLine( &strCmdLine, *(pExec->_pstrURLParams) )) { return FALSE; }
pstrPath = NULL;
//
// If a command line wasn't supplied, then use the default command line
//
if ( !pstrCmdLine ) pstrCmdLine = &strCmdLine;
//
// Check to see if we're spawning cmd.exe, if so, refuse the request if
// there are any special shell characters. Note we do the check here
// so that the command line has been fully unescaped
//
if ( !fAllowSpecialCharsInShell || ISUNC(pstrCmdLine->QueryStr()) ) { if ( fIsCmdExe = IsCmdExe( pstrCmdLine->QueryStr() ) ) { //
// if invoking cmd.exe for a UNC script then don't set the working directory.
// otherwise cmd.exe will complains about working dir on UNC being not
// supported, which will destroy HTTP headers.
//
if ( ISUNC(pstrCmdLine->QueryStr()) ) { pstrWorkingDir = NULL; } } }
if ( !fAllowSpecialCharsInShell ) { DWORD i;
if ( fIsCmdExe ) { //
// We'll either match one of the characters or the '\0'
//
i = strcspn( pstrCmdLine->QueryStr(), "&|(,;%<>" );
if ( pstrCmdLine->QueryStr()[i] ) { DBGPRINTF(( DBG_CONTEXT, "[ProcessCGI] Refusing request for command shell due " " to special characters\n" ));
SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } } }
//
// See if CPU Throttling has this CPU stopped.
//
if (_pW3Instance->AreProcsCPUStopped() && pExec->_pMetaData->QueryJobCGIEnabled()) { SetLastError(ERROR_NOT_ENOUGH_QUOTA); fRet = FALSE; goto Exit; }
//
// Setup the pipes information
//
pCGIInfo = new CGI_INFO( this );
if ( !pCGIInfo ) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); goto Exit; }
pCGIInfo->_pExec = pExec;
ZeroMemory( &startupinfo, sizeof(startupinfo) ); startupinfo.cb = sizeof(startupinfo);
EnterCriticalSection( &CGI_INFO::_csCgiList );
InsertHeadList( &CGI_INFO::_CgiListHead, &pCGIInfo->_CgiListEntry );
LeaveCriticalSection( &CGI_INFO::_csCgiList );
//
// We specify an unnamed desktop so a new windowstation will be created
// in the context of the calling user
//
startupinfo.lpDesktop = "";
if ( !SetupChildPipes( &startupinfo, &pCGIInfo->_hStdIn, &pCGIInfo->_hStdOut ) ) { IF_DEBUG( CGI ) { DBGPRINTF((DBG_CONTEXT, "[ProcessCGI] Failed to create child pipes, error %d", GetLastError() )); }
goto Exit; }
if ( pExec->_pMetaData->QueryCreateProcessNewConsole() ) { dwFlags = CREATE_NEW_CONSOLE; }
//////////////////////////////////////////////////////////////
//
// Allow control over whether CreateProcess is called instead of
// CreateProcessAsUser. Running the services as a windows app
// works around the security problem spawning executables
//
// Note this code block is outside the impersonation block
//
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
//
// Spawn the process and close the handles since we don't need them
//
IF_DEBUG( CGI ) { DBGPRINTF(( DBG_CONTEXT, "[ProcessCGI] Creating process, path = %s, cmdline = %s\n", (pstrPath ? pstrPath->QueryStr() : "NULL"), pstrCmdLine->QueryStr() )); }
if (pExec->_pMetaData->QueryJobCGIEnabled()) { dwFlags |= CREATE_SUSPENDED; }
if ( !pExec->_pMetaData->QueryCreateProcessAsUser() || QuerySingleAccessToken() ) { if ( !pExec->ImpersonateUser() ) { goto Exit; }
fRet = CreateProcess( (pstrPath ? pstrPath->QueryStr() : NULL), pstrCmdLine->QueryStr(), NULL, // Process security
NULL, // Thread security
TRUE, // Inherit handles
dwFlags, buffEnv.QueryPtr(), pstrWorkingDir ? pstrWorkingDir->QueryStr() : NULL, &startupinfo, &processinfo );
pExec->RevertUser();
} else { HANDLE hDelete = NULL; HANDLE hToken = pExec->QueryPrimaryHandle( &hDelete );
if ( !ImpersonateLoggedOnUser( hToken ) ) { DBGPRINTF(( DBG_CONTEXT, "[ProcessCGI] ImpersonateLoggedOnUser failed, error %lx\n", GetLastError() ));
if ( hDelete ) { TCP_REQUIRE( CloseHandle( hDelete )); }
goto Exit; }
fRet = CreateProcessAsUser( hToken, (pstrPath ? pstrPath->QueryStr() : NULL), pstrCmdLine->QueryStr(), NULL, // Process security
NULL, // Thread security
TRUE, // Inherit handles
dwFlags, buffEnv.QueryPtr(), pstrWorkingDir ? pstrWorkingDir->QueryStr() : NULL, &startupinfo, &processinfo );
TCP_REQUIRE( RevertToSelf() );
if ( hDelete ) { TCP_REQUIRE( CloseHandle( hDelete ) ); } }
if ((fRet) && (pExec->_pMetaData->QueryJobCGIEnabled())) {
DBG_ASSERT(_pW3Instance != NULL); DBG_REQUIRE(_pW3Instance->AddProcessToJob(processinfo.hProcess, FALSE) == ERROR_SUCCESS); if (ResumeThread(processinfo.hThread) == 0xFFFFFFFF) { fRet = FALSE; TerminateProcess(processinfo.hThread, GetLastError()); TCP_REQUIRE( CloseHandle( processinfo.hProcess )); } }
TCP_REQUIRE( CloseHandle( startupinfo.hStdOutput )); TCP_REQUIRE( CloseHandle( startupinfo.hStdInput ));
if ( !fRet ) { DBGPRINTF((DBG_CONTEXT, "[ProcessCGI] Create process failed, error %d, exe = %s, cmd line = %s\n", GetLastError(), (pstrPath ? pstrPath->QueryStr() : "null"), (pstrCmdLine ? pstrCmdLine->QueryStr() : "null") ));
goto Exit; }
QueryW3StatsObj()->IncrTotalCGIRequests();
TCP_REQUIRE( CloseHandle( processinfo.hThread )); DBG_ASSERT( startupinfo.hStdError == startupinfo.hStdOutput);
//
// Save the process handle in case we need to terminate it later on
//
pCGIInfo->_hProcess = processinfo.hProcess;
//
// Before we start the CGI thread, set our new state
//
SetState( HTR_CGI );
if ( QueryW3Instance()->IsUsePoolThreadForCGI() ) { BOOL fIncPoolThread = FALSE;
//
// To maintain IIS 3.0 compatible behavior, allow 10 CGI threads
// on a single proc machine, since IIS 3.0 by default had 10 ATQ
// threads per processor.
// Note that on multi-proc machines, you will end up with more
// than 10 threads (specifically, you get a boost in the max thread
// count of IIS3_MIN_CGI - ATQ_REG_DEF_PER_PROCESSOR_ATQ_THREADS).
//
if ( InterlockedIncrement( &g_cMinCGIs ) <= ( IIS3_MIN_CGI - ATQ_REG_DEF_PER_PROCESSOR_ATQ_THREADS ) ) { fIncPoolThread = TRUE; AtqSetInfo( AtqIncMaxPoolThreads, 0 ); }
//
// Call the CGI processor directly.
//
pCGIInfo->_fServerPoolThread = TRUE;
CGIThread( pCGIInfo );
if ( fIncPoolThread ) { AtqSetInfo( AtqDecMaxPoolThreads, 0 ); }
InterlockedDecrement( &g_cMinCGIs ); } else {
//
// Create a thread to handle IO with the child process
//
// Child execs don't need referencing since they are executed
// synchronously
if ( !fChild ) { Reference(); }
if ( !(hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) CGIThread, pCGIInfo, 0, &dwThreadId ))) { if ( !fChild ) { Dereference(); } goto Exit; }
//
// If this is a child CGI, we must wait for completion.
// Therefore, wait indefinitely on the CGIThread
//
if ( fChild ) { TCP_REQUIRE( WaitForSingleObject( hThread, INFINITE ) ); }
//
// We don't use the thread handle so free the resource
//
TCP_REQUIRE( CloseHandle( hThread )); }
fRet = TRUE;
Exit: if ( !fRet ) { DWORD err = GetLastError();
if (pCGIInfo != NULL) { EnterCriticalSection( &CGI_INFO::_csCgiList ); RemoveEntryList( &pCGIInfo->_CgiListEntry ); LeaveCriticalSection( &CGI_INFO::_csCgiList );
delete pCGIInfo; }
if ( !fChild ) { if ( err == ERROR_ACCESS_DENIED ) { SetDeniedFlags( SF_DENIED_RESOURCE ); } else if ( err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND ) { SetState( HTR_DONE, HT_NOT_FOUND, err ); Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished );
fRet = TRUE; } else if ( err == ERROR_MORE_DATA || ( err == ERROR_INVALID_PARAMETER && pstrCmdLine->QueryCB() > CGI_COMMAND_LINE_MAXCB ) ) { // These errors are returned by CreateProcess[AsUser] if
// the query string is too long
SetState( HTR_DONE, HT_URL_TOO_LONG, err ); Disconnect( HT_URL_TOO_LONG, NO_ERROR, FALSE, pfFinished );
fRet = TRUE; } else if (err == ERROR_NOT_ENOUGH_QUOTA) {
SetState( HTR_DONE, HT_SVC_UNAVAILABLE, ERROR_NOT_ENOUGH_QUOTA ); Disconnect( HT_SVC_UNAVAILABLE, IDS_SITE_RESOURCE_BLOCKED, FALSE, pfFinished );
fRet = TRUE; } } }
return fRet; }
/*******************************************************************
NAME: SetupChildPipes
SYNOPSIS: Creates/duplicates pipes for redirecting stdin and stdout to a child process
ENTRY: pstartupinfo - pointer to startup info structure, receives child stdin and stdout handles phParentIn - Pipe to use for parent reading phParenOut - Pipe to use for parent writing
RETURNS: TRUE if successful, FALSE on failure
HISTORY: Johnl 22-Sep-1994 Created
********************************************************************/
BOOL SetupChildPipes( STARTUPINFO * pstartupinfo, HANDLE * phParentIn, HANDLE * phParentOut )
{ SECURITY_ATTRIBUTES sa;
*phParentIn = NULL; *phParentOut = NULL;
sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE;
pstartupinfo->dwFlags = STARTF_USESTDHANDLES;
//
// Create the pipes then mark them as not inheritted in the
// DuplicateHandle to prevent handle leaks
//
if ( !CreatePipe( phParentIn, &pstartupinfo->hStdOutput, &sa, 0 ) || !DuplicateHandle( GetCurrentProcess(), *phParentIn, GetCurrentProcess(), phParentIn, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE) || !CreatePipe( &pstartupinfo->hStdInput, phParentOut, &sa, 0 ) || !DuplicateHandle( GetCurrentProcess(), *phParentOut, GetCurrentProcess(), phParentOut, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE )) { goto ErrorExit; }
//
// Stdout and Stderror will use the same pipe. If clients tend
// to close stderr, then we'll have to duplicate the handle
//
pstartupinfo->hStdError = pstartupinfo->hStdOutput;
IF_DEBUG ( CGI ) { DBGPRINTF((DBG_CONTEXT, "[SetupChildPipes] Parent In = %x, Parent Out = %x, Child In = %x, Child Out = %x\n", *phParentIn, *phParentOut, pstartupinfo->hStdInput, pstartupinfo->hStdOutput));
}
return TRUE;
ErrorExit: IF_DEBUG( CGI ) { DBGPRINTF((DBG_CONTEXT, "[SetupChildPipes] Failed with error %d\n", GetLastError())); }
if ( *phParentIn ) { TCP_REQUIRE( CloseHandle( *phParentIn )); *phParentIn = NULL; }
if ( *phParentOut ) { TCP_REQUIRE( CloseHandle( *phParentOut )); *phParentOut = NULL; }
return FALSE;
}
/*******************************************************************
NAME: SetupChildEnv
SYNOPSIS: Based on the passed pRequest, builds a CGI environment block
ENTRY: pExec - Execution Descriptor Block pBuff - Buffer to receive environment block
RETURNS: TRUE if successful, FALSE on failure
HISTORY: Johnl 22-Sep-1994 Created
********************************************************************/
BOOL SetupChildEnv( EXEC_DESCRIPTOR * pExec, BUFFER * pBuff ) { TCHAR * pch; TCHAR * pchtmp; CHAR tmpStr[1024]; STR strVal(tmpStr,1024); UINT cchCurrentPos = 0; // Points to '\0' in buffer
UINT cchName, cchValue; UINT cbNeeded; BOOL fChild = pExec->IsChild(); HTTP_REQUEST * pRequest = pExec->_pRequest; int i = 0;
//
// Build the environment block for CGI
//
while ( g_pEnvEntries[i].m_pszName != NULL ) { //
// Check if this is a copy entry from process environment
//
if ( g_pEnvEntries[i].m_cchToCopy ) { if ( !pBuff->Resize( (cchCurrentPos + g_pEnvEntries[i].m_cchToCopy) * sizeof(TCHAR) ) ) { return FALSE; }
pch = (TCHAR *) pBuff->QueryPtr();
memcpy( pch + cchCurrentPos, g_pEnvEntries[i].m_pszName, g_pEnvEntries[i].m_cchToCopy );
cchCurrentPos += g_pEnvEntries[i].m_cchToCopy;
++i; continue; }
//
// The NULL string means we're adding all of
// the HTTP header fields which requires a little
// bit of special processing
//
if ( !*g_pEnvEntries[i].m_pszName ) { pch = "ALL_HTTP"; } else { pch = g_pEnvEntries[i].m_pszName; }
if ( !strcmp( pch, "PATH_INFO" ) ) { if ( !strVal.Copy( pExec->_pstrPathInfo->QueryStr() ) ) { return FALSE; } } else if ( !strcmp( pch, "PATH_TRANSLATED" ) ) { if ( !pRequest->LookupVirtualRoot( &strVal, pExec->_pstrPathInfo->QueryStr(), pExec->_pstrPathInfo->QueryCCH(), NULL, NULL, NULL, NULL, FALSE, pExec->_pstrPathInfo->QueryCCH() ? &pExec->_pPathInfoMetaData : NULL, pExec->_pstrPathInfo->QueryCCH() ? &pExec->_pPathInfoURIBlob : NULL ) ) { return FALSE; } } else if ( fChild && !strcmp( pch, "QUERY_STRING" ) ) { if ( !strVal.Copy( pExec->_pstrURLParams->QueryStr() ) ) { return FALSE; } } else { if ( !pRequest->GetInfo( pch, &strVal ) ) { return FALSE; } }
cchName = _tcslen( g_pEnvEntries[i].m_pszName ); cchValue = strVal.QueryCCH();
//
// We need space for the terminating '\0' and the '='
//
cbNeeded = ( cchName + cchValue + 1 + 1) * sizeof(TCHAR);
if ( !pBuff->Resize( cchCurrentPos * sizeof(TCHAR) + cbNeeded, 512 )) { return FALSE; }
//
// Replace the '\n' with a '\0' as needed
// for the HTTP headers
//
if ( !*g_pEnvEntries[i].m_pszName ) { pchtmp = strVal.QueryStr();
//
// Convert the first ':' of each header to to an '=' for the
// environment table
//
while ( pchtmp = strchr( pchtmp, ':' )) { *pchtmp = '=';
if ( !(pchtmp = strchr( pchtmp, '\n' ))) { break; } }
pchtmp = strVal.QueryStr();
while ( pchtmp = strchr( pchtmp+1, '\n' )) { *pchtmp = '\0'; } }
pch = (TCHAR *) pBuff->QueryPtr();
if ( *g_pEnvEntries[i].m_pszName ) { if ( strVal.QueryStr()[0] ) { memcpy( pch + cchCurrentPos, g_pEnvEntries[i].m_pszName, cchName * sizeof(TCHAR));
*(pch + cchCurrentPos + cchName) = '=';
memcpy( pch + cchCurrentPos + cchName + 1, strVal.QueryStr(), (cchValue + 1) * sizeof(TCHAR));
cchCurrentPos += cchName + cchValue + 1 + 1; } } else { CopyMemory( pch + cchCurrentPos + cchName, strVal.QueryStr(), (cchValue + 1) * sizeof(TCHAR));
cchCurrentPos += cchName + cchValue; }
i++; }
//
// Add a '\0' terminator to the environment list
//
if ( !pBuff->Resize( (cchCurrentPos + 1) * sizeof(TCHAR))) { return FALSE; }
*((TCHAR *) pBuff->QueryPtr() + cchCurrentPos) = TEXT('\0');
return TRUE; }
/*******************************************************************
NAME: SetupCmdLine
SYNOPSIS: Sets up a CGI command line
ENTRY: pstrCmdLine - Receives command line strParams - Parameters following "?" in URL
HISTORY: Johnl 04-Oct-1994 Created
********************************************************************/
BOOL SetupCmdLine( STR * pstrCmdLine, const STR & strParams ) { TCHAR * pch;
//
// If an unencoded "=" is found, don't use the command line
// (some weird CGI rule)
//
if ( _tcschr( strParams.QueryStr(), TEXT('=') )) { return TRUE; }
STACK_STR (strDecodedParams, 256);
//
// Replace "+" with spaces and decode any hex escapes
//
if ( !strDecodedParams.Copy( strParams ) ) { return FALSE; }
while ( pch = _tcschr( strDecodedParams.QueryStr(), TEXT('+') )) { *pch = TEXT(' '); pch++; }
if (!strDecodedParams.Unescape() || !pstrCmdLine->Append(strDecodedParams)) { return FALSE; }
return TRUE;
} // SetupCmdLine
/*******************************************************************
NAME: CGIThread
SYNOPSIS: Sends any gateway data to the scripts stdin and forwards the script's stdout to the client's socket
ENTRY: Param - Pointer to CGI_INFO structure
HISTORY: Johnl 22-Sep-1994 Created
********************************************************************/
DWORD CGIThread( PVOID Param ) { CGI_INFO * pCGIInfo = (CGI_INFO *) Param; HTTP_REQUEST * pRequest = pCGIInfo->_pRequest; EXEC_DESCRIPTOR * pExec = pCGIInfo->_pExec; BYTE buff[2048]; DWORD cbWritten; DWORD cbRead; DWORD cbSent; DWORD dwExitCode; DWORD err; BOOL fReadHeaders = FALSE; BOOL fChild = pExec->IsChild(); BOOL fNoHeaders = pExec->NoHeaders(); BOOL fRedirectOnly = pExec->RedirectOnly(); BOOL fDone = FALSE; BOOL fSkipDisconnect; BOOL fRet = TRUE; DWORD dwHttpStatus = HT_OK; DWORD msScriptTimeout = pRequest->QueryMetaData()->QueryScriptTimeout() * 1000; STACK_STR(strTemp, 128); STACK_STR(strResponse, 512); CHAR ach[17]; CHAR *pszCRLF; DWORD dwCRLFSize; BOOL fExitCodeProcess; DWORD dwCookie = 0;
IF_DEBUG( CGI ) { DBGPRINTF((DBG_CONTEXT, "[CGIThread] Entered, hstdin %x, hstdout = %x\n", pCGIInfo->_hStdIn, pCGIInfo->_hStdOut)); }
pRequest->SetLogStatus( HT_OK, NO_ERROR );
//
// Update the statistics counters
//
pRequest->QueryW3StatsObj()->IncrCGIRequests();
//
// Schedule a callback to kill the process if he doesn't die
// in a timely manner
//
pCGIInfo->_dwSchedCookie = ScheduleWorkItem( CGITerminateProcess, pCGIInfo->_hProcess, msScriptTimeout );
if ( !pCGIInfo->_dwSchedCookie ) { DBGPRINTF(( DBG_CONTEXT, "[CGI_THREAD] ScheduleWorkItem failed, error %d\n", GetLastError() )); }
//
// First we have to write any additional data to the program's stdin
//
if ( pRequest->QueryEntityBodyCB() ) { DWORD cbNextRead; DWORD cbLeft = 0;
IF_DEBUG( CGI ) { DBGPRINTF((DBG_CONTEXT, "[CGIThread] Writing %d bytes to child's stdin\n", pRequest->QueryEntityBodyCB())); }
if ( !::WriteFile( pCGIInfo->_hStdOut, pRequest->QueryEntityBody(), pRequest->QueryEntityBodyCB(), &cbWritten, NULL )) { DBGPRINTF((DBG_CONTEXT, "[CGI_THREAD] WriteFile failed, error %d\n", GetLastError())); }
if ( cbWritten != pRequest->QueryEntityBodyCB() ) { DBGPRINTF((DBG_CONTEXT, "[CGI_THREAD] %d bytes written of %d bytes\n", cbWritten, pRequest->QueryEntityBodyCB())); }
//
// Now stream any unread data to the CGI application but
// watch out for the case where we get more data then is
// indicated by the Content-Length
//
if ( pRequest->QueryEntityBodyCB() < pRequest->QueryClientContentLength()) { cbLeft = pRequest->QueryClientContentLength() - pRequest->QueryEntityBodyCB(); }
while ( cbLeft ) {
cbNextRead = min( cbLeft, pRequest->QueryClientReqBuff()->QuerySize() );
if ( !pRequest->ReadFile( pRequest->QueryClientRequest(), cbNextRead, &cbRead, IO_FLAG_SYNC ) || !cbRead ) { DBGPRINTF(( DBG_CONTEXT, "[CGI_THREAD] Error reading gateway data (%d)\n", GetLastError() )); fRet = FALSE; break; }
cbLeft -= cbRead; pRequest->AddTotalEntityBodyCB( cbRead );
if ( !::WriteFile( pCGIInfo->_hStdOut, pRequest->QueryClientRequest(), cbRead, &cbWritten, NULL )) { fRet = FALSE; break; } } }
if ( !fRet ) { //
// If an error occurred during the client read or write, we let the CGI
// application continue
//
DBGPRINTF((DBG_CONTEXT, "[CGI_THREAD] Gateway ReadFile or CGI WriteFile failed, error %d\n", GetLastError())); }
//
// Now wait for any data the child sends to its stdout or for the
// process to exit
//
//
// Handle input from child
//
while (TRUE) {
fRet = ::ReadFile( pCGIInfo->_hStdIn, buff, sizeof(buff), &cbRead, NULL );
if ( !fRet ) { err = GetLastError();
if ( err == ERROR_BROKEN_PIPE ) { break; }
IF_DEBUG( CGI ) { DBGPRINTF((DBG_CONTEXT, "[CGI_THREAD] ReadFile from child stdout failed, error %d, _hStdIn = %x\n", GetLastError(), pCGIInfo->_hStdIn)); }
pRequest->SetLogStatus( 500, err );
break; }
IF_DEBUG( CGI ) { DBGPRINTF((DBG_CONTEXT, "[CGI_THREAD] ReadFile read %d bytes\n", cbRead)); }
//
// If no bytes were read, assume the file has been closed so
// get out
//
if ( !cbRead ) { break; }
//
// The CGI script can specify headers to include in the
// response. Wait till we receive all of the headers.
//
if ( !fReadHeaders && !pExec->IsNPH() ) { if ( !fNoHeaders) { if ( !ProcessCGIInput( pCGIInfo, buff, cbRead, &fReadHeaders, &fDone, &fSkipDisconnect, &dwHttpStatus )) { DBGPRINTF((DBG_CONTEXT, "[CGIThread] ProcessCGIInput failed with error %d\n", GetLastError()));
if ( fChild ) { goto SkipDisconnect; } else { goto Disconnect; } } if ( fSkipDisconnect ) { goto SkipDisconnect; }
if ( fDone ) { if ( fChild ) { goto SkipDisconnect; } else { goto Disconnect; } } } else { BYTE * pbExtraData; DWORD cbRemainingData; pbExtraData = ScanForTerminator( (CHAR*) buff ); if ( pbExtraData != NULL ) { fReadHeaders = TRUE; cbRemainingData = cbRead - DIFF( pbExtraData - buff ); if ( HTV_HEAD != pRequest->QueryVerb() && !pRequest->WriteFile( pbExtraData, cbRemainingData, &cbSent, IO_FLAG_SYNC ) ) { pRequest->SetLogStatus( HT_SERVER_ERROR, GetLastError() ); break; } } } //
// Either we are waiting for the rest of the header or
// we've sent the header and any residual data so wait
// for more data
//
continue; }
if ( (HTV_HEAD != pRequest->QueryVerb()) && !pRequest->WriteFile( buff, cbRead, &cbSent, IO_FLAG_SYNC )) { DBGPRINTF((DBG_CONTEXT, "[CGI_THREAD] WriteFile to socket failed, error %d\n", GetLastError()));
pRequest->SetLogStatus( 500, GetLastError() ); break; } }
//
// Remove the scheduled timeout callback
//
dwCookie = (DWORD) InterlockedExchange( (LPLONG) &(pCGIInfo->_dwSchedCookie), 0 ); if ( dwCookie != 0 ) { if ( !RemoveWorkItem( dwCookie ) ) { DBGPRINTF(( DBG_CONTEXT, "[CGI_THREAD] Failed to remove scheduled item\n" )); } }
//
// If we had to kill the process, or the Job Object killed the process,
// log an event
//
fExitCodeProcess = GetExitCodeProcess( pCGIInfo->_hProcess, &dwExitCode );
if ( (fExitCodeProcess) && (( dwExitCode == CGI_PREMATURE_DEATH_CODE ) || ( dwExitCode == ERROR_NOT_ENOUGH_QUOTA ))) { const CHAR * apsz[2];
//
// Log an event and terminate the process
//
DBGPRINTF((DBG_CONTEXT, "[CGI_THREAD] - CGI Script %s, params %s was killed\n", pExec->_pstrURL->QueryStr(), pExec->_pstrURLParams->QueryStr()));
apsz[0] = pExec->_pstrURL->QueryStr(); apsz[1] = pExec->_pstrURLParams->QueryStr();
//
// As it happens, this message is appropriate for Job Object
// CGI Timeout as well as the original timeout
//
g_pInetSvc->LogEvent( W3_EVENT_KILLING_SCRIPT, 2, apsz, 0 );
dwHttpStatus = HT_BAD_GATEWAY;
//
// If we haven't sent the headers, build up a full response, otherwise
// tack on the message to the end of the current output
//
if ( !fNoHeaders && !fRedirectOnly ) { DWORD dwTemp;
pRequest->SetLogStatus( HT_BAD_GATEWAY, ERROR_SERVICE_REQUEST_TIMEOUT );
if ( HTTP_REQ_BASE::BuildStatusLine( &strTemp, HT_BAD_GATEWAY, 0, pRequest->QueryURL(), NULL)) {
if ( pRequest->BuildBaseResponseHeader( &strResponse, &fDone, &strTemp, HTTPH_NO_CUSTOM)) { strResponse.SetLen( strlen(strResponse.QueryStr()) ); strTemp.SetLen(0);
if (pRequest->CheckCustomError(&strTemp, HT_BAD_GATEWAY, MD_ERROR_SUB502_TIMEOUT, &fDone, &dwTemp)) {
if (fDone) { goto SkipDisconnect; }
pszCRLF = "\r\n"; dwCRLFSize = CRLF_SIZE;
DBG_REQUIRE(strTemp.SetLen(strlen(strTemp.QueryStr())) );
} else {
if ( !g_pInetSvc->LoadStr( strTemp, IDS_CGI_APP_TIMEOUT )) { goto Disconnect; }
dwTemp = strTemp.QueryCB();
pszCRLF = "\r\nContent-Type: text/html\r\n\r\n"; dwCRLFSize = sizeof("\r\nContent-Type: text/html\r\n\r\n") - 1; }
_itoa( dwTemp, ach, 10 );
DBG_REQUIRE(strResponse.Append("Content-Length: ", sizeof("Content-Length: ") - 1));
DBG_REQUIRE(strResponse.Append(ach));
DBG_REQUIRE(strResponse.Append(pszCRLF, dwCRLFSize));
if (HTV_HEAD != pRequest->QueryVerb()) { DBG_REQUIRE( strResponse.Append( strTemp)); }
pRequest->SendHeader( strResponse.QueryStr(), strResponse.QueryCB(), IO_FLAG_SYNC, &fDone ); } }
} else { if ( g_pInetSvc->LoadStr( strResponse, IDS_CGI_APP_TIMEOUT )) { pRequest->WriteFile( strResponse.QueryStr(), strResponse.QueryCB(), &cbSent, IO_FLAG_SYNC ); } }
} else if ( !fReadHeaders ) { //
// If we never finished reading the headers, send a nice message
// to the client
//
if ( !fNoHeaders && !fRedirectOnly && !pExec->IsNPH() ) { DWORD dwTemp; CHAR *pszTemp;
dwHttpStatus = HT_BAD_GATEWAY; pRequest->SetLogStatus( HT_BAD_GATEWAY, ERROR_SERVICE_REQUEST_TIMEOUT );
if ( HTTP_REQ_BASE::BuildStatusLine( &strTemp, HT_BAD_GATEWAY, 0, pRequest->QueryURL(), NULL)) { CHAR ach[17]; CHAR *pszCRLF; DWORD dwCRLFSize; DWORD dwExtraSize;
if ( pRequest->BuildBaseResponseHeader( &strResponse, &fDone, &strTemp, HTTPH_NO_CUSTOM)) { DWORD dwCGIHeaderLength;
strResponse.SetLen( strlen(strResponse.QueryStr()) ); strTemp.SetLen(0);
if (pRequest->CheckCustomError(&strTemp, HT_BAD_GATEWAY, MD_ERROR_SUB502_PREMATURE_EXIT, &fDone, &dwTemp)) { if (fDone) { goto SkipDisconnect; }
pszCRLF = "\r\n"; dwCRLFSize = CRLF_SIZE;
DBG_REQUIRE(strTemp.SetLen(strlen(strTemp.QueryStr())) );
dwExtraSize = strTemp.QueryCB() - dwTemp; } else {
if ( !g_pInetSvc->LoadStr( strTemp, IDS_BAD_CGI_APP )) { goto Disconnect; }
dwTemp = strTemp.QueryCB(); dwExtraSize = 0;
pszCRLF = "\r\nContent-Type: text/html\r\n\r\n";
dwCRLFSize = sizeof("\r\nContent-Type: text/html\r\n\r\n") - 1;
}
dwCGIHeaderLength = strlen((CHAR *)pCGIInfo->_Buff.QueryPtr() );
if (strstr(strTemp.QueryStr(), "%s") != NULL) { dwTemp += dwCGIHeaderLength; dwTemp -= (sizeof("%s") - 1); }
// Truncate the buffer to 1024, since wsprintf won't do more than that.
if (dwTemp > 1024) { dwTemp = 1024; }
_itoa( dwTemp, ach, 10 );
DBG_REQUIRE(strResponse.Append("Content-Length: ", sizeof("Content-Length: ") - 1));
DBG_REQUIRE(strResponse.Append(ach));
DBG_REQUIRE(strResponse.Append(pszCRLF, dwCRLFSize));
if (HTV_HEAD != pRequest->QueryVerb()) { //
// There might be a string substitute pattern in the error
// message. Resize the buffer to handle it, and substitue
// the headers.
//
if (!strResponse.Resize(strResponse.QueryCB() + dwTemp + dwExtraSize + 1)) { goto Disconnect; }
if (!pCGIInfo->_Buff.Resize(dwCGIHeaderLength + 1)) { goto Disconnect; }
pszTemp = (CHAR *)pCGIInfo->_Buff.QueryPtr();
pszTemp[dwCGIHeaderLength] = '\0'; wsprintf(strResponse.QueryStr() + strResponse.QueryCB(), strTemp.QueryStr(), pszTemp);
DBG_REQUIRE(strResponse.SetLen(strResponse.QueryCB() + dwTemp + dwExtraSize)); } pRequest->SendHeader( strResponse.QueryStr(), strResponse.QueryCB(), IO_FLAG_SYNC, &fDone ); } } } else { //
// We don't want headers and none were found.
// Send whatever CGI output was 'as is'.
//
if ( pCGIInfo->_cbData ) { pRequest->WriteFile( pCGIInfo->_Buff.QueryPtr(), pCGIInfo->_cbData, &cbSent, IO_FLAG_SYNC ); } } }
if ( fChild ) { goto SkipDisconnect; }
Disconnect:
DBG_ASSERT( !fChild );
IF_DEBUG ( CGI ) { DBGPRINTF((DBG_CONTEXT, "[CGIThread] Exiting thread, Current State = %d, Ref = %d\n", pRequest->QueryState(), pRequest->QueryRefCount())); }
//
// Make sure this request gets logged
//
pRequest->SetState( pRequest->QueryState(), dwHttpStatus, NO_ERROR ); // Don't have a good Win32 mapping here
pRequest->WriteLogRecord();
//
// Force a shutdown here so the CGI process exit doesn't cause a
// reset on this socket
//
pRequest->Disconnect( 0, NO_ERROR, TRUE );
SkipDisconnect: // The only time the disconnect is skipped is when
// a CGI app sends a redirect
if ( !pCGIInfo->_fServerPoolThread ) { //
// Indicate that this Atq context should not be used because this thread
// is about to go away which will cause the AcceptEx IO to be cancelled
//
pRequest->QueryClientConn()->SetAtqReuseContextFlag( FALSE );
//
// Reference is only done if we've created a new thread
//
if ( !fChild ) { DereferenceConn( pRequest->QueryClientConn() ); } }
EnterCriticalSection( &CGI_INFO::_csCgiList ); RemoveEntryList( &pCGIInfo->_CgiListEntry ); LeaveCriticalSection( &CGI_INFO::_csCgiList );
delete pCGIInfo;
pRequest->QueryW3StatsObj()->DecrCurrentCGIRequests();
return 0; }
/*******************************************************************
NAME: ProcessCGIInput
SYNOPSIS: Handles headers the CGI program hands back to the server
ENTRY: pCGIInfo - Pointer to CGI structure buff - Pointer to data just read cbRead - Number of bytes read into buff pfReadHeaders - Set to TRUE after we've finished processing all of the HTTP headers the CGI script gave us pfDone - Set to TRUE to indicate no further processing is needed pfSkipDisconnect - Set to TRUE to indicate no further processing is needed and the caller should not call disconnect
HISTORY: Johnl 22-Sep-1994 Created
********************************************************************/
BOOL ProcessCGIInput( CGI_INFO * pCGIInfo, BYTE * buff, DWORD cbRead, BOOL * pfReadHeaders, BOOL * pfDone, BOOL * pfSkipDisconnect, DWORD * pdwHttpStatus ) { PCHAR pchValue; PCHAR pchField; BYTE * pbData; PCHAR pszTail; DWORD cbData; DWORD cbSent; STACK_STR( strContentType, 64 ); STACK_STR( strStatus, 32 ); STACK_STR( strCGIResp, MAX_PATH ); // Contains CGI client headers
BOOL fFoundContentType = FALSE; BOOL fFoundStatus = FALSE; HTTP_REQUEST * pRequest = pCGIInfo->_pRequest; DWORD cbNeeded, cbBaseResp; BOOL fNoHeaders = pCGIInfo->_pExec->NoHeaders(); BOOL fRedirectOnly = pCGIInfo->_pExec->RedirectOnly(); STACK_STR( strError, 80); DWORD dwContentLength; CHAR ach[17]; DWORD dwCLLength;
DBG_ASSERT( cbRead > 0 );
*pfDone = FALSE;
*pfSkipDisconnect = FALSE;
if ( !pCGIInfo->_Buff.Resize( pCGIInfo->_cbData + cbRead, 256 )) { return FALSE; }
CopyMemory( (BYTE *)pCGIInfo->_Buff.QueryPtr() + pCGIInfo->_cbData, buff, cbRead );
pCGIInfo->_cbData += cbRead;
//
// The end of CGI headers are marked by a blank line, check to see if
// we've hit that line
//
if ( !CheckForTermination( pfReadHeaders, &pCGIInfo->_Buff, pCGIInfo->_cbData, &pbData, &cbData, 256 )) { return FALSE; }
if ( !*pfReadHeaders ) { return TRUE; }
//
// We've found the end of the headers, process them
//
// if request header contains:
//
// Content-Type: xxxx - Send as the content type
// Location: xxxx - if starts with /, send doc, otherwise send redirect message
// URI: preferred synonym to Location:
// Status: nnn xxx - Send as status code (HTTP/1.0 nnn xxx)
//
// Send other request headers (server, message date, mime version)
//
CHAR pszOutputString[512]; BOOL fDidRedirect = FALSE;
INET_PARSER Parser( (CHAR *) pCGIInfo->_Buff.QueryPtr() );
while ( *(pchField = Parser.QueryToken()) ) { Parser.SkipTo( ':' ); Parser += 1; pchValue = Parser.QueryToken();
if ( !fRedirectOnly && !::_strnicmp( "Status", pchField, 6 ) ) { DWORD cbLine = strlen( Parser.QueryLine()); fFoundStatus = TRUE;
*pdwHttpStatus = atoi( pchValue );
if ( !strStatus.Resize( LEN_PSZ_HTTP_VERSION_STR + cbLine + 4) || !strStatus.Copy( !g_ReplyWith11 ? PSZ_HTTP_VERSION_STR : PSZ_HTTP_VERSION_STR11, LEN_PSZ_HTTP_VERSION_STR ) || !strStatus.Append( Parser.QueryLine(), cbLine ) ) { return FALSE; }
// I am safe to assume space is there, because of resize
DBG_ASSERT( strStatus.QueryCB() < (strStatus.QuerySize() - 2)); strStatus.AppendCRLF(); } else if ( !_strnicmp( "Location", pchField, 8 ) || !_strnicmp( "URI", pchField, 3 )) {
//
// The CGI script is redirecting us to another URL.
// If it begins with a '/', then send it, otherwise
// send a redirect message
//
if ( *pchValue == TEXT('/') && !fRedirectOnly ) { if ( !pRequest->ReprocessURL( pchValue, HTV_GET )) { return FALSE; }
*pfSkipDisconnect = TRUE;
*pfDone = TRUE; return TRUE; }
DWORD cbLen; CHAR pszMessageString[256];
cbLen = LoadString( GetModuleHandle( W3_MODULE_NAME ), IDS_URL_MOVED, pszMessageString, 256 ); if ( !cbLen ) { return FALSE; }
wsprintf( pszOutputString, pszMessageString, pchValue );
if ( fRedirectOnly ) { if ( !pRequest->WriteFile( pszOutputString, strlen(pszOutputString), &cbLen, IO_FLAG_SYNC ) ) { return FALSE; } } else if ( !strCGIResp.Append( "Location: ", 10 ) || !strCGIResp.Append( pchValue ) || !strCGIResp.Append( "\r\n", 2 ) || !strStatus.Copy( !g_ReplyWith11 ? PSZ_HTTP_VERSION_STR : PSZ_HTTP_VERSION_STR11, LEN_PSZ_HTTP_VERSION_STR ) || !strStatus.Append( "302 Object Moved\r\n", 18 ) ) { return FALSE; }
fDidRedirect = TRUE; } else if ( !fRedirectOnly ) { //
// Copy any other fields the script specified
//
Parser.QueryLine();
if ( !::_strnicmp( "Content-Type", pchField, 12 )) { fFoundContentType = TRUE;
if ( !strContentType.Append( pchField ) || !strContentType.Append( "\r\n", 2 )) { return FALSE; } } else { //
// Terminate line
//
if ( !strCGIResp.Append( pchField ) || !strCGIResp.Append( "\r\n", 2 )) { return FALSE; } } }
Parser.NextLine(); }
//
// If we're ignoring all but redirects, then simply sent the data
// past the headers and we're done.
//
if ( fRedirectOnly ) { goto SendRemainder; }
//
// If the CGI script didn't specify a content type, then use
// the default
//
if ( fDidRedirect ) { if ( !strContentType.Copy( "Content-Type: text/html\r\n", 25 ) ) { return FALSE; } } else if ( !fFoundContentType ) { STR str;
// NYI: SelectMimeMapping will yield a string with allocation
// this is a temp string - try to avoid allocs
if ( !strContentType.Append( PSZ_KWD_CONTENT_TYPE, LEN_PSZ_KWD_CONTENT_TYPE )|| !SelectMimeMapping( &str, NULL, pCGIInfo->_pExec->_pMetaData )|| !strContentType.Append( str ) || !strContentType.Append( "\r\n", 2 )) { return FALSE; } }
//
// Combine the CGI specified headers with the regular headers
// the server would send (message date, server ver. etc)
//
if ( !*pdwHttpStatus && !fDidRedirect ) { *pdwHttpStatus = HT_OK; } else { if ( *pdwHttpStatus == HT_DENIED ) { pRequest->SetDeniedFlags( SF_DENIED_APPLICATION ); pRequest->SetAuthenticationRequested( TRUE ); } }
if ( !pRequest->BuildBaseResponseHeader( pRequest->QueryRespBuf(), pfDone, (fFoundStatus || fDidRedirect) ? &strStatus : NULL, (*pdwHttpStatus == HT_OK) ? 0 : HTTPH_NO_CUSTOM )) { return FALSE; }
if ( *pfDone ) { return TRUE; }
if ( *pdwHttpStatus != HT_OK && !fDidRedirect ) { DWORD dwSubStatus; BOOL fErrorDone;
// Some sort of error status, check for a custom error.
fErrorDone = FALSE;
dwSubStatus = pRequest->IsAuthenticationRequested() ? MD_ERROR_SUB401_APPLICATION : 0;
if (pRequest->CheckCustomError(&strError, *pdwHttpStatus, dwSubStatus, &fErrorDone, &dwContentLength, dwSubStatus == 0 ? TRUE : FALSE)) {
// Had some sort of a custom error. If it's being completely
// handled, bail out now.
if (fErrorDone) { *pfSkipDisconnect = TRUE; return TRUE; }
// We had a custom error, but it wasn't completely handled. This
// overrides a content-type and any content sent by the CGI script
// itself.
strError.SetLen(strlen(strError.QueryStr()));
cbData = 0;
} }
cbBaseResp = pRequest->QueryRespBufCB();
if (strError.QueryCB() != 0) { _itoa( dwContentLength, ach, 10 );
dwCLLength = strlen(ach);
cbNeeded = cbBaseResp + strError.QueryCB() + sizeof("Content-Length: \r\n") - 1 + dwCLLength + 1; // For trailing NULL.
if ( !pRequest->QueryRespBuf()->Resize( cbNeeded )) { return FALSE; }
pszTail = pRequest->QueryRespBufPtr() + cbBaseResp;
memcpy(pszTail, "Content-Length: ", sizeof("Content-Length: ") - 1); pszTail += sizeof("Content-Length: ") - 1; memcpy(pszTail, ach, dwCLLength); pszTail += dwCLLength; memcpy(pszTail, "\r\n", CRLF_SIZE); pszTail += CRLF_SIZE;
memcpy(pszTail, strError.QueryStr(), strError.QueryCB() + 1);
pszTail += strError.QueryCB();
} else {
cbNeeded = cbBaseResp + strContentType.QueryCB() + strCGIResp.QueryCB() + sizeof( "\r\n" ); // Include the '\0' in the count
if ( fDidRedirect ) { cbNeeded += strlen(pszOutputString); }
if ( !pRequest->QueryRespBuf()->Resize( cbNeeded )) { return FALSE; }
pszTail = pRequest->QueryRespBufPtr() + cbBaseResp;
memcpy( pszTail, strContentType.QueryStr(), strContentType.QueryCB() ); pszTail += strContentType.QueryCB();
memcpy(pszTail, strCGIResp.QueryStr(), strCGIResp.QueryCB()); pszTail += strCGIResp.QueryCB();
memcpy(pszTail, "\r\n", CRLF_SIZE+1); pszTail += CRLF_SIZE;
if ( fDidRedirect ) { memcpy(pszTail, pszOutputString, strlen(pszOutputString)); pszTail += strlen(pszOutputString); } }
if ( !pRequest->SendHeader( pRequest->QueryRespBufPtr(), DIFF(pszTail - pRequest->QueryRespBufPtr()), IO_FLAG_SYNC, pfDone )) { return FALSE; }
//
// If we had a custom error message, make sure we set the done flag now.
//
if (strError.QueryCB() != 0) { *pfDone = TRUE; }
if ( fDidRedirect ) { *pfDone = TRUE; return TRUE; }
//
// If there was additional data in the buffer, send that out now
//
SendRemainder:
if ( cbData ) { if ( !pRequest->WriteFile( pbData, cbData, &cbSent, IO_FLAG_SYNC )) { return FALSE; } }
return TRUE; }
VOID WINAPI CGITerminateProcess( PVOID pContext ) /*++
Routine Description:
This function is the callback called by the scheduler thread after the specified timeout period has elapsed.
Arguments:
pContext - Handle of process to kill
--*/ { IF_DEBUG( CGI ) { DBGPRINTF(( DBG_CONTEXT, "[CGITerminateProcess] - Terminating process handle %x\n", pContext )); }
if ( !TerminateProcess( (HANDLE) pContext, CGI_PREMATURE_DEATH_CODE )) { DBGPRINTF((DBG_CONTEXT, "[CGITerminateProcess] - TerminateProcess returned %d\n", GetLastError())); }
} // CGITerminateProcess
BOOL IsCmdExe( const CHAR * pchPath ) { while ( *pchPath ) { if ( (*pchPath == 'c') || (*pchPath == 'C') ) { if ( !_strnicmp(pchPath,"cmd.exe",sizeof("cmd.exe") - 1) || !_strnicmp(pchPath,"command.com",sizeof("command.com") - 1) ) { return TRUE; } }
pchPath++; }
return FALSE;
} // IsCmdExe
|