Leaked source code of windows server 2003
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.
 
 
 
 
 
 

2265 lines
63 KiB

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name :
cgi_handler.cxx
Abstract:
Handle CGI requests
Author:
Taylor Weiss (TaylorW) 27-Jan-2000
Environment:
Win32 - User Mode
Project:
ULW3.DLL
--*/
#include "precomp.hxx"
#include "cgi_handler.h"
//
// W3_CGI_HANDLER statics
//
BOOL W3_CGI_HANDLER::sm_fForwardServerEnvironmentBlock = FALSE;
WCHAR * W3_CGI_HANDLER::sm_pEnvString = NULL;
DWORD W3_CGI_HANDLER::sm_cchEnvLength = 0;
LIST_ENTRY W3_CGI_HANDLER::sm_CgiListHead;
CRITICAL_SECTION W3_CGI_HANDLER::sm_CgiListLock;
//
// Environment variable block used for CGI
//
LPSTR g_CGIServerVars[] =
{
{"ALL_HTTP"}, // Means insert all HTTP_ headers here
{"APP_POOL_ID"},
{"AUTH_TYPE"},
{"AUTH_PASSWORD"},
{"AUTH_USER"},
{"CERT_COOKIE"},
{"CERT_FLAGS"},
{"CERT_ISSUER"},
{"CERT_SERIALNUMBER"},
{"CERT_SUBJECT"},
{"CONTENT_LENGTH"},
{"CONTENT_TYPE"},
{"GATEWAY_INTERFACE"},
{"HTTPS"},
{"HTTPS_KEYSIZE"},
{"HTTPS_SECRETKEYSIZE"},
{"HTTPS_SERVER_ISSUER"},
{"HTTPS_SERVER_SUBJECT"},
{"INSTANCE_ID"},
{"LOCAL_ADDR"},
{"LOGON_USER"},
{"PATH_INFO"},
{"PATH_TRANSLATED"},
{"QUERY_STRING"},
{"REMOTE_ADDR"},
{"REMOTE_HOST"},
{"REMOTE_USER"},
{"REQUEST_METHOD"},
{"SCRIPT_NAME"},
{"SERVER_NAME"},
{"SERVER_PORT"},
{"SERVER_PORT_SECURE"},
{"SERVER_PROTOCOL"},
{"SERVER_SOFTWARE"},
{"UNMAPPED_REMOTE_USER"},
{NULL}
};
// static
HRESULT W3_CGI_HANDLER::Initialize()
{
DBGPRINTF((DBG_CONTEXT, "W3_CGI_HANDLER::Initialize() called\n"));
//
// Read some CGI configuration from the registry
//
HRESULT hr;
if (!InitializeCriticalSectionAndSpinCount(&sm_CgiListLock, 10))
{
return HRESULT_FROM_WIN32(GetLastError());
}
InitializeListHead(&sm_CgiListHead);
HKEY w3Params;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
W3_PARAMETERS_KEY,
0,
KEY_READ,
&w3Params) == NO_ERROR)
{
DWORD dwType;
DWORD cbData = sizeof BOOL;
if ((RegQueryValueEx(w3Params,
L"ForwardServerEnvironmentBlock",
NULL,
&dwType,
(LPBYTE)&sm_fForwardServerEnvironmentBlock,
&cbData) != NO_ERROR) ||
(dwType != REG_DWORD))
{
sm_fForwardServerEnvironmentBlock = TRUE;
}
RegCloseKey(w3Params);
}
//
// Read the environment
//
if (sm_fForwardServerEnvironmentBlock)
{
WCHAR *EnvString = GetEnvironmentStrings();
if (EnvString == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
DBGPRINTF((DBG_CONTEXT, "GetEnvironmentStrings failed\n"));
goto Exit;
}
//
// Compute length of environment block (excluding block delimiter)
//
DWORD length;
sm_cchEnvLength = 0;
while ((length = (DWORD)wcslen(EnvString + sm_cchEnvLength)) != 0)
{
sm_cchEnvLength += length + 1;
}
//
// store it
//
if ((sm_pEnvString = new WCHAR[sm_cchEnvLength]) == NULL)
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
FreeEnvironmentStrings(EnvString);
goto Exit;
}
memcpy(sm_pEnvString,
EnvString,
sm_cchEnvLength * sizeof(WCHAR));
FreeEnvironmentStrings(EnvString);
}
return S_OK;
Exit:
DBG_ASSERT(FAILED(hr));
//
// Cleanup partially created stuff
//
Terminate();
return hr;
}
W3_CGI_HANDLER::~W3_CGI_HANDLER()
{
//
// Close all open handles related to this request
//
EnterCriticalSection(&sm_CgiListLock);
RemoveEntryList(&m_CgiListEntry);
LeaveCriticalSection(&sm_CgiListLock);
if (m_hTimer)
{
if (!DeleteTimerQueueTimer(NULL,
m_hTimer,
INVALID_HANDLE_VALUE))
{
DBGPRINTF((DBG_CONTEXT,
"DeleteTimerQueueTimer failed, %d\n",
GetLastError()));
}
m_hTimer = NULL;
}
if (m_hStdOut != INVALID_HANDLE_VALUE)
{
if (!CloseHandle(m_hStdOut))
{
DBGPRINTF((DBG_CONTEXT,
"CloseHandle failed on StdOut, %d\n",
GetLastError()));
}
m_hStdOut = INVALID_HANDLE_VALUE;
}
if (m_hStdIn != INVALID_HANDLE_VALUE)
{
if (!CloseHandle(m_hStdIn))
{
DBGPRINTF((DBG_CONTEXT,
"CloseHandle failed on StdIn, %d\n",
GetLastError()));
}
m_hStdIn = INVALID_HANDLE_VALUE;
}
if (m_hProcess)
{
//
// Just in case it is still running
//
TerminateProcess(m_hProcess, 0);
DBG_REQUIRE(CloseHandle(m_hProcess));
m_hProcess = NULL;
}
// perf ctr
QueryW3Context()->QuerySite()->DecCgiReqs();
if ( ETW_IS_TRACE_ON(ETW_LEVEL_CP) )
{
HTTP_REQUEST_ID RequestId = QueryW3Context()->QueryRequest()->QueryRequestId();
g_pEtwTracer->EtwTraceEvent( &CgiEventGuid,
ETW_TYPE_END,
&RequestId,
sizeof(HTTP_REQUEST_ID),
NULL,
0 );
}
}
VOID CALLBACK W3_CGI_HANDLER::OnPipeIoCompletion(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped)
{
if (dwErrorCode && dwErrorCode != ERROR_BROKEN_PIPE)
{
DBGPRINTF((DBG_CONTEXT, "Error %d on CGI_HANDLER::OnPipeIoCompletion\n", dwErrorCode));
}
HRESULT hr = S_OK;
BOOL fIsCgiError = TRUE;
W3_CGI_HANDLER *pHandler = CONTAINING_RECORD(lpOverlapped,
W3_CGI_HANDLER,
m_Overlapped);
if (dwErrorCode ||
(dwNumberOfBytesTransfered == 0))
{
if (pHandler->m_dwRequestState == CgiStateProcessingRequestEntity)
{
//
// If we could not write the request entity, for example because
// the CGI did not wait to read the entity, ignore the error and
// continue on to reading the output
//
//
// If this is an nph cgi, we do not parse header
//
if (pHandler->QueryIsNphCgi())
{
pHandler->m_dwRequestState = CgiStateProcessingResponseEntity;
}
else
{
pHandler->m_dwRequestState = CgiStateProcessingResponseHeaders;
}
pHandler->m_cbData = 0;
if (SUCCEEDED(hr = pHandler->CGIReadCGIOutput()))
{
return;
}
}
else
{
hr = HRESULT_FROM_WIN32(dwErrorCode);
}
fIsCgiError = TRUE;
goto ErrorExit;
}
if (pHandler->m_dwRequestState == CgiStateProcessingResponseHeaders)
{
//
// Copy the headers to the header buffer to be parsed when we have
// all the headers
//
if (!pHandler->m_bufResponseHeaders.Resize(
pHandler->m_cbData + dwNumberOfBytesTransfered + 1))
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto ErrorExit;
}
memcpy((LPSTR)pHandler->m_bufResponseHeaders.QueryPtr() +
pHandler->m_cbData,
pHandler->m_DataBuffer,
dwNumberOfBytesTransfered);
pHandler->m_cbData += dwNumberOfBytesTransfered;
((LPSTR)pHandler->m_bufResponseHeaders.QueryPtr())[pHandler->m_cbData] = '\0';
}
else if (pHandler->m_dwRequestState == CgiStateProcessingResponseEntity)
{
pHandler->m_cbData = dwNumberOfBytesTransfered;
}
if (FAILED(hr = pHandler->CGIContinueOnPipeCompletion(&fIsCgiError)))
{
goto ErrorExit;
}
return;
ErrorExit:
if (hr != HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE))
{
pHandler->QueryW3Context()->SetErrorStatus(hr);
}
//
// If the error happened due to an CGI problem, mark it as 502
// appropriately
//
if (fIsCgiError)
{
if (pHandler->m_dwRequestState != CgiStateProcessingResponseEntity &&
pHandler->m_dwRequestState != CgiStateDoneWithRequest)
{
pHandler->QueryW3Context()->QueryResponse()->Clear();
DWORD dwExitCode;
if (GetExitCodeProcess(pHandler->m_hProcess,
&dwExitCode) &&
dwExitCode == CGI_PREMATURE_DEATH_CODE)
{
STACK_STRU( strUrl, 128);
STACK_STRU( strQuery, 128);
if (SUCCEEDED(pHandler->QueryW3Context()->QueryRequest()->GetUrl(&strUrl)) &&
SUCCEEDED(pHandler->QueryW3Context()->QueryRequest()->GetQueryString(&strQuery)))
{
LPCWSTR apsz[2];
apsz[0] = strUrl.QueryStr();
apsz[1] = strQuery.QueryStr();
g_pW3Server->LogEvent(W3_EVENT_KILLING_SCRIPT,
2,
apsz);
}
pHandler->QueryW3Context()->QueryResponse()->SetStatus(
HttpStatusBadGateway,
Http502Timeout);
}
else
{
pHandler->QueryW3Context()->QueryResponse()->SetStatus(
HttpStatusBadGateway,
Http502PrematureExit);
}
}
}
else
{
pHandler->QueryW3Context()->QueryResponse()->SetStatus(
HttpStatusBadRequest);
}
pHandler->m_dwRequestState = CgiStateDoneWithRequest;
POST_MAIN_COMPLETION( pHandler->QueryW3Context()->QueryMainContext() );
}
HRESULT
GeneratePipeNames(
STRU *pstrStdoutName,
STRU *pstrStdinName)
{
RPC_STATUS rpcStatus;
UUID pipeUuid;
LPWSTR pszPipeUuid = NULL;
HRESULT hr;
rpcStatus = UuidCreate(&pipeUuid);
if (rpcStatus != RPC_S_OK)
{
return E_FAIL;
}
rpcStatus = UuidToString(&pipeUuid, &pszPipeUuid);
if (rpcStatus != RPC_S_OK)
{
return E_FAIL;
}
if (FAILED(hr = pstrStdoutName->Copy(L"\\\\.\\pipe\\IISCgiStdout")) ||
FAILED(hr = pstrStdoutName->Append(pszPipeUuid)) ||
FAILED(hr = pstrStdinName->Copy(L"\\\\.\\pipe\\IISCgiStdin")) ||
FAILED(hr = pstrStdinName->Append(pszPipeUuid)))
{
RpcStringFree(&pszPipeUuid);
return hr;
}
RpcStringFree(&pszPipeUuid);
return hr;
}
// static
HRESULT W3_CGI_HANDLER::SetupChildPipes(
HANDLE *phStdOut,
HANDLE *phStdIn,
STARTUPINFO *pstartupinfo)
/*++
Synopsis
Setup the pipes to use for communicating with the child process
Arguments
phStdOut: this will be populated with the server side of CGIs stdout
phStdIn: this will be populated with the server side of CGIs stdin
pstartupinfo: this will be populated with the startinfo that can be
passed to a CreateProcess call
Return Value
HRESULT
--*/
{
STACK_STRU( strStdoutName, 128);
STACK_STRU( strStdinName, 128);
HRESULT hr = S_OK;
DBG_ASSERT(phStdOut != NULL);
DBG_ASSERT(phStdIn != NULL);
DBG_ASSERT(pstartupinfo != NULL);
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
pstartupinfo->hStdOutput = INVALID_HANDLE_VALUE;
pstartupinfo->hStdInput = INVALID_HANDLE_VALUE;
pstartupinfo->dwFlags = STARTF_USESTDHANDLES;
hr = GeneratePipeNames( &strStdoutName,
&strStdinName );
if (FAILED(hr))
{
goto ErrorExit;
}
*phStdOut = CreateNamedPipe(strStdoutName.QueryStr(),
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
0,
1,
MAX_CGI_BUFFERING,
MAX_CGI_BUFFERING,
INFINITE,
NULL);
if (*phStdOut == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
if (!ThreadPoolBindIoCompletionCallback(*phStdOut,
OnPipeIoCompletion,
0))
{
DBGPRINTF((DBG_CONTEXT, "ThreadPoolBindIo failed\n"));
hr = E_FAIL;
goto ErrorExit;
}
pstartupinfo->hStdOutput = CreateFile(strStdoutName.QueryStr(),
GENERIC_WRITE,
0,
&sa,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (pstartupinfo->hStdOutput == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
*phStdIn = CreateNamedPipe(strStdinName.QueryStr(),
PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
0,
1,
MAX_CGI_BUFFERING,
MAX_CGI_BUFFERING,
INFINITE,
NULL);
if (*phStdIn == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
if (!ThreadPoolBindIoCompletionCallback(*phStdIn,
OnPipeIoCompletion,
0))
{
DBGPRINTF((DBG_CONTEXT, "ThreadPoolBindIo failed\n"));
hr = E_FAIL;
goto ErrorExit;
}
pstartupinfo->hStdInput = CreateFile(strStdinName.QueryStr(),
GENERIC_READ,
0,
&sa,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (pstartupinfo->hStdInput == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
//
// Stdout and Stderror will use the same pipe.
//
pstartupinfo->hStdError = pstartupinfo->hStdOutput;
return S_OK;
ErrorExit:
DBGPRINTF((DBG_CONTEXT, "SetupChildPipes Failed, hr %x\n", hr));
//
// Need to close these now so that other instances do not connect to it
//
if (pstartupinfo->hStdOutput != INVALID_HANDLE_VALUE)
{
DBG_REQUIRE(CloseHandle(pstartupinfo->hStdOutput));
pstartupinfo->hStdOutput = INVALID_HANDLE_VALUE;
}
if (pstartupinfo->hStdInput != INVALID_HANDLE_VALUE)
{
DBG_REQUIRE(CloseHandle(pstartupinfo->hStdInput));
pstartupinfo->hStdInput = INVALID_HANDLE_VALUE;
}
if (*phStdOut != INVALID_HANDLE_VALUE)
{
DBG_REQUIRE(CloseHandle(*phStdOut));
*phStdOut = INVALID_HANDLE_VALUE;
}
if (*phStdIn != INVALID_HANDLE_VALUE)
{
DBG_REQUIRE(CloseHandle(*phStdIn));
*phStdIn = INVALID_HANDLE_VALUE;
}
return hr;
}
BOOL IsCmdExe(const WCHAR *pchPath)
/*++
Tells whether the CGI call is for a cmd shell
--*/
{
//
// do case-insensitive search for cmd.exe (there is no function in
// msvcrt for this)
//
for (; *pchPath != L'\0'; pchPath++)
{
if ((pchPath[0] == L'c' || pchPath[0] == 'C') &&
(pchPath[1] == L'm' || pchPath[1] == 'M') &&
(pchPath[2] == L'd' || pchPath[2] == 'D') &&
pchPath[3] == L'.' &&
(pchPath[4] == L'e' || pchPath[4] == 'E') &&
(pchPath[5] == L'x' || pchPath[5] == 'X') &&
(pchPath[6] == L'e' || pchPath[6] == 'E'))
{
return TRUE;
}
}
return FALSE;
}
BOOL IsNphCgi(const WCHAR *pszUrl)
/*++
Tells whether the URL is for an nph cgi script
--*/
{
LPWSTR pszLastSlash = wcsrchr(pszUrl, L'/');
if (pszLastSlash != NULL)
{
if (_wcsnicmp(pszLastSlash + 1, L"nph-", 4) == 0)
{
return TRUE;
}
}
return FALSE;
}
HRESULT SetupCmdLine(W3_REQUEST *pRequest,
STRU *pstrCmdLine)
{
STACK_STRU (queryString, MAX_PATH);
HRESULT hr = S_OK;
if (FAILED(hr = pRequest->GetQueryString(&queryString)))
{
return hr;
}
//
// If there is no QueryString OR if an unencoded "=" is found, don't
// append QueryString to the command line according to spec
//
if (queryString.QueryCCH() == 0 ||
wcschr(queryString.QueryStr(), L'='))
{
return S_OK;
}
queryString.Unescape();
return pstrCmdLine->Append(queryString);
} // SetupCmdLine
HRESULT
W3_CGI_HANDLER::SetupChildEnv(OUT BUFFER *pBuff)
{
//
// Build the environment block for CGI
//
DWORD cchCurrentPos = 0;
//
// Copy the environment variables
//
if (sm_fForwardServerEnvironmentBlock)
{
if (!pBuff->Resize(sm_cchEnvLength * sizeof(WCHAR), 512))
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
memcpy(pBuff->QueryPtr(),
sm_pEnvString,
sm_cchEnvLength * sizeof(WCHAR));
cchCurrentPos = sm_cchEnvLength;
}
CHAR * pszName;
STACK_STRU (strVal, 512);
STACK_STRU (struName, 32);
for (DWORD i = 0; (pszName = g_CGIServerVars[i]) != NULL; i++)
{
HRESULT hr;
if (FAILED(hr = SERVER_VARIABLE_HASH::GetServerVariableW(
QueryW3Context(),
pszName,
&strVal)))
{
return hr;
}
DWORD cchName = (DWORD)strlen(pszName);
DWORD cchValue = strVal.QueryCCH();
//
// We need space for the terminating '\0' and the '='
//
DWORD cbNeeded = (cchName + cchValue + 1 + 1) * sizeof(WCHAR);
if (!pBuff->Resize(cchCurrentPos * sizeof(WCHAR) + cbNeeded,
512))
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
WCHAR * pchtmp = (WCHAR *)pBuff->QueryPtr();
if (strcmp(pszName, "ALL_HTTP") == 0)
{
//
// ALL_HTTP means we're adding all of the HTTP_ header
// fields which requires a little bit of special processing
//
memcpy(pchtmp + cchCurrentPos,
strVal.QueryStr(),
(cchValue + 1) * sizeof(WCHAR));
WCHAR * pszColonPosition = wcschr(pchtmp + cchCurrentPos, L':');
WCHAR * pszNewLinePosition;
//
// Convert the Name:Value\n entries to Name=Value\0 entries
// for the environment table
//
while (pszColonPosition != NULL)
{
*pszColonPosition = L'=';
pszNewLinePosition = wcschr(pszColonPosition + 1, L'\n');
DBG_ASSERT(pszNewLinePosition != NULL);
*pszNewLinePosition = L'\0';
pszColonPosition = wcschr(pszNewLinePosition + 1, L':');
}
cchCurrentPos += cchValue;
}
else
{
//
// Normal ServerVariable, add it
//
if (FAILED(hr = struName.CopyA(pszName,
cchName)))
{
return hr;
}
memcpy(pchtmp + cchCurrentPos,
struName.QueryStr(),
cchName * sizeof(WCHAR));
*(pchtmp + cchCurrentPos + cchName) = L'=';
memcpy(pchtmp + cchCurrentPos + cchName + 1,
strVal.QueryStr(),
(cchValue + 1) * sizeof(WCHAR));
cchCurrentPos += cchName + cchValue + 1 + 1;
}
}
//
// Add an extra null terminator to the environment list
//
if (!pBuff->Resize((cchCurrentPos + 1) * sizeof(WCHAR)))
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
*((WCHAR *)pBuff->QueryPtr() + cchCurrentPos) = L'\0';
return S_OK;
}
VOID CALLBACK W3_CGI_HANDLER::CGITerminateProcess(PVOID pContext,
BOOLEAN)
/*++
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
--*/
{
W3_CGI_HANDLER *pHandler = reinterpret_cast<W3_CGI_HANDLER *>(pContext);
if (pHandler->m_hProcess &&
!TerminateProcess(pHandler->m_hProcess, CGI_PREMATURE_DEATH_CODE))
{
DBGPRINTF((DBG_CONTEXT,
"CGITerminateProcess - TerminateProcess failed, error %d\n",
GetLastError()));
}
if (pHandler->m_hStdIn != INVALID_HANDLE_VALUE &&
!DisconnectNamedPipe(pHandler->m_hStdIn))
{
DBGPRINTF((DBG_CONTEXT,
"CGITerminateProcess - DisconnectNamedPipe failed, error %d\n",
GetLastError()));
}
if (pHandler->m_hStdOut != INVALID_HANDLE_VALUE &&
!DisconnectNamedPipe(pHandler->m_hStdOut))
{
DBGPRINTF((DBG_CONTEXT,
"CGITerminateProcess - DisconnectNamedPipe failed, error %d\n",
GetLastError()));
}
} // CGITerminateProcess
BOOL CheckForEndofHeaders(IN LPSTR pbuff,
IN int cbData,
OUT DWORD *pcbIndexStartOfData)
{
//
// If the response starts with a newline (\n, \r\n or \r\r\n), then,
// no headers present, it is all data
//
if (pbuff[0] == '\n')
{
*pcbIndexStartOfData = 1;
return TRUE;
}
if ((pbuff[0] == '\r') && (pbuff[1] == '\n'))
{
*pcbIndexStartOfData = 2;
return TRUE;
}
if ((pbuff[0] == '\r') && (pbuff[1] == '\r') && (pbuff[2] == '\n'))
{
*pcbIndexStartOfData = 3;
return TRUE;
}
//
// Look for two consecutive newline, \n\r\r\n, \n\r\n or \n\n
// No problem with running beyond the end of buffer as the buffer is
// null terminated
//
int index;
for (index = 0; index < cbData - 1; index++)
{
if (pbuff[index] == '\n')
{
if (pbuff[index + 1] == '\n')
{
*pcbIndexStartOfData = index + 2;
return TRUE;
}
else if (pbuff[index + 1] == '\r')
{
if (pbuff[index + 2] == '\n')
{
*pcbIndexStartOfData = index + 3;
return TRUE;
}
else if ((pbuff[index + 2] == '\r') &&
(pbuff[index + 3] == '\n'))
{
*pcbIndexStartOfData = index + 4;
return TRUE;
}
}
}
}
return FALSE;
}
HRESULT
W3_CGI_HANDLER::ProcessCGIOutput()
/*++
Synopsis
This function parses the CGI output and seperates out the headers and
interprets them as appropriate
Return Value
HRESULT
--*/
{
W3_REQUEST *pRequest = QueryW3Context()->QueryRequest();
W3_RESPONSE *pResponse = QueryW3Context()->QueryResponse();
HRESULT hr = S_OK;
STACK_STRA (strReason, 32);
DWORD index = 0;
BOOL fCanKeepConn = FALSE;
//
// The end of CGI headers are marked by a blank line, check to see if
// we've hit that line.
//
LPSTR Headers = (LPSTR)m_bufResponseHeaders.QueryPtr();
DWORD cbIndexStartOfData;
if (!CheckForEndofHeaders(Headers,
m_cbData,
&cbIndexStartOfData))
{
return CGIReadCGIOutput();
}
m_dwRequestState = CgiStateProcessingResponseEntity;
//
// We've found the end of the headers, process them
//
// if request header contains:
//
// Location: xxxx - if starts with /, send doc, otherwise send
// redirect message
// URI: preferred synonym to Location:
// Status: nnn xxxx - Send as status code (HTTP/1.1 nnn xxxx)
//
//
// The first line in the response could (optionally) look like
// HTTP/n.n nnn xxxx\r\n
//
if (!strncmp(Headers, "HTTP/", 5))
{
USHORT statusCode;
LPSTR pszSpace;
LPSTR pszNewline;
if ((pszNewline = strchr(Headers, '\n')) == NULL)
{
goto ErrorExit;
}
index = (DWORD)DIFF(pszNewline - Headers) + 1;
if (pszNewline[-1] == '\r')
pszNewline--;
if (((pszSpace = strchr(Headers, ' ')) == NULL) ||
(pszSpace >= pszNewline))
{
goto ErrorExit;
}
while (pszSpace[0] == ' ')
pszSpace++;
statusCode = (USHORT) atoi(pszSpace);
//
// UL only allows status codes upto 999, so reject others
//
if (statusCode > 999)
{
goto ErrorExit;
}
if (((pszSpace = strchr(pszSpace, ' ')) == NULL) ||
(pszSpace >= pszNewline))
{
goto ErrorExit;
}
while (pszSpace[0] == ' ')
pszSpace++;
if (FAILED(hr = strReason.Copy(pszSpace,
(DWORD)DIFF(pszNewline - pszSpace))) ||
FAILED(hr = pResponse->SetStatus(statusCode, strReason)))
{
goto ErrorExit;
}
if (pResponse->QueryStatusCode() == HttpStatusUnauthorized.statusCode)
{
pResponse->SetStatus(HttpStatusUnauthorized,
Http401Application);
}
}
while ((index + 3) < cbIndexStartOfData)
{
//
// Find the ':' in Header : Value\r\n
//
LPSTR pchColon = strchr(Headers + index, ':');
//
// Find the '\n' in Header : Value\r\n
//
LPSTR pchNewline = strchr(Headers + index, '\n');
//
// Take care of header continuation
//
while (pchNewline[1] == ' ' ||
pchNewline[1] == '\t')
{
pchNewline = strchr(pchNewline + 1, '\n');
}
if ((pchColon == NULL) ||
(pchColon >= pchNewline))
{
goto ErrorExit;
}
//
// Skip over any spaces before the ':'
//
LPSTR pchEndofHeaderName;
for (pchEndofHeaderName = pchColon - 1;
(pchEndofHeaderName >= Headers + index) &&
(*pchEndofHeaderName == ' ');
pchEndofHeaderName--)
{}
//
// Copy the header name
//
STACK_STRA (strHeaderName, 32);
if (FAILED(hr = strHeaderName.Copy(Headers + index,
(DWORD)DIFF(pchEndofHeaderName - Headers) - index + 1)))
{
goto ErrorExit;
}
//
// Skip over the ':' and any trailing spaces
//
for (index = (DWORD)DIFF(pchColon - Headers) + 1;
Headers[index] == ' ';
index++)
{}
//
// Skip over any spaces before the '\n'
//
LPSTR pchEndofHeaderValue;
for (pchEndofHeaderValue = pchNewline - 1;
(pchEndofHeaderValue >= Headers + index) &&
((*pchEndofHeaderValue == ' ') ||
(*pchEndofHeaderValue == '\r'));
pchEndofHeaderValue--)
{}
//
// Copy the header value
//
STACK_STRA (strHeaderValue, 32);
if (FAILED(hr = strHeaderValue.Copy(Headers + index,
(DWORD)DIFF(pchEndofHeaderValue - Headers) - index + 1)))
{
goto ErrorExit;
}
if (!_stricmp("Status", strHeaderName.QueryStr()))
{
USHORT statusCode = (USHORT) atoi(strHeaderValue.QueryStr());
//
// UL only allows status codes upto 999, so reject others
//
if (statusCode > 999)
{
goto ErrorExit;
}
CHAR * pchReason = strchr(strHeaderValue.QueryStr(), ' ');
if (pchReason != NULL)
{
pchReason++;
if (FAILED(hr = strReason.Copy(pchReason)) ||
FAILED(hr = pResponse->SetStatus(statusCode, strReason)))
{
goto ErrorExit;
}
if (pResponse->QueryStatusCode() == HttpStatusUnauthorized.statusCode)
{
pResponse->SetStatus(HttpStatusUnauthorized,
Http401Application);
}
}
}
else if (!_stricmp("Location", strHeaderName.QueryStr()) ||
!_stricmp("URI", strHeaderName.QueryStr()))
{
//
// The CGI script is redirecting us to another URL. If it
// begins with a '/', then get it, otherwise send a redirect
// message
//
m_dwRequestState = CgiStateDoneWithRequest;
m_fResponseRedirected = TRUE;
if (strHeaderValue.QueryStr()[0] == '/')
{
//
// Execute a child request
//
pResponse->Clear();
pResponse->SetStatus(HttpStatusOk);
if (FAILED(hr = pRequest->SetUrlA(strHeaderValue)) ||
FAILED(hr = QueryW3Context()->ExecuteChildRequest(
pRequest,
FALSE,
W3_FLAG_ASYNC )))
{
goto ErrorExit;
}
return S_OK;
}
else
{
HTTP_STATUS httpStatus;
pResponse->GetStatus(&httpStatus);
//
// Plain old redirect since this was not a "local" URL
// If the CGI had already set the status to some 3xx value,
// honor it
//
if (FAILED(hr = QueryW3Context()->SetupHttpRedirect(
strHeaderValue,
FALSE,
((httpStatus.statusCode / 100) == 3) ? httpStatus : HttpStatusRedirect)))
{
goto ErrorExit;
}
}
}
else
{
//
// Remember the Content-Length the cgi specified
//
if (!_stricmp("Content-Length", strHeaderName.QueryStr()))
{
fCanKeepConn = TRUE;
m_bytesToSend = atoi(strHeaderValue.QueryStr());
}
else if (!_stricmp("Transfer-Encoding", strHeaderName.QueryStr()) &&
!_stricmp("chunked", strHeaderValue.QueryStr()))
{
fCanKeepConn = TRUE;
}
else if (!_stricmp("Connection", strHeaderName.QueryStr()) &&
!_stricmp("close", strHeaderValue.QueryStr()))
{
QueryW3Context()->SetDisconnect(TRUE);
}
if (strHeaderName.QueryCCH() > MAXUSHORT ||
strHeaderValue.QueryCCH() > MAXUSHORT)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
goto ErrorExit;
}
//
// Copy any other fields the script specified
//
hr = pResponse->SetHeader(strHeaderName.QueryStr(),
(USHORT)strHeaderName.QueryCCH(),
strHeaderValue.QueryStr(),
(USHORT)strHeaderValue.QueryCCH());
if (FAILED(hr))
{
goto ErrorExit;
}
}
index = (DWORD)DIFF(pchNewline - Headers) + 1;
} // while
if (m_fResponseRedirected)
{
return QueryW3Context()->SendResponse(W3_FLAG_ASYNC);
}
//
// We allow CGI to send its own entity if no custom-error is defined
//
if (pResponse->QueryStatusCode() >= 400)
{
BOOL fIsFileError;
STACK_STRU ( strError, 64 );
HTTP_SUB_ERROR subError;
pResponse->QuerySubError( &subError );
W3_METADATA *pMetaData = QueryW3Context()->QueryUrlContext()->QueryMetaData();
hr = pMetaData->FindCustomError( pResponse->QueryStatusCode(),
subError.mdSubError,
&fIsFileError,
&strError );
if (SUCCEEDED(hr))
{
//
// Found a custom error, send it
//
m_dwRequestState = CgiStateDoneWithRequest;
return QueryW3Context()->SendResponse(W3_FLAG_ASYNC);
}
}
//
// If the CGI did not say how much data was present, mark the
// connection for closing
//
if (!fCanKeepConn)
{
QueryW3Context()->SetDisconnect(TRUE);
}
//
// Now send any data trailing the headers
//
if (cbIndexStartOfData < m_cbData)
{
if (m_bytesToSend < m_cbData - cbIndexStartOfData)
{
m_cbData = m_bytesToSend + cbIndexStartOfData;
}
if (FAILED(hr = pResponse->AddMemoryChunkByReference(
Headers + cbIndexStartOfData,
m_cbData - cbIndexStartOfData)))
{
return hr;
}
m_bytesToSend -= m_cbData - cbIndexStartOfData;
if (m_bytesToSend == 0)
{
m_dwRequestState = CgiStateDoneWithRequest;
}
}
return QueryW3Context()->SendResponse(W3_FLAG_ASYNC
| W3_FLAG_MORE_DATA
| W3_FLAG_NO_CONTENT_LENGTH
| W3_FLAG_NO_ERROR_BODY);
ErrorExit:
m_dwRequestState = CgiStateProcessingResponseHeaders;
if (FAILED(hr))
{
return hr;
}
return E_FAIL;
}
HRESULT W3_CGI_HANDLER::CGIReadRequestEntity(BOOL *pfIoPending)
/*++
Synopsis
This function reads the next chunk of request entity
Arguments
pfIoPending: On return indicates if there is an I/O pending
Return Value
HRESULT
--*/
{
if (m_bytesToReceive == 0)
{
*pfIoPending = FALSE;
return S_OK;
}
HRESULT hr;
DWORD cbRead;
if (FAILED(hr = QueryW3Context()->ReceiveEntity(
W3_FLAG_ASYNC,
m_DataBuffer,
(DWORD)min(m_bytesToReceive, sizeof(m_DataBuffer)),
&cbRead)))
{
if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF))
{
*pfIoPending = FALSE;
return S_OK;
}
DBGPRINTF((DBG_CONTEXT,
"CGIReadEntity Error reading gateway data, hr %x\n",
hr));
return hr;
}
*pfIoPending = TRUE;
return S_OK;
}
HRESULT W3_CGI_HANDLER::CGIWriteResponseEntity()
/*++
Synopsis
This function writes the next chunk of response entity
Arguments
None
Return Value
HRESULT
--*/
{
W3_RESPONSE *pResponse = QueryW3Context()->QueryResponse();
pResponse->Clear( TRUE );
//
// At this point, we got to be reading the entity
//
DBG_ASSERT(m_dwRequestState == CgiStateProcessingResponseEntity);
if (m_bytesToSend <= m_cbData)
{
m_cbData = m_bytesToSend;
m_bytesToSend = 0;
m_dwRequestState = CgiStateDoneWithRequest;
}
else
{
m_bytesToSend -= m_cbData;
}
HRESULT hr;
if (FAILED(hr = pResponse->AddMemoryChunkByReference(m_DataBuffer,
m_cbData)))
{
return hr;
}
return QueryW3Context()->SendEntity(W3_FLAG_ASYNC | W3_FLAG_MORE_DATA);
}
HRESULT W3_CGI_HANDLER::CGIReadCGIOutput()
/*++
Synopsis
This function Reads the next chunk of data from the CGI
Arguments
None
Return Value
S_OK if async I/O posted
Failure otherwise
--*/
{
if (!ReadFile(m_hStdOut,
m_DataBuffer,
MAX_CGI_BUFFERING,
NULL,
&m_Overlapped))
{
DWORD dwErr = GetLastError();
if (dwErr == ERROR_IO_PENDING)
return S_OK;
if (dwErr != ERROR_BROKEN_PIPE)
{
DBGPRINTF((DBG_CONTEXT,
"ReadFile from child stdout failed, error %d\n",
GetLastError()));
}
return HRESULT_FROM_WIN32(dwErr);
}
return S_OK;
}
HRESULT W3_CGI_HANDLER::CGIWriteCGIInput()
/*++
Synopsis
This function
Arguments
None
Return Value
HRESULT
--*/
{
if (!WriteFile(m_hStdIn,
m_DataBuffer,
m_cbData,
NULL,
&m_Overlapped))
{
if (GetLastError() == ERROR_IO_PENDING)
return S_OK;
DBGPRINTF((DBG_CONTEXT, "WriteFile failed, error %d\n",
GetLastError()));
return HRESULT_FROM_WIN32(GetLastError());
}
return S_OK;
}
HRESULT W3_CGI_HANDLER::CGIContinueOnPipeCompletion(
BOOL *pfIsCgiError)
/*++
Synopsis
This function continues on I/O completion to a pipe.
If the I/O completion was because of a write on the pipe, it reads
the next chunk of request entity, or if there is no more request entity,
reads the first chunk of CGI output.
If the I/O completion was because of a read on the pipe, it processes
that data by either adding it to the header buffer or sending it to the
client
Arguments
pfIsCgiError - Tells whether any error occurring inside this function
was the fault of the CGI or the client
Return Value
HRESULT
--*/
{
HRESULT hr;
if (m_dwRequestState == CgiStateProcessingRequestEntity)
{
BOOL fIoPending = FALSE;
if (FAILED(hr = CGIReadRequestEntity(&fIoPending)) ||
(fIoPending == TRUE))
{
*pfIsCgiError = FALSE;
return hr;
}
//
// There was no more request entity to read
//
DBG_ASSERT(fIoPending == FALSE);
//
// If this is an nph cgi, we do not parse header
//
if (QueryIsNphCgi())
{
m_dwRequestState = CgiStateProcessingResponseEntity;
}
else
{
m_dwRequestState = CgiStateProcessingResponseHeaders;
}
m_cbData = 0;
if (FAILED(hr = CGIReadCGIOutput()))
{
*pfIsCgiError = TRUE;
}
return hr;
}
else if (m_dwRequestState == CgiStateProcessingResponseHeaders)
{
if (FAILED(hr = ProcessCGIOutput()))
{
*pfIsCgiError = TRUE;
}
return hr;
}
else
{
DBG_ASSERT(m_dwRequestState == CgiStateProcessingResponseEntity);
if (FAILED(hr = CGIWriteResponseEntity()))
{
*pfIsCgiError = FALSE;
}
return hr;
}
}
HRESULT W3_CGI_HANDLER::CGIStartProcessing()
/*++
Synopsis
This function kicks off the CGI processing by reading request entity
if any or reading the first chunk of CGI output
Arguments
None
Return Value
HRESULT
--*/
{
DWORD cbAvailableAlready;
PVOID pbAvailableAlready;
m_dwRequestState = CgiStateProcessingRequestEntity;
//
// First we have to write any entity body to the program's stdin
//
// Start with the Entity Body already read
//
QueryW3Context()->QueryAlreadyAvailableEntity( &pbAvailableAlready,
&cbAvailableAlready );
m_bytesToReceive = QueryW3Context()->QueryRemainingEntityFromUl();
if ( cbAvailableAlready != 0 )
{
if (WriteFile(m_hStdIn,
pbAvailableAlready,
cbAvailableAlready,
NULL,
&m_Overlapped))
{
return S_OK;
}
if (GetLastError() == ERROR_IO_PENDING)
{
return S_OK;
}
DBGPRINTF((DBG_CONTEXT, "WriteFile failed, error %d\n",
GetLastError()));
//
// If we could not write the request entity, for example because
// the CGI did not wait to read the entity, ignore the error and
// continue on to reading the output
//
}
//
// Now continue with either reading the rest of the request entity
// or the CGI response
//
BOOL fIsCgiError;
return CGIContinueOnPipeCompletion(&fIsCgiError);
}
HRESULT W3_CGI_HANDLER::CGIContinueOnClientCompletion()
{
if (m_dwRequestState == CgiStateProcessingRequestEntity)
{
if (SUCCEEDED(CGIWriteCGIInput()))
{
return S_OK;
}
//
// If we could not write the request entity, for example because
// the CGI did not wait to read the entity, ignore the error and
// continue on to reading the output
//
//
// If this is an nph cgi, we do not parse header
//
if (QueryIsNphCgi())
{
m_dwRequestState = CgiStateProcessingResponseEntity;
}
else
{
m_dwRequestState = CgiStateProcessingResponseHeaders;
}
m_cbData = 0;
}
return CGIReadCGIOutput();
}
CONTEXT_STATUS W3_CGI_HANDLER::OnCompletion(DWORD cbCompletion,
DWORD dwCompletionStatus)
{
DBG_ASSERT(m_dwRequestState != CgiStateProcessingResponseHeaders);
HRESULT hr = S_OK;
if (dwCompletionStatus)
{
hr = HRESULT_FROM_WIN32(dwCompletionStatus);
DBGPRINTF((DBG_CONTEXT, "Error %d on CGI_HANDLER::OnCompletion\n", dwCompletionStatus));
}
//
// Is this completion for the entity body preload? If so note the
// number of bytes and start handling the CGI request
//
if (!m_fEntityBodyPreloadComplete)
{
BOOL fComplete = FALSE;
//
// This completion is for entity body preload
//
W3_REQUEST *pRequest = QueryW3Context()->QueryRequest();
hr = pRequest->PreloadCompletion(QueryW3Context(),
cbCompletion,
dwCompletionStatus,
&fComplete);
if (SUCCEEDED(hr))
{
if (!fComplete)
{
return CONTEXT_STATUS_PENDING;
}
m_fEntityBodyPreloadComplete = TRUE;
//
// Finally we can call the CGI
//
return DoWork();
}
}
//
// UL can return EOF on the async completion, rather than on the
// original call (especially on chunked requests), treat them as no error
//
if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF))
{
hr = S_OK;
}
W3_RESPONSE *pResponse = QueryW3Context()->QueryResponse();
DBG_ASSERT(pResponse != NULL);
if (SUCCEEDED(hr) &&
m_dwRequestState != CgiStateDoneWithRequest)
{
if (m_dwRequestState == CgiStateProcessingRequestEntity)
{
m_bytesToReceive -= cbCompletion;
m_cbData = cbCompletion;
}
if (SUCCEEDED(hr = CGIContinueOnClientCompletion()))
{
return CONTEXT_STATUS_PENDING;
}
if (m_dwRequestState != CgiStateProcessingResponseEntity &&
m_dwRequestState != CgiStateDoneWithRequest)
{
pResponse->SetStatus(HttpStatusBadGateway,
Http502PrematureExit);
}
}
else
{
if (hr == HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY))
{
pResponse->SetStatus(HttpStatusServerError);
}
else if ((m_dwRequestState == CgiStateProcessingRequestEntity) ||
!m_fEntityBodyPreloadComplete)
{
pResponse->SetStatus(HttpStatusBadRequest);
}
}
if (FAILED(hr))
{
QueryW3Context()->SetErrorStatus(hr);
}
if (!m_fResponseRedirected &&
m_dwRequestState != CgiStateProcessingResponseEntity &&
m_dwRequestState != CgiStateDoneWithRequest)
{
//
// If we reached here, i.e. no response was sent, status should be
// 502 or 400 or 500
//
DBG_ASSERT(pResponse->QueryStatusCode() == HttpStatusBadGateway.statusCode ||
pResponse->QueryStatusCode() == HttpStatusBadRequest.statusCode ||
pResponse->QueryStatusCode() == HttpStatusServerError.statusCode );
QueryW3Context()->SendResponse(W3_FLAG_SYNC);
}
return CONTEXT_STATUS_CONTINUE;
}
CONTEXT_STATUS
W3_CGI_HANDLER::DoWork()
{
W3_CONTEXT *pW3Context = QueryW3Context();
DBG_ASSERT( pW3Context != NULL );
W3_RESPONSE *pResponse;
W3_REQUEST *pRequest;
URL_CONTEXT *pUrlContext;
W3_METADATA *pMetaData;
HRESULT hr = S_OK;
STACK_STRU( strSSICommandLine, 256 );
STRU *pstrPhysical;
HANDLE hImpersonationToken;
HANDLE hPrimaryToken;
DWORD dwFlags = DETACHED_PROCESS;
STACK_STRU( strCmdLine, 256);
BOOL fIsCmdExe;
WCHAR * pszWorkingDir;
STACK_BUFFER( buffEnv, MAX_CGI_BUFFERING);
STACK_STRU ( strApplicationName, 256);
WCHAR * pszCommandLine = NULL;
DWORD dwFileAttributes = 0;
BOOL fImageDisabled = FALSE;
BOOL fIsVrToken;
STARTUPINFO startupinfo;
pRequest = pW3Context->QueryRequest();
DBG_ASSERT( pRequest != NULL );
pResponse = pW3Context->QueryResponse();
DBG_ASSERT( pResponse != NULL );
ZeroMemory(&startupinfo, sizeof(startupinfo));
startupinfo.cb = sizeof(startupinfo);
startupinfo.hStdOutput = INVALID_HANDLE_VALUE;
startupinfo.hStdInput = INVALID_HANDLE_VALUE;
pResponse->SetStatus( HttpStatusOk );
if (!m_fEntityBodyPreloadComplete)
{
BOOL fComplete = FALSE;
hr = pRequest->PreloadEntityBody( pW3Context,
&fComplete );
if (FAILED(hr))
{
goto Exit;
}
if (!fComplete)
{
return CONTEXT_STATUS_PENDING;
}
m_fEntityBodyPreloadComplete = TRUE;
}
DBG_ASSERT( m_fEntityBodyPreloadComplete );
pUrlContext = pW3Context->QueryUrlContext();
DBG_ASSERT( pUrlContext != NULL );
pMetaData = pUrlContext->QueryMetaData();
DBG_ASSERT( pMetaData != NULL );
if (m_pszSSICommandLine == NULL)
{
pstrPhysical = pUrlContext->QueryPhysicalPath();
DBG_ASSERT(pstrPhysical != NULL);
}
else
{
hr = strSSICommandLine.CopyA(m_pszSSICommandLine);
if (FAILED(hr))
{
goto Exit;
}
pstrPhysical = &strSSICommandLine;
}
hImpersonationToken = pW3Context->QueryImpersonationToken( &fIsVrToken );
hPrimaryToken = pW3Context->QueryPrimaryToken();
if (QueryScriptMapEntry() != NULL &&
!QueryScriptMapEntry()->QueryIsStarScriptMap())
{
STRU *pstrExe = QueryScriptMapEntry()->QueryExecutable();
//
// Check to see if script mapped CGI is enabled.
//
if ( g_pW3Server->QueryIsCgiImageEnabled( pstrExe->QueryStr() ) == FALSE )
{
DBGPRINTF(( DBG_CONTEXT,
"CGI image disabled: %S.\r\n",
pstrExe->QueryStr() ));
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
fImageDisabled = TRUE;
goto Exit;
}
if (wcschr(pstrPhysical->QueryStr(), '\"') != NULL)
{
DBGPRINTF((DBG_CONTEXT,
"Refusing request for CGI due to \" in path\n"));
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
goto Exit;
}
fIsCmdExe = IsCmdExe(pstrExe->QueryStr());
STACK_STRU (strDecodedQueryString, MAX_PATH);
if (FAILED(hr = SetupCmdLine(pRequest, &strDecodedQueryString)))
{
goto Exit;
}
STACK_BUFFER (bufCmdLine, MAX_PATH);
DWORD cchLen = pstrExe->QueryCCH() +
pstrPhysical->QueryCCH() +
strDecodedQueryString.QueryCCH();
if (!bufCmdLine.Resize(cchLen*sizeof(WCHAR) + sizeof(WCHAR)))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
cchLen = _snwprintf((LPWSTR)bufCmdLine.QueryPtr(),
cchLen,
pstrExe->QueryStr(),
pstrPhysical->QueryStr(),
strDecodedQueryString.QueryStr());
if (FAILED(hr = strCmdLine.Copy((LPWSTR)bufCmdLine.QueryPtr(),
cchLen)))
{
goto Exit;
}
}
else
{
//
// Check to see if non-script-mapped CGI is enabled
//
if ( g_pW3Server->QueryIsCgiImageEnabled( pstrPhysical->QueryStr() ) == FALSE )
{
DBGPRINTF(( DBG_CONTEXT,
"CGI image disabled: %S.\r\n",
pstrPhysical->QueryStr() ));
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
fImageDisabled = TRUE;
goto Exit;
}
fIsCmdExe = IsCmdExe(pstrPhysical->QueryStr());
if (FAILED(hr = strCmdLine.Copy(L"\"", 1)) ||
FAILED(hr = strCmdLine.Append(*pstrPhysical)) ||
FAILED(hr = strCmdLine.Append(L"\" ", 2)) ||
FAILED(hr = SetupCmdLine(pRequest, &strCmdLine)))
{
goto Exit;
}
}
if (FAILED(hr = SetupChildEnv(&buffEnv)))
{
goto Exit;
}
pszWorkingDir = pMetaData->QueryVrPath()->QueryStr();
//
// 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
//
// Also, if invoking cmd.exe for a UNC script then don't set the
// working directory. Otherwise cmd.exe will complain about
// working dir on UNC being not supported, which will destroy the
// HTTP headers.
//
if (fIsCmdExe)
{
if (ISUNC(pstrPhysical->QueryStr()))
{
pszWorkingDir = NULL;
}
DWORD i;
//
// We'll either match one of the characters or the '\0'
//
i = (DWORD)wcscspn(strCmdLine.QueryStr(), L"&|(),;%<>");
if (strCmdLine.QueryStr()[i])
{
DBGPRINTF((DBG_CONTEXT,
"Refusing request for command shell due to special characters\n"));
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
goto Exit;
}
//
// If this is a cmd.exe invocation, then ensure that the script
// does exist
//
hr = pW3Context->CheckPathInfoExists(NULL);
if (FAILED(hr))
{
goto Exit;
}
}
//
// Now check if it is an nph cgi (if not already)
//
if (!m_fIsNphCgi)
{
m_fIsNphCgi = IsNphCgi( pUrlContext->QueryUrlInfo()->QueryProcessedUrl()->QueryStr() );
}
if (m_fIsNphCgi)
{
pW3Context->SetDisconnect(TRUE);
}
//
// We specify an unnamed desktop so a new windowstation will be
// created in the context of the calling user
//
startupinfo.lpDesktop = L"";
//
// Setup the pipes information
//
if (FAILED(hr = SetupChildPipes(&m_hStdOut,
&m_hStdIn,
&startupinfo)))
{
goto Exit;
}
if (pMetaData->QueryCreateProcessNewConsole())
{
dwFlags = CREATE_NEW_CONSOLE;
}
//
// Depending what type of CGI this is (SSI command exec, Scriptmap,
// Explicit), the command line and application path are different
//
if (m_pszSSICommandLine != NULL )
{
pszCommandLine = strSSICommandLine.QueryStr();
}
else
{
if (QueryScriptMapEntry() == NULL )
{
if (FAILED(hr = MakePathCanonicalizationProof(pstrPhysical->QueryStr(),
&strApplicationName)))
{
goto Exit;
}
}
pszCommandLine = strCmdLine.QueryStr();
}
if (!pMetaData->QueryCreateProcessAsUser())
{
//
// If we are not creating the process as user, make sure to fix the
// default ACL on the token
//
if ((fIsVrToken && !pW3Context->QueryVrToken()->QueryOOPToken()) ||
(!fIsVrToken && !pW3Context->QueryUserContext()->QueryIsCachedToken()) ||
(!fIsVrToken && !pW3Context->QueryUserContext()->QueryCachedToken()->QueryOOPToken()))
{
if (FAILED(hr = AddWpgToTokenDefaultDacl(hImpersonationToken)))
{
goto Exit;
}
}
}
//
// Spawn the process and close the handles since we don't need them
//
if (!SetThreadToken(NULL, hImpersonationToken))
{
DBGPRINTF((DBG_CONTEXT,
"SetThreadToken failed, error %d\n",
GetLastError()));
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
PROCESS_INFORMATION processinfo;
BOOL fThreadsIncremented = FALSE;
if (QueryScriptMapEntry() == NULL)
{
if (ISUNC(pstrPhysical->QueryStr()))
{
ThreadPoolSetInfo( ThreadPoolIncMaxPoolThreads, 0 );
fThreadsIncremented = TRUE;
}
}
else
{
if (ISUNC(pszCommandLine))
{
ThreadPoolSetInfo( ThreadPoolIncMaxPoolThreads, 0 );
fThreadsIncremented = TRUE;
}
}
if (!pMetaData->QueryCreateProcessAsUser())
{
if (!CreateProcess(strApplicationName.QueryCCH() ? strApplicationName.QueryStr() : NULL,
pszCommandLine,
NULL, // Process security
NULL, // Thread security
TRUE, // Inherit handles
dwFlags | CREATE_UNICODE_ENVIRONMENT,
buffEnv.QueryPtr(),
pszWorkingDir,
&startupinfo,
&processinfo))
{
DBGPRINTF((DBG_CONTEXT,
"CreateProcess failed, error %d\n",
GetLastError()));
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
else
{
if (!CreateProcessAsUser(hPrimaryToken,
strApplicationName.QueryCCH() ? strApplicationName.QueryStr() : NULL,
pszCommandLine,
NULL, // Process security
NULL, // Thread security
TRUE, // Inherit handles
dwFlags | CREATE_UNICODE_ENVIRONMENT,
buffEnv.QueryPtr(),
pszWorkingDir,
&startupinfo,
&processinfo))
{
DBGPRINTF((DBG_CONTEXT,
"CreateProcessAsUser failed, error %d\n",
GetLastError()));
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
if (fThreadsIncremented)
{
ThreadPoolSetInfo( ThreadPoolDecMaxPoolThreads, 0 );
}
DBG_REQUIRE(RevertToSelf());
if (FAILED(hr))
{
goto Exit;
}
DBG_REQUIRE(CloseHandle(startupinfo.hStdInput));
startupinfo.hStdInput = INVALID_HANDLE_VALUE;
DBG_REQUIRE(CloseHandle(startupinfo.hStdOutput));
startupinfo.hStdOutput = INVALID_HANDLE_VALUE;
DBG_REQUIRE(CloseHandle(processinfo.hThread));
//
// Save the process handle in case we need to terminate it later on
//
m_hProcess = processinfo.hProcess;
//
// Schedule a callback to kill the process if it doesn't die
// in a timely manner
//
if (!CreateTimerQueueTimer(&m_hTimer,
NULL,
CGITerminateProcess,
this,
pMetaData->QueryScriptTimeout() * 1000,
0,
WT_EXECUTEONLYONCE))
{
DBGPRINTF((DBG_CONTEXT,
"CreateTimerQueueTimer failed, error %d\n",
GetLastError()));
}
if (SUCCEEDED(hr = CGIStartProcessing()))
{
return CONTEXT_STATUS_PENDING;
}
Exit:
if (startupinfo.hStdInput != INVALID_HANDLE_VALUE)
{
DBG_REQUIRE(CloseHandle(startupinfo.hStdInput));
startupinfo.hStdInput = INVALID_HANDLE_VALUE;
}
if (startupinfo.hStdOutput != INVALID_HANDLE_VALUE)
{
DBG_REQUIRE(CloseHandle(startupinfo.hStdOutput));
startupinfo.hStdOutput = INVALID_HANDLE_VALUE;
}
if (FAILED(hr))
{
switch (WIN32_FROM_HRESULT(hr))
{
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_NAME:
if (fImageDisabled)
{
pResponse->SetStatus(HttpStatusNotFound,
Http404DeniedByPolicy);
}
else
{
pResponse->SetStatus(HttpStatusNotFound);
}
break;
case ERROR_ACCESS_DENIED:
case ERROR_ACCOUNT_DISABLED:
case ERROR_LOGON_FAILURE:
pResponse->SetStatus(HttpStatusUnauthorized,
Http401Resource);
break;
case ERROR_PRIVILEGE_NOT_HELD:
pResponse->SetStatus(HttpStatusForbidden,
Http403InsufficientPrivilegeForCgi);
break;
case ERROR_NOT_ENOUGH_MEMORY:
pResponse->SetStatus(HttpStatusServerError);
break;
default:
//
// If we were not able to preload the request entity, we will
// blame the client for it
//
if (!m_fEntityBodyPreloadComplete)
{
pResponse->SetStatus(HttpStatusBadRequest);
}
else
{
pResponse->SetStatus(HttpStatusServerError);
}
}
if ( fImageDisabled )
{
pW3Context->SetErrorStatus( ERROR_ACCESS_DISABLED_BY_POLICY );
}
else
{
pW3Context->SetErrorStatus(hr);
}
}
//
// If we reached here, there was some error, response should not be 200
//
DBG_ASSERT(pResponse->QueryStatusCode() != HttpStatusOk.statusCode);
m_dwRequestState = CgiStateDoneWithRequest;
if (FAILED(hr = pW3Context->SendResponse(W3_FLAG_ASYNC)))
{
return CONTEXT_STATUS_CONTINUE;
}
return CONTEXT_STATUS_PENDING;
}
// static
VOID W3_CGI_HANDLER::KillAllCgis()
{
DBGPRINTF((DBG_CONTEXT, "W3_CGI_HANDLER::KillAllCgis() called\n"));
//
// Kill all outstanding processes
//
EnterCriticalSection(&sm_CgiListLock);
for (LIST_ENTRY *pEntry = sm_CgiListHead.Flink;
pEntry != &sm_CgiListHead;
pEntry = pEntry->Flink)
{
W3_CGI_HANDLER *pCgi = CONTAINING_RECORD(pEntry,
W3_CGI_HANDLER,
m_CgiListEntry);
CGITerminateProcess(pCgi, 0);
}
LeaveCriticalSection(&sm_CgiListLock);
}
// static
VOID W3_CGI_HANDLER::Terminate()
{
DBGPRINTF((DBG_CONTEXT, "W3_CGI_HANDLER::Terminate() called\n"));
if (sm_pEnvString != NULL)
{
delete sm_pEnvString;
sm_pEnvString = NULL;
}
DeleteCriticalSection(&sm_CgiListLock);
}