Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

2369 lines
63 KiB

/**********************************************************************/
/** 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 "tssched.hxx"
//
// Private constants.
//
//
// The name of the WAIS lookup tool
//
#define WAIS_CMD "waislook"
//
// This is the command line that spawns the WAIS lookup engine. The
// parameters are:
//
// Full path to database name w/o extension
// Host name (or IP address)
// Port number
// URL path to database (used for building WAIS http document, not
// supported in all version of waislook)
// Decoded parameters from URL query parameters
//
#define WAIS_CMD_ARGS "%s -d %s -h %s -p %s -t \"Search Result\" -http %s"
//
// This is the exit code given to processes that we terminate
//
#define CGI_PREMATURE_DEATH_CODE 0xf1256323
//
// Prototypes
//
BOOL
IsCmdExe(
const CHAR * pchPath
);
//
// Private globals.
//
BOOL fCGIInitialized = FALSE;
//
// 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("ComSpec"),TRUE},
{TEXT("CONTENT_LENGTH"),FALSE},
{TEXT("CONTENT_TYPE"),FALSE},
{TEXT("GATEWAY_INTERFACE"),FALSE},
{TEXT(""),FALSE}, // Means insert all HTTP_ headers here
{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}
};
//
// Allow control over whether CreateProcess or CreateProcessAsUser is called
//
BOOL fCreateProcessAsUser = TRUE;
BOOL fCreateProcessWithNewConsole = FALSE;
BOOL fForwardServerEnvironmentBlock = TRUE;
//
// Store environment block for IIS process
//
LPSTR g_pszIisEnv = NULL;
CgiEnvTableEntry *g_pEnvEntries = NULL;
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 ),
_fNoProcessHeaders( FALSE ),
_fWAISLookup ( FALSE ),
_hStdOut ( INVALID_HANDLE_VALUE ),
_hStdIn ( INVALID_HANDLE_VALUE ),
_hProcess ( INVALID_HANDLE_VALUE ),
_dwSchedCookie ( 0 ),
_fServerPoolThread( FALSE )
{
}
~CGI_INFO( VOID )
{
if ( _hStdOut != INVALID_HANDLE_VALUE )
{
if ( !::CloseHandle( _hStdOut ))
{
TCP_PRINT((DBG_CONTEXT,
"[~CGI_INFO] CloseHandle failed on StdOut, %d\n",
GetLastError()));
}
}
if ( _hStdIn != INVALID_HANDLE_VALUE )
{
if ( !::CloseHandle( _hStdIn ))
{
TCP_PRINT((DBG_CONTEXT,
"[~CGI_INFO] CloseHandle failed on StdIn, %d\n",
GetLastError()));
}
}
if ( _hProcess != INVALID_HANDLE_VALUE )
{
if ( !::CloseHandle( _hProcess ))
{
TCP_PRINT((DBG_CONTEXT,
"[~CGI_INFO] CloseHandle failed on Process, %d\n",
GetLastError()));
}
}
}
HTTP_REQUEST * _pRequest;
DWORD _fNoProcessHeaders;
DWORD _fWAISLookup;
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;
};
//
// Private prototypes.
//
BOOL ProcessCGIInput( CGI_INFO * pCGIInfo );
BOOL SetupChildEnv( HTTP_REQUEST * pRequest,
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 );
VOID
CGITerminateProcess(
PVOID pContext
);
/*******************************************************************/
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;
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE,
W3_PARAMETERS_KEY,
0,
KEY_ALL_ACCESS,
&hkeyParam ) == NO_ERROR )
{
fAllowSpecialCharsInShell = !!ReadRegistryDword( hkeyParam,
"AllowSpecialCharsInShell",
FALSE );
fForwardServerEnvironmentBlock = !!ReadRegistryDword(
hkeyParam,
"ForwardServerEnvironmentBlock",
TRUE );
fCreateProcessAsUser = !!ReadRegistryDword( hkeyParam,
"CreateProcessAsUser",
TRUE );
fCreateProcessWithNewConsole = !!ReadRegistryDword( hkeyParam,
"CreateProcessWithNewConsole",
FALSE );
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;
}
//
// add one for final '\0' ( empty string ) delimiter
//
++cchIisEnv;
//
// store it
//
if ( (g_pszIisEnv = (LPSTR)LocalAlloc(
LMEM_FIXED, cchIisEnv * sizeof(TCHAR))) == NULL )
{
return ERROR_NOT_ENOUGH_MEMORY;
}
memcpy( 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;
}
return NO_ERROR;
}
VOID
TerminateCGI(
VOID
)
/*++
Routine Description:
Terminate CGI
Arguments:
None
Return Value:
TRUE if successful, FALSE on error
--*/
{
if (g_pszIisEnv != NULL )
{
LocalFree( g_pszIisEnv );
}
if ( g_pEnvEntries && g_pEnvEntries != CGIEnvTable )
{
delete [] g_pEnvEntries;
}
}
BOOL
HTTP_REQUEST::ProcessGateway(
BOOL * pfHandled,
BOOL * pfFinished
)
/*++
Routine Description:
Prepares for either a CGI or BGI 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:
pfHandled - Indicates if the request was a gateway request
pfFinished - Indicates no further processing is required
Return Value:
TRUE if successful, FALSE on error
--*/
{
BOOL fRet = TRUE;
TCHAR * pch;
DWORD cchRootPath;
STR strWorkingDir;
DWORD dwMask;
*pfHandled = FALSE;
*pfFinished = FALSE; // ProcessBGI may reset this
if ( !_strPathInfo.Unescape() )
{
return FALSE;
}
//
// Make sure the user has execute on this virtual root
//
if ( !(_dwRootMask & VROOT_MASK_EXECUTE) )
{
*pfHandled = TRUE;
Disconnect( HT_FORBIDDEN, IDS_EXECUTE_ACCESS_DENIED );
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( &_strPhysicalPath,
_strURL.QueryStr(),
&cchRootPath,
NULL,
TRUE,
&dwMask ))
{
return FALSE;
}
if ( !strWorkingDir.Resize( (cchRootPath + 1) * sizeof(TCHAR) ))
{
return FALSE;
}
::_tcsncpy( strWorkingDir.QueryStr(),
_strPhysicalPath.QueryStr(),
cchRootPath );
*(strWorkingDir.QueryStr() + cchRootPath) = TEXT('\0');
//
// Process a CGI, BGI or WAIS request based on the file extension
//
switch ( _GatewayType )
{
case GATEWAY_BGI:
fRet = ProcessBGI( (_strGatewayImage.IsEmpty() ? _strPhysicalPath :
_strGatewayImage),
strWorkingDir,
pfHandled,
pfFinished );
break;
case GATEWAY_CGI:
{
//
// Keep-alive not supported for CGI
//
SetKeepConn( FALSE );
//
// If this was a mapped script extension expand any parameters
//
if ( !_strGatewayImage.IsEmpty() )
{
STR strDecodedParams;
STR strCmdLine;
if ( !SetupCmdLine( &strDecodedParams,
_strURLParams ) ||
!strCmdLine.Resize( _strGatewayImage.QueryCB() +
_strPhysicalPath.QueryCB() +
strDecodedParams.QueryCB()))
{
return FALSE;
}
if ( IsCmdExe( _strGatewayImage.QueryStr() ))
{
//
// Make sure the path to the file exists if we're running
// the command interpreter
//
if ( GetFileAttributes( _strPhysicalPath.QueryStr() ) ==
0xffffffff )
{
DWORD err = GetLastError();
TCP_PRINT(( DBG_CONTEXT,
"[ProcessGateway] Error %d openning batch file %s\n",
err,
_strPhysicalPath.QueryStr() ));
if ( 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 );
*pfHandled = TRUE;
return TRUE;
}
return FALSE;
}
}
wsprintf( strCmdLine.QueryStr(),
_strGatewayImage.QueryStr(),
_strPhysicalPath.QueryStr(),
strDecodedParams.QueryStr() );
fRet = ProcessCGI( NULL,
&strWorkingDir,
pfHandled,
&strCmdLine );
}
else
{
fRet = ProcessCGI( &_strPhysicalPath,
&strWorkingDir,
pfHandled );
}
}
break;
case GATEWAY_WAIS:
{
DWORD attr;
STR strCmdLine;
STR strExe;
STR strDecodedParams;
STR strRelativePath;
STR strPort;
STR strHostName;
CHAR * pchExt;
TS_OPEN_FILE_INFO * pFile;
//
// Keep-alive not supported for CGI
//
SetKeepConn( FALSE );
//
// Check if this is a WAIS DB query. Note that strPath is actually
// the document/database name rather then the .exe.
//
// If either the document file doesn't exist or the WAIS database
// file doesn't exist, then we punt and treat like a normal request
//
if ( !ImpersonateUser() )
{
return FALSE;
}
pFile = TsCreateFile( g_pTsvcInfo->GetTsvcCache(),
_strPhysicalPath.QueryStr(),
QueryImpersonationHandle(),
(_fClearTextPass || _fAnonymous)
? TS_CACHING_DESIRED : 0 );
if ( !pFile )
{
RevertUser();
IF_DEBUG( CGI )
{
TCP_PRINT(( DBG_CONTEXT,
"[ProcessGateway] Ignoring possible IsIndex for %s"
" is directory or error openning\n",
_strPhysicalPath.QueryStr() ));
}
return GetLastError() == ERROR_FILE_NOT_FOUND ||
GetLastError() == ERROR_PATH_NOT_FOUND;
}
attr = pFile->QueryAttributes();
TCP_REQUIRE( TsCloseHandle( g_pTsvcInfo->GetTsvcCache(),
pFile ));
if ( attr == 0xffffffff ||
attr & FILE_ATTRIBUTE_DIRECTORY )
{
RevertUser();
IF_DEBUG( CGI )
{
TCP_PRINT(( DBG_CONTEXT,
"[ProcessGateway] Ignoring possible IsIndex for %s"
" is directory or error openning\n",
_strPhysicalPath.QueryStr() ));
}
return TRUE;
}
pchExt = strrchr( _strPhysicalPath.QueryStr(), '.' );
if ( pchExt )
*pchExt = '\0';
//
// Now see if there's a corresponding dictionary file
//
if ( !_strPhysicalPath.Append( TEXT(".dct") ))
return FALSE;
pFile = TsCreateFile( g_pTsvcInfo->GetTsvcCache(),
_strPhysicalPath.QueryStr(),
QueryImpersonationHandle(),
_fClearTextPass || _fAnonymous );
if ( !pFile )
{
RevertUser();
IF_DEBUG( CGI )
{
TCP_PRINT(( DBG_CONTEXT,
"[ProcessGateway] Ignoring possible IsIndex for %s"
" is directory or error openning\n",
_strPhysicalPath.QueryStr() ));
}
return GetLastError() == ERROR_FILE_NOT_FOUND ||
GetLastError() == ERROR_PATH_NOT_FOUND;
}
attr = pFile->QueryAttributes();
TCP_REQUIRE( TsCloseHandle( g_pTsvcInfo->GetTsvcCache(),
pFile ));
if ( attr == 0xffffffff ||
attr & FILE_ATTRIBUTE_DIRECTORY )
{
RevertUser();
IF_DEBUG( CGI )
{
TCP_PRINT(( DBG_CONTEXT,
"[ProcessGateway] Ignoring possible IsIndex for %s"
" is directory or error openning\n",
_strPhysicalPath.QueryStr() ));
}
return TRUE;
}
RevertUser();
//
// Looks like we have a WAIS query. Build the command line for the
// Lookup utility and process like a CGI request
//
if ( !strRelativePath.Copy( _strURL ) ||
!GetInfo( "SERVER_PORT",
&strPort ) ||
!GetInfo( "SERVER_NAME",
&strHostName ))
{
return FALSE;
}
if ( (pch = _tcsrchr( strRelativePath.QueryStr(),
TEXT('//'))) &&
pch )
{
*pch = TEXT('\0');
}
if ( !SetupCmdLine( &strDecodedParams,
_strURLParams ) ||
!strExe.Copy( WAIS_CMD ) ||
!strCmdLine.Resize( sizeof(WAIS_CMD_ARGS) +
sizeof(WAIS_CMD) +
_strPhysicalPath.QueryCB() +
strHostName.QueryCB() +
strPort.QueryCB() +
strRelativePath.QueryCB() +
strDecodedParams.QueryCB() + 15 ))
{
return FALSE;
}
wsprintf( strCmdLine.QueryStr(),
WAIS_CMD_ARGS,
WAIS_CMD, // Program name
_strPhysicalPath.QueryStr(), // Database name
//strHostName.QueryStr(), // Server name (or IP addr)
QueryHostAddr(),
strPort.QueryStr(), // Socket Port number
//strRelativePath.QueryStr(), // Relative URL to this dir
strDecodedParams.QueryStr()); // Search words
fRet = ProcessCGI( NULL,
&strWorkingDir,
pfHandled,
&strCmdLine,
TRUE );
}
break;
default:
TCP_ASSERT( FALSE );
SetLastError( ERROR_INVALID_PARAMETER );
fRet = FALSE;
break;
} // switch
return fRet;
}
/********************************************************************/
BOOL
HTTP_REQUEST::ProcessCGI(
const STR * pstrPath,
const STR * pstrWorkingDir,
BOOL * pfHandled,
STR * pstrCmdLine,
BOOL fWAISLookup
)
/*++
Routine Description:
Processes a CGI client request
This same code path is used for WAIS DB support
Arguments:
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
pstrCmdLine - Optional command line to use instead of the default
fWAISLookup - We don't do header processing if this is a WAIS lookup
Return Value:
TRUE if successful, FALSE on error
--*/
{
STARTUPINFO startupinfo;
PROCESS_INFORMATION processinfo;
BUFFER buffEnv;
BOOL fRet = FALSE;
CGI_INFO * pCGIInfo = NULL;
DWORD dwThreadId;
HANDLE hThread;
STR strCmdLine;
DWORD dwFlags = DETACHED_PROCESS | CREATE_SEPARATE_WOW_VDM;
*pfHandled = TRUE;
if ( !fWAISLookup )
{
//
// Only build the command line and environment block if
// this is a real CGI request (as opposed to a WAIS lookup)
//
// Note we move the module name to the command line so argv
// comes out correctly
//
if ( pstrPath )
{
if ( !strCmdLine.Resize( MAX_PATH ) ||
!strCmdLine.Copy( "\"" ) ||
!strCmdLine.Append( pstrPath ? pstrPath->QueryStr() :
NULL ) ||
!strCmdLine.Append( "\" " ))
{
return FALSE;
}
}
if ( !SetupChildEnv( this,
&buffEnv ) ||
!SetupCmdLine( &strCmdLine,
_strURLParams ))
{
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 )
{
DWORD i;
if ( IsCmdExe( pstrCmdLine->QueryStr()) )
{
//
// We'll either match one of the characters or the '\0'
//
i = strcspn( pstrCmdLine->QueryStr(), "&|(,;%<>" );
if ( pstrCmdLine->QueryStr()[i] )
{
TCP_PRINT(( DBG_CONTEXT,
"[ProcessCGI] Refusing request for command shell due "
" to special characters\n" ));
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
}
}
//
// Setup the pipes information
//
pCGIInfo = new CGI_INFO( this );
if ( !pCGIInfo )
{
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
goto Exit;
}
pCGIInfo->_fWAISLookup = fWAISLookup;
pCGIInfo->_fNoProcessHeaders = _fNPHScript;
memset( &startupinfo, 0, sizeof(startupinfo) );
startupinfo.cb = sizeof(startupinfo);
//
// 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 )
{
TCP_PRINT((DBG_CONTEXT,
"[ProcessCGI] Failed to create child pipes, error %d",
GetLastError() ));
}
goto Exit;
}
if ( fCreateProcessWithNewConsole )
{
dwFlags = CREATE_NEW_CONSOLE | CREATE_SEPARATE_WOW_VDM;
}
//////////////////////////////////////////////////////////////
//
// 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 )
{
TCP_PRINT(( DBG_CONTEXT,
"[ProcessCGI] Creating process, path = %s, cmdline = %s\n",
(pstrPath ? pstrPath->QueryStr() : "NULL"),
pstrCmdLine->QueryStr() ));
}
TryAgain:
if ( !fCreateProcessAsUser )
{
if ( !ImpersonateUser() )
{
goto Exit;
}
fRet = CreateProcess( (pstrPath ? pstrPath->QueryStr() : NULL),
pstrCmdLine->QueryStr(),
NULL, // Process security
NULL, // Thread security
TRUE, // Inherit handles
dwFlags,
buffEnv.QueryPtr(),
pstrWorkingDir->QueryStr(),
&startupinfo,
&processinfo );
RevertUser();
}
else
{
HANDLE hDelete = NULL;
HANDLE hToken = QueryPrimaryToken( &hDelete );
if ( !ImpersonateLoggedOnUser( hToken ))
{
TCP_PRINT(( 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->QueryStr(),
&startupinfo,
&processinfo );
TCP_REQUIRE( RevertToSelf() );
if ( hDelete )
{
TCP_REQUIRE( CloseHandle( hDelete ) );
}
}
//
// If we get access denied this may be a 16 bit app, so try again w/o
// the process detached flag
//
if ( !fRet &&
(GetLastError() == ERROR_ACCESS_DENIED ||
GetLastError() == ERROR_INVALID_HANDLE ) &&
(dwFlags & DETACHED_PROCESS) )
{
TCP_PRINT(( DBG_CONTEXT,
"[ProcessCGI] Retrying as a win16 app\n" ));
dwFlags &= ~DETACHED_PROCESS;
goto TryAgain;
}
TCP_REQUIRE( CloseHandle( startupinfo.hStdOutput ));
TCP_REQUIRE( CloseHandle( startupinfo.hStdInput ));
if ( !fRet )
{
TCP_PRINT((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;
}
INCREMENT_COUNTER( TotalCGIRequests );
TCP_REQUIRE( CloseHandle( processinfo.hThread ));
TCP_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 ( g_fUsePoolThreadForCGI )
{
//
// Call the CGI processor directly
//
pCGIInfo->_fServerPoolThread = TRUE;
CGIThread( pCGIInfo );
}
else
{
//
// Create a thread to handle IO with the child process
//
Reference();
if ( !(hThread = CreateThread( NULL,
0,
(LPTHREAD_START_ROUTINE) CGIThread,
pCGIInfo,
0,
&dwThreadId )))
{
Dereference();
goto Exit;
}
//
// We don't use the thread handle so free the resource
//
TCP_REQUIRE( CloseHandle( hThread ));
}
fRet = TRUE;
Exit:
if ( !fRet )
{
DWORD err = GetLastError();
delete pCGIInfo;
//
// We may have mistook this request, try handling as a non-CGI
// request
//
if ( err == ERROR_PATH_NOT_FOUND ||
err == ERROR_FILE_NOT_FOUND )
{
fRet = TRUE;
*pfHandled = FALSE;
}
else if ( err == ERROR_ACCESS_DENIED )
{
SetDeniedFlags( SF_DENIED_RESOURCE );
}
}
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 )
{
TCP_PRINT((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 )
{
TCP_PRINT((DBG_CONTEXT,
"[SetupChildPipes] Failed with error %d\n",
GetLastError()));
}
if ( *phParentIn )
TCP_REQUIRE( CloseHandle( *phParentIn ));
if ( *phParentOut )
TCP_REQUIRE( CloseHandle( *phParentOut ));
return FALSE;
}
/*******************************************************************
NAME: SetupChildEnv
SYNOPSIS: Based on the passed pRequest, builds a CGI environment block
ENTRY: pRequest - HTTP request object
pBuff - Buffer to receive environment block
RETURNS: TRUE if successful, FALSE on failure
HISTORY:
Johnl 22-Sep-1994 Created
********************************************************************/
BOOL SetupChildEnv( HTTP_REQUEST * pRequest,
BUFFER * pBuff )
{
TCHAR * pch, *pchtmp;
STR strVal;
UINT cchCurrentPos = 0; // Points to '\0' in buffer
UINT cchName, cchValue;
UINT cbNeeded;
int i = 0;
if ( !pBuff->Resize( 1500 * sizeof(TCHAR) ))
{
return FALSE;
}
//
// Build the environment block for CGI
//
while ( g_pEnvEntries[i].m_pszName )
{
//
// 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 ( !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
{
memcpy( 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;
}
//
// Replace "+" with spaces and decode any hex escapes
//
if ( !pstrCmdLine->Append( strParams ) )
return FALSE;
while ( pch = _tcschr( pstrCmdLine->QueryStr(),
TEXT('+') ))
{
*pch = TEXT(' ');
pch++;
}
return pstrCmdLine->Unescape();
}
/*******************************************************************
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;
BYTE buff[2048];
DWORD cbWritten;
DWORD cbRead;
DWORD cbSent;
DWORD dwExitCode;
DWORD err;
BOOL fReadHeaders = pCGIInfo->_fNoProcessHeaders;
BOOL fDone = FALSE;
BOOL fSkipDisconnect;
BOOL fRet = TRUE;
DWORD dwHttpStatus = HT_OK;
IF_DEBUG( CGI )
{
TCP_PRINT((DBG_CONTEXT,
"[CGIThread] Entered, hstdin %x, hstdout = %x\n",
pCGIInfo->_hStdIn,
pCGIInfo->_hStdOut));
}
pRequest->SetLogStatus( HT_OK, NO_ERROR );
//
// Update the statistics counters
//
INCREMENT_COUNTER( CurrentCGIRequests );
if ( W3Stats.CurrentCGIRequests > W3Stats.MaxCGIRequests )
{
LockStatistics();
if ( W3Stats.CurrentCGIRequests > W3Stats.MaxCGIRequests )
W3Stats.MaxCGIRequests = W3Stats.CurrentCGIRequests;
UnlockStatistics();
}
//
// First we have to write any additional data to the program's stdin
//
if ( pRequest->QueryGatewayDataCB() )
{
DWORD cbNextRead;
DWORD cbLeft = 0;
IF_DEBUG( CGI )
{
TCP_PRINT((DBG_CONTEXT,
"[CGIThread] Writing %d bytes to child's stdin\n",
pRequest->QueryGatewayDataCB()));
}
pCGIInfo->_dwSchedCookie = ScheduleWorkItem( CGITerminateProcess,
pCGIInfo->_hProcess,
msScriptTimeout );
if ( !::WriteFile( pCGIInfo->_hStdOut,
pRequest->QueryGatewayData(),
pRequest->QueryGatewayDataCB(),
&cbWritten,
NULL ))
{
TCP_PRINT((DBG_CONTEXT,
"[CGI_THREAD] WriteFile failed, error %d\n",
GetLastError()));
}
RemoveWorkItem( pCGIInfo->_dwSchedCookie );
if ( cbWritten != pRequest->QueryGatewayDataCB() )
{
TCP_PRINT((DBG_CONTEXT,
"[CGI_THREAD] %d bytes written of %d bytes\n",
cbWritten,
pRequest->QueryGatewayDataCB()));
}
//
// 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->QueryGatewayDataCB() < pRequest->QueryClientContentLength())
{
cbLeft = pRequest->QueryClientContentLength() -
pRequest->QueryGatewayDataCB();
}
while ( cbLeft )
{
//
// Place a time limit on the network read and CGI
// write
//
pCGIInfo->_dwSchedCookie = ScheduleWorkItem( CGITerminateProcess,
pCGIInfo->_hProcess,
msScriptTimeout );
cbNextRead = min( cbLeft,
pRequest->QueryClientReqBuff()->QuerySize() );
if ( !pRequest->ReadFile( pRequest->QueryClientRequest(),
cbNextRead,
&cbRead,
IO_FLAG_SYNC ) ||
!cbRead )
{
TCP_PRINT(( DBG_CONTEXT,
"[CGI_THREAD] Error reading gateway data (%d)\n",
GetLastError() ));
fRet = FALSE;
break;
}
cbLeft -= cbRead;
if ( !::WriteFile( pCGIInfo->_hStdOut,
pRequest->QueryClientRequest(),
cbRead,
&cbWritten,
NULL ))
{
fRet = FALSE;
break;
}
RemoveWorkItem( pCGIInfo->_dwSchedCookie );
}
}
if ( !fRet )
{
//
// If an error occurred during the client read or write, we let the CGI
// application continue
//
TCP_PRINT((DBG_CONTEXT,
"[CGI_THREAD] Gateway ReadFile or CGI WriteFile failed, error %d\n",
GetLastError()));
RemoveWorkItem( pCGIInfo->_dwSchedCookie );
}
//
// Now wait for any data the child sends to its stdout or for the
// process to exit
//
//
// Handle input from child
//
while (TRUE)
{
//
// 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 )
{
TCP_PRINT(( DBG_CONTEXT,
"[CGI_THREAD] ScheduleWorkItem failed, error %d\n",
GetLastError() ));
}
fRet = ::ReadFile( pCGIInfo->_hStdIn,
buff,
sizeof(buff),
&cbRead,
NULL );
if ( !fRet )
{
err = GetLastError();
}
if ( !fRet )
{
RemoveWorkItem( pCGIInfo->_dwSchedCookie );
if ( err == ERROR_BROKEN_PIPE )
{
break;
}
IF_DEBUG( CGI )
{
TCP_PRINT((DBG_CONTEXT,
"[CGI_THREAD] ReadFile from child stdout failed, error %d, _hStdIn = %x\n",
GetLastError(),
pCGIInfo->_hStdIn));
}
pRequest->SetLogStatus( 500, err );
break;
}
//
// Remove the scheduled timeout callback
//
if ( !RemoveWorkItem( pCGIInfo->_dwSchedCookie ))
{
TCP_PRINT(( DBG_CONTEXT,
"[CGI_THREAD] Failed to remove scheduled item\n" ));
}
IF_DEBUG( CGI )
{
TCP_PRINT((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.
//
// For a WAIS lookup, we don't get any headers so we just send
// our normal server headers followed by the .exe's output
//
if ( !fReadHeaders )
{
if ( !ProcessCGIInput( pCGIInfo,
buff,
cbRead,
&fReadHeaders,
&fDone,
&fSkipDisconnect,
&dwHttpStatus ))
{
TCP_PRINT((DBG_CONTEXT,
"[CGIThread] ProcessCGIInput failed with error %d\n",
GetLastError()));
goto Disconnect;
}
if ( !pCGIInfo->_fWAISLookup )
{
if ( fSkipDisconnect )
goto SkipDisconnect;
if ( fDone )
goto Disconnect;
//
// 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 ( !pRequest->WriteFile( buff,
cbRead,
&cbSent,
IO_FLAG_SYNC ))
{
TCP_PRINT((DBG_CONTEXT,
"[CGI_THREAD] WriteFile to socket failed, error %d\n",
GetLastError()));
pRequest->SetLogStatus( 500,
GetLastError() );
break;
}
}
//
// If we had to kill the process, log an event
//
if ( GetExitCodeProcess( pCGIInfo->_hProcess,
&dwExitCode ) &&
dwExitCode == CGI_PREMATURE_DEATH_CODE )
{
STR strResponse;
const CHAR * apsz[2];
//
// Log an event and terminate the process
//
TCP_PRINT((DBG_CONTEXT,
"[CGI_THREAD] - CGI Script %s, params %s was killed\n",
pRequest->QueryURL(),
pRequest->QueryURLParams()));
apsz[0] = pRequest->QueryURL();
apsz[1] = pRequest->QueryURLParams();
g_pTsvcInfo->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 ( !fReadHeaders )
{
if ( strResponse.Resize( 512 ) &&
HTTP_REQUEST::BuildExtendedStatus( &strResponse,
HT_BAD_GATEWAY,
NO_ERROR,
IDS_CGI_APP_TIMEOUT ))
{
pRequest->WriteFile( strResponse.QueryStr(),
strResponse.QueryCB(),
&cbSent,
IO_FLAG_SYNC );
}
}
else
{
if ( g_pTsvcInfo->LoadStr( strResponse, IDS_CGI_APP_TIMEOUT ))
{
pRequest->WriteFile( strResponse.QueryStr(),
strResponse.QueryCB(),
&cbSent,
IO_FLAG_SYNC );
}
}
pRequest->SetLogStatus( HT_BAD_GATEWAY, ERROR_SERVICE_REQUEST_TIMEOUT );
}
else
{
//
// If we never finished reading the headers, send a nice message
// to the client
//
if ( !fReadHeaders )
{
STR strResponse;
if ( strResponse.Resize( 512 ) &&
HTTP_REQUEST::BuildExtendedStatus( &strResponse,
HT_BAD_GATEWAY,
NO_ERROR,
IDS_BAD_CGI_APP ) &&
strResponse.Append((const CHAR *)pCGIInfo->_Buff.QueryPtr() )&&
strResponse.Append( "</pre></body>" ))
{
pRequest->WriteFile( strResponse.QueryStr(),
strResponse.QueryCB(),
&cbSent,
IO_FLAG_SYNC );
}
pRequest->SetLogStatus( HT_BAD_GATEWAY,
ERROR_SERVICE_REQUEST_TIMEOUT );
}
}
Disconnect:
IF_DEBUG ( CGI )
{
TCP_PRINT((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:
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 );
}
if ( !pCGIInfo->_fServerPoolThread )
{
//
// Reference is only done if we've created a new thread
//
DereferenceConn( pRequest->QueryClientConn() );
}
delete pCGIInfo;
DECREMENT_COUNTER( CurrentCGIRequests );
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 )
{
CHAR * pchValue;
CHAR * pchField;
BYTE * pbData;
CHAR * pszTail;
DWORD cbData;
DWORD cbSent;
STR strContentType;
STR strStatus;
STR strCGIResp; // Contains CGI client headers
BOOL fFoundContentType = FALSE;
BOOL fFoundStatus = FALSE;
HTTP_REQUEST * pRequest = pCGIInfo->_pRequest;
DWORD cbNeeded, cbBaseResp;
ASSERT( cbRead > 0 );
*pfDone = FALSE;
*pfSkipDisconnect = FALSE;
//
// The WAIS lookup program doesn't send any headers so we just add our
// own and and treat all of the read data as entity data
//
if ( pCGIInfo->_fWAISLookup )
{
*pfReadHeaders = TRUE;
if ( !strContentType.Copy( TEXT("Content-Type: text/html\r\n") ))
return FALSE;
//
// Set the entity data
//
cbData = pCGIInfo->_cbData;
pbData = (BYTE *) pCGIInfo->_Buff.QueryPtr();
goto SendHeaders;
}
if ( !pCGIInfo->_Buff.Resize( pCGIInfo->_cbData + cbRead,
256 ))
{
return FALSE;
}
memcpy( (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)
//
{
INET_PARSER Parser( (CHAR *) pCGIInfo->_Buff.QueryPtr() );
while ( *(pchField = Parser.QueryToken()) )
{
Parser.SkipTo( ':' );
Parser += 1;
pchValue = Parser.QueryToken();
if ( !::_strnicmp( "Status", pchField, 6 ) )
{
fFoundStatus = TRUE;
*pdwHttpStatus = atoi( pchValue );
if ( !strStatus.Append( HTTP_VERSION_STR " " ) ||
!strStatus.Append( Parser.QueryLine() ) ||
!strStatus.Append( "\r\n" ))
{
return FALSE;
}
}
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
//
STR strURL( pchValue );
STR strResp;
if ( !strURL.IsValid() )
{
return FALSE;
}
if ( *pchValue == TEXT('/') )
{
if ( !pRequest->ReprocessURL( strURL.QueryStr(),
HTV_GET ))
{
return FALSE;
}
*pfSkipDisconnect = TRUE;
}
else
{
if ( !pRequest->BuildURLMovedResponse( &strResp,
&strURL ) ||
!pRequest->WriteFile( strResp.QueryStrA(),
strResp.QueryCB(),
NULL,
IO_FLAG_ASYNC ))
{
return FALSE;
}
}
*pfDone = TRUE;
return TRUE;
}
else
{
//
// Copy any other fields the script specified
//
if ( !::_strnicmp( "Content-Type", pchField, 12 ))
fFoundContentType = TRUE;
//
// Terminate line
//
Parser.QueryLine();
if ( !strCGIResp.Append( pchField ) ||
!strCGIResp.Append( "\r\n" ))
{
return FALSE;
}
}
Parser.NextLine();
}
}
//
// If the CGI script didn't specify a content type, then use
// the default
//
if ( !fFoundContentType )
{
STR str;
if ( !strContentType.Append( "Content-Type: " )||
!SelectMimeMapping( &str,
NULL ) ||
!strContentType.Append( str ) ||
!strContentType.Append( TEXT("\r\n") ))
{
return FALSE;
}
}
SendHeaders:
//
// Combine the CGI specified headers with the regular headers
// the server would send (message date, server ver. etc)
//
if ( !*pdwHttpStatus )
*pdwHttpStatus = HT_OK;
if ( *pdwHttpStatus == HT_DENIED )
{
pRequest->SetDeniedFlags( SF_DENIED_APPLICATION );
pRequest->SetAuthenticationRequested( TRUE );
}
if ( !pRequest->BuildBaseResponseHeader( pRequest->QueryRespBuf(),
pfDone,
fFoundStatus ? &strStatus :
NULL ))
{
return FALSE;
}
if ( *pfDone )
{
return TRUE;
}
cbBaseResp = pRequest->QueryRespBufCB();
cbNeeded = cbBaseResp +
strContentType.QueryCB() +
strCGIResp.QueryCB() +
sizeof( "\r\n" ); // Include the '\0' in the count
if ( !pRequest->QueryRespBuf()->Resize( cbNeeded ))
{
return FALSE;
}
pszTail = pRequest->QueryRespBufPtr() + cbBaseResp;
strcpy( pszTail, strContentType.QueryStr() );
strcat( pszTail, strCGIResp.QueryStr() );
strcat( pszTail, "\r\n" );
if ( !pRequest->WriteFile( pRequest->QueryRespBufPtr(),
pRequest->QueryRespBufCB(),
&cbSent,
IO_FLAG_SYNC ))
{
return FALSE;
}
//
// If there was additional data in the buffer, send that out now
//
if ( cbData )
{
if ( !pRequest->WriteFile( pbData,
cbData,
&cbSent,
IO_FLAG_SYNC ))
{
return FALSE;
}
}
return TRUE;
}
VOID
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 )
{
TCP_PRINT(( DBG_CONTEXT,
"[CGITerminateProcess] - Terminating process handle %x\n",
pContext ));
}
if ( !TerminateProcess( (HANDLE) pContext, CGI_PREMATURE_DEATH_CODE ))
{
TCP_PRINT((DBG_CONTEXT,
"[CGITerminateProcess] - TerminateProcess returned %d\n",
GetLastError()));
}
}
BOOL
IsCmdExe(
const CHAR * pchPath
)
{
while ( *pchPath )
{
if ( *pchPath == 'c' || *pchPath == 'C' )
{
if ( !_strnicmp( pchPath, "cmd.exe", sizeof("cmd.exe") - 1))
{
return TRUE;
}
}
pchPath++;
}
return FALSE;
}