|
|
/*++
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); }
|