2944 lines
82 KiB
C++
2944 lines
82 KiB
C++
/**********************************************************************/
|
||
/** 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
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|