2025-04-27 07:49:33 -04:00

346 lines
11 KiB
C++

/*++
Copyright (c) 1999 Microsoft Corporation
Module Name :
CgiNative.cxx
Abstract :
This Module is used by the cgi handler to handle all the stuff
that needs to be done in native code.
Author:
Anil Ruia (anilr) 11-Aug-1999
Project:
Web Server
--*/
/************************************************************
* Include Headers
************************************************************/
#include "precomp.hxx"
#include <malloc.h>
#include <stdio.h>
/********************************************************************++
Global Entry Points for callback from XSP
++********************************************************************/
# if !defined( dllexp)
# define dllexp __declspec( dllexport)
# endif // !defined( dllexp)
/********************************************************************++
++********************************************************************/
#define BUFSIZE 0x1000
#define CGI_PEEK_INTERVAL 1000 // in milliseconds
#define CGI_TIMEOUT 30000 // in milliseconds
#define CGI_FINAL_TIMEOUT 300000 // in milliseconds
#define MAXDWORD 0xffffffff
// HTTP status codes
#define Ok 200
#define GatewayTimeout 504
#define InternalServerError 500
typedef struct _IOPipes
{
HANDLE parentstdin;
HANDLE parentstdout;
HANDLE childstdin;
HANDLE childstdout;
HANDLE processHandle;
} IOPipes;
// Function which sets up and standard input/ouput and environment for the
// child and starts it.
dllexp
BOOL
SetupIOAndStartProc(LPTSTR cmdLine, LPTSTR workingDir, LPTSTR childEnv, IOPipes *parentpipes, HANDLE userToken)
{
HANDLE hChildStdoutRd;
SECURITY_ATTRIBUTES saAttr;
// set flag so pipe handles are inherited
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Steps for redirecting child process's stdout
// 1. create anonymous pipe
// 2. create noninheritable duplicate of read handle of
// the pipe and close the inheritable handle
// Step 1
if (!CreatePipe(&hChildStdoutRd, &parentpipes->childstdout, &saAttr, 0))
{
DBGPRINTF((DBG_CONTEXT, "Stdout pipe creation failed\n"));
return FALSE;
}
// Step 2
if (!DuplicateHandle(GetCurrentProcess(), // source Process Handle
hChildStdoutRd, // source handle
GetCurrentProcess(), // target process Handle
&parentpipes->parentstdout, // target handle
0, // requested access
FALSE, // Inheritance option
DUPLICATE_SAME_ACCESS)) // optional actions
{
DBGPRINTF((DBG_CONTEXT, "Duplicate Handle Failed\n"));
return FALSE;
}
CloseHandle(hChildStdoutRd);
// Steps for redirecting child process's stdin
// 1. create anonymous pipe
// 2. create noninheritable duplicate of write handle of
// the pipe and close the inheritable handle
HANDLE hChildStdinWr;
// Step 1
if (!CreatePipe(&parentpipes->childstdin, &hChildStdinWr, &saAttr, 0))
{
DBGPRINTF((DBG_CONTEXT, "Stdin pipe creation failed\n"));
return FALSE;
}
// Step 2
if (!DuplicateHandle(GetCurrentProcess(), // source process handle
hChildStdinWr, // source handle
GetCurrentProcess(), // target process handle
&parentpipes->parentstdin, // target handle
0, // requested access
FALSE, // inheritance option
DUPLICATE_SAME_ACCESS)) // optional action
{
DBGPRINTF((DBG_CONTEXT, "Duplicate Handle Failed\n"));
return FALSE;
}
CloseHandle(hChildStdinWr);
// Set up members of STARTUPINFO structure, specifying the correct input,
// output and error handles for the child
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.dwFlags = STARTF_USESTDHANDLES;
siStartInfo.hStdInput = parentpipes->childstdin;
siStartInfo.hStdOutput = parentpipes->childstdout;
siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
// Create the child process
if(userToken == NULL)
{
if (!CreateProcess(NULL, // appName
cmdLine, // cmdLine
NULL, // processAttr
NULL, // threadAttr
TRUE, // bInherit
CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS,
// creationFlags
(LPVOID)childEnv, // envBlock
workingDir, // current directory
&siStartInfo, // startupinfo
&piProcInfo)) // processinfo
{
DBGPRINTF((DBG_CONTEXT, "Cannot create child process\n"));
return FALSE;
}
}
else
{
HANDLE procToken;
if (!DuplicateTokenEx(userToken,
MAXIMUM_ALLOWED,
NULL,
SecurityImpersonation,
TokenPrimary,
&procToken))
{
DBGPRINTF((DBG_CONTEXT, "Cannot duplicate token\n"));
return FALSE;
}
if (!CreateProcessAsUser(procToken, // User Token
NULL, // appName
cmdLine, // cmdLine
NULL, // processAttr
NULL, // threadAttr
TRUE, // bInherit
CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS,
// creationFlags
(LPVOID)childEnv, // envBlock
workingDir, // current directory
&siStartInfo, // startupinfo
&piProcInfo)) // processinfo
{
DBGPRINTF((DBG_CONTEXT, "Cannot create child process with the given token\n"));
return FALSE;
}
CloseHandle(procToken);
}
CloseHandle(piProcInfo.hThread);
parentpipes->processHandle = piProcInfo.hProcess;
return TRUE;
}
// Write out the entity body.
dllexp
BOOL
WriteOutRequest(LPBYTE data, int length, HANDLE parentstdin)
{
DWORD bytesWritten;
BOOL retval = WriteFile(parentstdin, data, length, &bytesWritten, NULL);
// Somehow the last part does not get flushed if the it does not end
// with a newline. So, if it does not, write a newline
if(data[length - 1] != '\n')
retval = retval && WriteFile(parentstdin, "\r\n", 2, &bytesWritten, NULL);
return retval;
}
// Structure used to pass back the response from the CGI script to the
// managed code
struct NativeResponse
{
LPBYTE ptr;
int length;
};
// function which gets the response from the CGI script. Right now, all
// of it is obtained and then passed to the managed code. Later, we may want
// to only obtain the headers and then the rest of the data could be obtained
// later.
dllexp
int
ReadResponse(HANDLE parentstdout, HANDLE childstdout, HANDLE procHandle, NativeResponse *respData)
{
int statusCode = Ok;
BOOL allDone = FALSE;
int totalTimeWaited = 0;
respData->ptr = NULL;
respData->length = 0;
LPBYTE data;
DWORD size = BUFSIZE, free = BUFSIZE;
LPBYTE origdata = (LPBYTE)malloc(BUFSIZE);
if (origdata == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Out of memory\n"));
statusCode = InternalServerError;
TerminateProcess(procHandle, 1);
goto Cleanup;
}
data = origdata;
while (!allDone)
{
int timeWaited = 0;
DWORD bytesToRead;
// peek from pipe to see if anything to read
PeekNamedPipe(parentstdout, NULL, 0, NULL, &bytesToRead, NULL);
// if nothing to read, keep peeking at every CGI_PEEK_INTERVAL
// until either we have waited for more than CGI_TIMEOUT ms for
// reading this chunk or for more than CGI_FINAL_TIMEOUT ms
// totally or the process terminated or we finally have something
// on the pipe
while ((bytesToRead == 0) && !allDone)
{
DWORD rc = WaitForSingleObject(procHandle, CGI_PEEK_INTERVAL);
timeWaited += CGI_PEEK_INTERVAL;
totalTimeWaited += CGI_PEEK_INTERVAL;
if ((rc == WAIT_TIMEOUT) &&
((timeWaited >= CGI_TIMEOUT) ||
(totalTimeWaited >= CGI_FINAL_TIMEOUT)))
{
TerminateProcess(procHandle, 1);
allDone = TRUE;
statusCode = GatewayTimeout;
break;
}
if (rc == WAIT_OBJECT_0) // process terminated
allDone = TRUE;
if (statusCode == Ok)
PeekNamedPipe(parentstdout, NULL, 0, NULL, &bytesToRead, NULL);
}
// Now, if there are any bytes to read, read them.
while (bytesToRead > 0)
{
DWORD numRead;
if (bytesToRead > free)
ReadFile(parentstdout, data, free, &numRead, NULL);
else
ReadFile(parentstdout, data, bytesToRead, &numRead, NULL);
data += numRead;
free -= numRead;
bytesToRead -= numRead;
if (free == 0) // need more buffer space
{
size += BUFSIZE;
origdata = (LPBYTE)realloc(origdata, size);
if (origdata == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Out of memory\n"));
// just give up at this point
allDone = TRUE;
size = 0;
statusCode = InternalServerError;
TerminateProcess(procHandle, 1);
break;
}
data = origdata + size - BUFSIZE;
free = BUFSIZE;
}
}
}
respData->ptr = origdata;
respData->length = size - free;
Cleanup:
CloseHandle(procHandle);
CloseHandle(childstdout);
CloseHandle(parentstdout);
return statusCode;
}
dllexp
void
cgiCompletionCallback(LPBYTE respData,
HANDLE parentstdin,
HANDLE childstdin)
{
free(respData);
CloseHandle(parentstdin);
CloseHandle(childstdin);
}