1033 lines
27 KiB
C
1033 lines
27 KiB
C
/****************************** Module Header ******************************\
|
||
* Module Name: session.c
|
||
*
|
||
* Copyright (c) 1991, Microsoft Corporation
|
||
*
|
||
* Remote shell session module
|
||
*
|
||
* History:
|
||
* 06-28-92 Davidc Created.
|
||
* 05-05-94 DaveTh Modifed for RCMD service
|
||
* 02-04-99 MarkHar Fixed NTBug #287923
|
||
\***************************************************************************/
|
||
|
||
|
||
#include <nt.h>
|
||
#include <ntrtl.h>
|
||
#include <windef.h>
|
||
#include <nturtl.h>
|
||
#include <winbase.h>
|
||
|
||
#include "rcmdsrv.h"
|
||
#include <io.h>
|
||
#include <stdlib.h>
|
||
|
||
//
|
||
// Define shell command line
|
||
//
|
||
|
||
#define SHELL_COMMAND_LINE TEXT("cmd /q")
|
||
#define SHELL_COMMAND_PREFIX TEXT("cmd /c ")
|
||
#define SHELL_REMOTE_CMD_TITLE TEXT("Remote Command")
|
||
#define SHELL_CMD_PREFIX_LEN 7
|
||
|
||
//
|
||
// Define buffer size for reads/writes to/from shell
|
||
//
|
||
|
||
#define SHELL_BUFFER_SIZE 1000
|
||
|
||
//
|
||
// Home directory constants
|
||
//
|
||
|
||
#define MAX_DIRECTORY_LENGTH 255
|
||
#define ROOT_OF_C TEXT("C:\\")
|
||
|
||
//
|
||
// Define multiple wait handle table size - extra handle for service stop
|
||
//
|
||
|
||
#define NUM_WAIT_HANDLES 5
|
||
|
||
//
|
||
// Define the structure used to describe each session
|
||
//
|
||
|
||
typedef struct {
|
||
|
||
//
|
||
// These fields are filled in at session creation time
|
||
//
|
||
|
||
HANDLE ShellReadPipeHandle; // Handle to shell stdout pipe
|
||
HANDLE ShellWritePipeHandle; // Handle to shell stdin pipe
|
||
HANDLE ShellProcessHandle; // Handle to shell process
|
||
DWORD ShellProcessGroupId; // Process group ID of shell process
|
||
PCHAR DefaultDirectory; // Default directory
|
||
|
||
//
|
||
// These fields maintain the state of asynchronouse reads/writes
|
||
// to the shell process across client disconnections. They
|
||
// are initialized at session creation.
|
||
//
|
||
|
||
BYTE ShellReadBuffer[SHELL_BUFFER_SIZE]; // Data for shell reads goes here
|
||
HANDLE ShellReadAsyncHandle; // Object used for async reads from shell
|
||
|
||
BYTE ShellWriteBuffer[SHELL_BUFFER_SIZE]; // Data for shell writes goes here
|
||
HANDLE ShellWriteAsyncHandle; // Object used for async writes to shell
|
||
|
||
//
|
||
// These fields are filled in at session connect time and are only
|
||
// valid when the session is connected
|
||
//
|
||
|
||
HANDLE ClientPipeHandle; // Handle to client pipe
|
||
HANDLE SessionThreadHandle; // Handle to session thread
|
||
|
||
|
||
} SESSION_DATA, *PSESSION_DATA;
|
||
|
||
|
||
|
||
|
||
//
|
||
// Private prototypes
|
||
//
|
||
|
||
HANDLE
|
||
StartShell(
|
||
HANDLE StdinPipeHandle,
|
||
HANDLE StdoutPipeHandle,
|
||
PSESSION_DATA Session,
|
||
HANDLE TokenToUse,
|
||
PCOMMAND_HEADER LpCommandHeader
|
||
);
|
||
|
||
DWORD
|
||
SessionThreadFn(
|
||
LPVOID Parameter
|
||
);
|
||
|
||
CHAR *
|
||
GetDefaultDirectory(
|
||
void
|
||
);
|
||
//
|
||
// Useful macros
|
||
//
|
||
|
||
#define SESSION_CONNECTED(Session) ((Session)->ClientPipeHandle != NULL)
|
||
|
||
|
||
|
||
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// CreateSession
|
||
//
|
||
// Creates a new session. Involves creating the shell process and establishing
|
||
// pipes for communication with it.
|
||
//
|
||
// Returns a handle to the session or NULL on failure.
|
||
//
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
|
||
HANDLE
|
||
CreateSession(
|
||
HANDLE TokenToUse,
|
||
PCOMMAND_HEADER LpCommandHeader
|
||
)
|
||
{
|
||
PSESSION_DATA Session = NULL;
|
||
BOOL Result;
|
||
SECURITY_ATTRIBUTES SecurityAttributes;
|
||
SECURITY_DESCRIPTOR SecurityDescriptor;
|
||
HANDLE ShellStdinPipe = NULL;
|
||
HANDLE ShellStdoutPipe = NULL;
|
||
|
||
//
|
||
// Allocate space for the session data
|
||
//
|
||
|
||
Session = (PSESSION_DATA)Alloc(sizeof(SESSION_DATA));
|
||
if (Session == NULL) {
|
||
return(NULL);
|
||
}
|
||
|
||
//
|
||
// Reset fields in preparation for failure
|
||
//
|
||
|
||
Session->ShellReadPipeHandle = NULL;
|
||
Session->ShellWritePipeHandle = NULL;
|
||
Session->ShellReadAsyncHandle = NULL;
|
||
Session->ShellWriteAsyncHandle = NULL;
|
||
|
||
|
||
//
|
||
// Create the I/O pipes for the shell - give world access so that spawned
|
||
// command process can access them in client's contex
|
||
//
|
||
|
||
Result = InitializeSecurityDescriptor (
|
||
&SecurityDescriptor,
|
||
SECURITY_DESCRIPTOR_REVISION);
|
||
|
||
if (!Result) {
|
||
RcDbgPrint("Failed to initialize shell pipe security descriptor, error = %d\n",
|
||
GetLastError());
|
||
goto Failure;
|
||
}
|
||
|
||
Result = SetSecurityDescriptorDacl (
|
||
&SecurityDescriptor,
|
||
TRUE,
|
||
NULL,
|
||
FALSE);
|
||
|
||
if (!Result) {
|
||
RcDbgPrint("Failed to set shell pipe DACL, error = %d\n", GetLastError());
|
||
goto Failure;
|
||
}
|
||
|
||
SecurityAttributes.nLength = sizeof(SecurityAttributes);
|
||
SecurityAttributes.lpSecurityDescriptor = &SecurityDescriptor; // Use world-ACL
|
||
SecurityAttributes.bInheritHandle = TRUE; // Shell will inherit handles
|
||
|
||
Result = RcCreatePipe(&Session->ShellReadPipeHandle,
|
||
&ShellStdoutPipe,
|
||
&SecurityAttributes,
|
||
0, // Default pipe size
|
||
0, // Default timeout
|
||
FILE_FLAG_OVERLAPPED, // shell read flags
|
||
0 // shell stdout flags
|
||
);
|
||
if (!Result) {
|
||
RcDbgPrint("Failed to create shell stdout pipe, error = %d\n",
|
||
GetLastError());
|
||
goto Failure;
|
||
}
|
||
|
||
Result = RcCreatePipe(&ShellStdinPipe,
|
||
&Session->ShellWritePipeHandle,
|
||
&SecurityAttributes,
|
||
0, // Default pipe size
|
||
0, // Default timeout
|
||
0, // shell stdin flags
|
||
FILE_FLAG_OVERLAPPED // shell write flags
|
||
);
|
||
if (!Result) {
|
||
RcDbgPrint("Failed to create shell stdin pipe, error = %d\n",
|
||
GetLastError());
|
||
goto Failure;
|
||
}
|
||
|
||
|
||
//
|
||
// Initialize async objects
|
||
//
|
||
|
||
Session->ShellReadAsyncHandle = CreateAsync(FALSE);
|
||
if (Session->ShellReadAsyncHandle == NULL) {
|
||
RcDbgPrint("Failed to create shell read async object, error = %d\n",
|
||
GetLastError());
|
||
goto Failure;
|
||
}
|
||
|
||
Session->ShellWriteAsyncHandle = CreateAsync(FALSE);
|
||
if (Session->ShellWriteAsyncHandle == NULL) {
|
||
RcDbgPrint("Failed to create shell write async object, error = %d\n",
|
||
GetLastError());
|
||
goto Failure;
|
||
}
|
||
|
||
|
||
|
||
Session->DefaultDirectory = GetDefaultDirectory();
|
||
|
||
//
|
||
// Start command shell with pipes connection to stdin/out/err
|
||
//
|
||
|
||
Session->ShellProcessHandle = StartShell(ShellStdinPipe,
|
||
ShellStdoutPipe,
|
||
Session, TokenToUse,
|
||
LpCommandHeader);
|
||
|
||
//
|
||
// Close local handles
|
||
//
|
||
|
||
if (!(CloseHandle(ShellStdinPipe) &&
|
||
CloseHandle(ShellStdoutPipe)))
|
||
{
|
||
ShellStdinPipe = NULL;
|
||
ShellStdoutPipe = NULL;
|
||
RcDbgPrint("Failed to close local pipe handles, error = %d\n",
|
||
GetLastError());
|
||
goto Failure;
|
||
}
|
||
|
||
//
|
||
// Check result of shell start
|
||
//
|
||
|
||
if (Session->ShellProcessHandle == NULL)
|
||
{
|
||
RcDbgPrint("Failed to execute shell\n");
|
||
goto Failure;
|
||
}
|
||
|
||
//
|
||
// The session is not yet connected, initialize variables to indicate that
|
||
//
|
||
|
||
Session->ClientPipeHandle = NULL;
|
||
|
||
//
|
||
// Success, return the session pointer as a handle
|
||
//
|
||
|
||
return((HANDLE)Session);
|
||
|
||
|
||
|
||
Failure:
|
||
|
||
//
|
||
// We get here for any failure case.
|
||
// Free up any resources and exit
|
||
//
|
||
|
||
|
||
//
|
||
// Cleanup shell pipe handles
|
||
//
|
||
|
||
if (ShellStdinPipe != NULL) {
|
||
RcCloseHandle(ShellStdinPipe, "shell stdin pipe (shell side)");
|
||
}
|
||
|
||
if (ShellStdoutPipe != NULL) {
|
||
RcCloseHandle(ShellStdoutPipe, "shell stdout pipe (shell side)");
|
||
}
|
||
|
||
if (Session->ShellReadPipeHandle != NULL) {
|
||
RcCloseHandle(Session->ShellReadPipeHandle, "shell read pipe (session side)");
|
||
}
|
||
|
||
if (Session->ShellWritePipeHandle != NULL) {
|
||
RcCloseHandle(Session->ShellWritePipeHandle, "shell write pipe (session side)");
|
||
}
|
||
|
||
|
||
//
|
||
// Cleanup async data
|
||
//
|
||
|
||
if (Session->ShellReadAsyncHandle != NULL) {
|
||
DeleteAsync(Session->ShellReadAsyncHandle);
|
||
}
|
||
|
||
if (Session->ShellWriteAsyncHandle != NULL) {
|
||
DeleteAsync(Session->ShellWriteAsyncHandle);
|
||
}
|
||
|
||
|
||
//
|
||
// Free up our session data
|
||
//
|
||
|
||
Free(Session);
|
||
|
||
return(NULL);
|
||
}
|
||
|
||
|
||
CHAR *
|
||
GetDefaultDirectory(
|
||
void
|
||
)
|
||
{
|
||
CHAR *HomeDirectory = (CHAR *)malloc(MAX_PATH * sizeof(CHAR));
|
||
WIN32_FIND_DATA FileFindData;
|
||
|
||
|
||
// Local system doesn't have many env. vars.
|
||
// One that it does have is USERPROFILE. We'll try that first
|
||
if (!GetEnvironmentVariable("USERPROFILE", HomeDirectory, MAX_PATH))
|
||
{
|
||
if (!GetEnvironmentVariable("TEMP", HomeDirectory, MAX_PATH))
|
||
{
|
||
if (!GetEnvironmentVariable("TMP", HomeDirectory, MAX_PATH))
|
||
{
|
||
RcDbgPrint("Can't find USERPROFILE, TEMP, TMP: defaulting to NULL");
|
||
free(HomeDirectory);
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
if (strlen((const char *)HomeDirectory) < MAX_DIRECTORY_LENGTH)
|
||
{
|
||
RcDbgPrint("Trying to use home directory %s\n", HomeDirectory);
|
||
|
||
// Now verify that indeed the USERPROFILE directory is actually a directory
|
||
// If not, then go for NULL
|
||
if (INVALID_HANDLE_VALUE == FindFirstFile(HomeDirectory, &FileFindData))
|
||
{
|
||
free(HomeDirectory);
|
||
return NULL;
|
||
}
|
||
else
|
||
{
|
||
if (!(FileFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
||
{
|
||
free(HomeDirectory);
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
RcDbgPrint("HomeDirectory = %s\n", HomeDirectory);
|
||
|
||
return HomeDirectory;
|
||
}
|
||
else
|
||
{
|
||
RcDbgPrint("Using NULL default directory\n");
|
||
free(HomeDirectory);
|
||
return NULL;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// DeleteSession
|
||
//
|
||
// Deletes the session specified by SessionHandle.
|
||
//
|
||
// Returns nothing
|
||
//
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
|
||
VOID
|
||
DeleteSession(
|
||
HANDLE SessionHandle
|
||
)
|
||
{
|
||
PSESSION_DATA Session = (PSESSION_DATA)SessionHandle;
|
||
BOOL Result;
|
||
|
||
|
||
//
|
||
// Kill off the shell process
|
||
//
|
||
|
||
Result = TerminateProcess(Session->ShellProcessHandle, 1);
|
||
if (!Result) {
|
||
RcDbgPrint("May have failed to terminate shell, error = %d\n", GetLastError());
|
||
}
|
||
|
||
RcCloseHandle(Session->ShellProcessHandle, "shell process");
|
||
|
||
|
||
//
|
||
// Close the shell pipe handles
|
||
//
|
||
|
||
RcCloseHandle(Session->ShellReadPipeHandle, "shell read pipe (session side)");
|
||
RcCloseHandle(Session->ShellWritePipeHandle, "shell write pipe (session side)");
|
||
|
||
|
||
//
|
||
// Cleanup async data
|
||
//
|
||
|
||
DeleteAsync(Session->ShellReadAsyncHandle);
|
||
DeleteAsync(Session->ShellWriteAsyncHandle);
|
||
|
||
|
||
//
|
||
// Free up the session structure
|
||
//
|
||
|
||
Free(Session);
|
||
|
||
//
|
||
// We're done
|
||
//
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
|
||
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// ConnectSession
|
||
//
|
||
// Connects the session specified by SessionHandle to a client
|
||
// on the other end of the pipe specified by PipeHandle
|
||
//
|
||
// Returns a session disconnect notification handle or NULL on failure.
|
||
// The returned handle will be signalled if the client disconnects or the
|
||
// shell terminates.
|
||
//
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
|
||
HANDLE
|
||
ConnectSession(
|
||
HANDLE SessionHandle,
|
||
HANDLE ClientPipeHandle
|
||
)
|
||
{
|
||
PSESSION_DATA Session = (PSESSION_DATA)SessionHandle;
|
||
DWORD ThreadId;
|
||
|
||
assert(ClientPipeHandle != NULL);
|
||
|
||
//
|
||
// Fail if the session is already connected
|
||
//
|
||
|
||
if (SESSION_CONNECTED(Session)) {
|
||
RcDbgPrint("Attempted to connect session already connected\n");
|
||
return(NULL);
|
||
}
|
||
|
||
|
||
//
|
||
// Store the client pipe handle in the session structure so the thread
|
||
// can get at it. This also signals that the session is connected.
|
||
//
|
||
|
||
Session->ClientPipeHandle = ClientPipeHandle;
|
||
|
||
|
||
//
|
||
// Create the session thread
|
||
//
|
||
|
||
Session->SessionThreadHandle = CreateThread(
|
||
NULL,
|
||
0, // Default stack size
|
||
(LPTHREAD_START_ROUTINE)SessionThreadFn, // Start address
|
||
(LPVOID)Session, // Parameter
|
||
0, // Creation flags
|
||
&ThreadId // Thread id
|
||
);
|
||
if (Session->SessionThreadHandle == NULL) {
|
||
|
||
RcDbgPrint("Failed to create session thread, error = %d\n", GetLastError());
|
||
|
||
//
|
||
// Reset the client pipe handle to indicate this session is disconnected
|
||
//
|
||
|
||
Session->ClientPipeHandle = NULL;
|
||
}
|
||
|
||
return(Session->SessionThreadHandle);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// StartShell
|
||
//
|
||
// Execs the shell with the specified handle as stdin, stdout/err
|
||
//
|
||
// Returns process handle or NULL on failure
|
||
//
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
|
||
HANDLE
|
||
StartShell(
|
||
HANDLE StdinPipeHandle,
|
||
HANDLE StdoutPipeHandle,
|
||
PSESSION_DATA Session,
|
||
HANDLE TokenToUse,
|
||
PCOMMAND_HEADER LpCommandHeader
|
||
)
|
||
{
|
||
|
||
PROCESS_INFORMATION ProcessInformation;
|
||
SECURITY_DESCRIPTOR SecurityDescriptor;
|
||
STARTUPINFO si;
|
||
HANDLE ProcessHandle = NULL;
|
||
PROCESS_ACCESS_TOKEN ProcessTokenInfo;
|
||
DWORD Result;
|
||
UCHAR ShellCommandLine[MAX_CMD_LENGTH+SHELL_CMD_PREFIX_LEN+1];
|
||
|
||
//
|
||
// Detached process has no console - create new process with stdin,
|
||
// stdout set to the passed-in handles
|
||
//
|
||
|
||
|
||
si.cb = sizeof(STARTUPINFO);
|
||
si.lpReserved = NULL;
|
||
si.lpTitle = SHELL_REMOTE_CMD_TITLE;
|
||
si.lpDesktop = NULL;
|
||
si.dwX = si.dwY = si.dwXSize = si.dwYSize = 0L;
|
||
si.dwFlags = STARTF_USESTDHANDLES;
|
||
si.hStdInput = StdinPipeHandle;
|
||
si.hStdOutput = StdoutPipeHandle;
|
||
si.hStdError = StdoutPipeHandle;
|
||
si.wShowWindow = SW_SHOW;
|
||
si.lpReserved2 = NULL;
|
||
si.cbReserved2 = 0;
|
||
|
||
//
|
||
// Set command string for single command or interactive mode
|
||
//
|
||
|
||
if (LpCommandHeader->CommandFixedHeader.CommandLength == 0)
|
||
{
|
||
strcpy ((char *)ShellCommandLine, SHELL_COMMAND_LINE);
|
||
}
|
||
else
|
||
{
|
||
|
||
//
|
||
// Construct command string for execute and exit
|
||
//
|
||
|
||
strcpy ((char *)ShellCommandLine, SHELL_COMMAND_PREFIX);
|
||
strncat ((char *)ShellCommandLine,
|
||
(const char *)LpCommandHeader->Command,
|
||
LpCommandHeader->CommandFixedHeader.CommandLength);
|
||
}
|
||
|
||
//
|
||
// Create process with stdin/out/err connected to session pipes.
|
||
// Create suspended then set the process token to the token of the
|
||
// connected client (primary version of impersonate token).
|
||
//
|
||
|
||
// What if I use CreateProcessAsUser here?
|
||
|
||
if (CreateProcess(NULL,
|
||
(char *)ShellCommandLine,
|
||
NULL,
|
||
NULL,
|
||
TRUE, // Inherit handles
|
||
CREATE_NEW_PROCESS_GROUP | CREATE_SUSPENDED , // | CREATE_NEW_CONSOLE
|
||
NULL,
|
||
Session->DefaultDirectory,
|
||
&si,
|
||
&ProcessInformation))
|
||
{
|
||
|
||
ProcessHandle = ProcessInformation.hProcess;
|
||
Session->ShellProcessGroupId = ProcessInformation.dwProcessId;
|
||
|
||
}
|
||
else
|
||
{
|
||
RcDbgPrint("Failed to execute shell, error = %d\n", GetLastError());
|
||
return(NULL);
|
||
}
|
||
|
||
//
|
||
// Set process token for client and local system context access
|
||
// BUGBUG - actually, world, for now
|
||
//
|
||
|
||
Result = InitializeSecurityDescriptor (
|
||
&SecurityDescriptor,
|
||
SECURITY_DESCRIPTOR_REVISION);
|
||
if (!Result)
|
||
{
|
||
RcDbgPrint(
|
||
"Failed to initialize shell process security descriptor, error = %d\n",
|
||
GetLastError()
|
||
);
|
||
RcCloseHandle(ProcessInformation.hThread, "process thread");
|
||
return(NULL);
|
||
}
|
||
|
||
Result = SetSecurityDescriptorDacl (
|
||
&SecurityDescriptor,
|
||
TRUE,
|
||
NULL,
|
||
FALSE);
|
||
if (0 == Result) {
|
||
RcDbgPrint("Failed to initialize shell process DACL, error = %d\n", GetLastError());
|
||
RcCloseHandle(ProcessInformation.hThread, "process thread");
|
||
return(NULL);
|
||
}
|
||
|
||
Result = SetKernelObjectSecurity(
|
||
ProcessHandle,
|
||
DACL_SECURITY_INFORMATION,
|
||
&SecurityDescriptor );
|
||
if (0 == Result) {
|
||
|
||
RcDbgPrint("Failed to set DACL on client token, error = %d\n", GetLastError());
|
||
RcCloseHandle(ProcessInformation.hThread, "process thread");
|
||
return(NULL);
|
||
}
|
||
|
||
//
|
||
// Now set the process token and resume execution
|
||
//
|
||
|
||
ProcessTokenInfo.Token = TokenToUse;
|
||
ProcessTokenInfo.Thread = ProcessInformation.hThread;
|
||
|
||
if (!NT_SUCCESS( NtSetInformationProcess(
|
||
ProcessInformation.hProcess,
|
||
ProcessAccessToken,
|
||
&ProcessTokenInfo,
|
||
sizeof(ProcessTokenInfo)))) {
|
||
|
||
RcDbgPrint("Failed to set token");
|
||
RcCloseHandle(ProcessInformation.hThread, "process thread");
|
||
return (NULL);
|
||
}
|
||
|
||
|
||
Result = ResumeThread (ProcessInformation.hThread);
|
||
|
||
if (Result != 1) {
|
||
RcDbgPrint("Failed to resume shell, suspend = %d, error = %d\n", Result, GetLastError());
|
||
RcCloseHandle(ProcessInformation.hThread, "process thread");
|
||
return(NULL);
|
||
}
|
||
|
||
RcCloseHandle(ProcessInformation.hThread, "process thread");
|
||
|
||
return(ProcessHandle);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// SessionThreadFn
|
||
//
|
||
// This is the code executed by the session thread
|
||
//
|
||
// Waits for read or write from/to shell or client pipe and termination
|
||
// event. Handles reads or writes by passing data to either client or
|
||
// shell as appropriate. Any error or termination event being signalled
|
||
// causes the thread to exit with an appropriate exit code.
|
||
//
|
||
/////////////////////////////////////////////////////////////////////////////
|
||
|
||
DWORD
|
||
SessionThreadFn(
|
||
LPVOID Parameter
|
||
)
|
||
{
|
||
PSESSION_DATA Session = (PSESSION_DATA)Parameter;
|
||
HANDLE ClientReadAsyncHandle;
|
||
HANDLE ClientWriteAsyncHandle;
|
||
DWORD BytesTransferred;
|
||
DWORD CompletionCode;
|
||
BOOL Result;
|
||
DWORD WaitResult;
|
||
DWORD ExitCode;
|
||
HANDLE WaitHandles[NUM_WAIT_HANDLES];
|
||
BOOL Done;
|
||
DWORD i;
|
||
|
||
|
||
//
|
||
// Initialize the client async structures
|
||
//
|
||
|
||
ClientReadAsyncHandle = CreateAsync(TRUE);
|
||
if (ClientReadAsyncHandle == NULL) {
|
||
RcDbgPrint("Failed to create client read async object, error = %d\n", GetLastError());
|
||
return((DWORD)ConnectError);
|
||
}
|
||
|
||
ClientWriteAsyncHandle = CreateAsync(TRUE);
|
||
if (ClientWriteAsyncHandle == NULL) {
|
||
RcDbgPrint("Failed to create client write async object, error = %d\n", GetLastError());
|
||
DeleteAsync(ClientReadAsyncHandle);
|
||
return((DWORD)ConnectError);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Initialize the handle array we'll wait on
|
||
//
|
||
|
||
WaitHandles[0] = RcmdStopEvent;
|
||
WaitHandles[1] = GetAsyncCompletionHandle(Session->ShellReadAsyncHandle);
|
||
WaitHandles[2] = GetAsyncCompletionHandle(Session->ShellWriteAsyncHandle);
|
||
WaitHandles[3] = GetAsyncCompletionHandle(ClientReadAsyncHandle);
|
||
WaitHandles[4] = GetAsyncCompletionHandle(ClientWriteAsyncHandle);
|
||
|
||
//
|
||
// Wait on our handle array in a loop until an error occurs or
|
||
// we're signalled to exit.
|
||
//
|
||
|
||
Done = FALSE;
|
||
|
||
while (!Done) {
|
||
|
||
//
|
||
// Wait for one of our objects to be signalled.
|
||
//
|
||
|
||
WaitResult = WaitForMultipleObjects(NUM_WAIT_HANDLES, WaitHandles, FALSE, INFINITE);
|
||
|
||
if (WaitResult == WAIT_FAILED) {
|
||
RcDbgPrint("Session thread wait failed, error = %d\n", GetLastError());
|
||
ExitCode = (DWORD)ConnectError;
|
||
break; // out of while
|
||
}
|
||
|
||
|
||
switch (WaitResult-WAIT_OBJECT_0) {
|
||
case 0:
|
||
|
||
//
|
||
// Service being stopped
|
||
//
|
||
|
||
RcDbgPrint("Service Shutdown\n");
|
||
ExitCode = (DWORD)ServiceStopped;
|
||
Done = TRUE;
|
||
break; // out of switch
|
||
|
||
case 1:
|
||
|
||
//
|
||
// Shell read completed
|
||
//
|
||
|
||
CompletionCode = GetAsyncResult(Session->ShellReadAsyncHandle,
|
||
&BytesTransferred);
|
||
|
||
if (CompletionCode != ERROR_SUCCESS) {
|
||
RcDbgPrint("Async read from shell returned error, completion code = %d\n", CompletionCode);
|
||
ExitCode = (DWORD)ShellEnded;
|
||
Done = TRUE;
|
||
break; // out of switch
|
||
}
|
||
|
||
//
|
||
// Start an async write to client pipe
|
||
//
|
||
|
||
Result = WriteFileAsync(Session->ClientPipeHandle,
|
||
Session->ShellReadBuffer,
|
||
BytesTransferred,
|
||
ClientWriteAsyncHandle);
|
||
if (!Result) {
|
||
RcDbgPrint("Async write to client pipe failed, error = %d\n", GetLastError());
|
||
ExitCode = (DWORD)ClientDisconnected;
|
||
Done = TRUE;
|
||
}
|
||
|
||
break; // out of switch
|
||
|
||
|
||
case 4:
|
||
|
||
//
|
||
// Client write completed
|
||
//
|
||
|
||
CompletionCode = GetAsyncResult(ClientWriteAsyncHandle,
|
||
&BytesTransferred);
|
||
|
||
if (CompletionCode != ERROR_SUCCESS) {
|
||
RcDbgPrint("Async write to client returned error, completion code = %d\n", CompletionCode);
|
||
ExitCode = (DWORD)ClientDisconnected;
|
||
Done = TRUE;
|
||
break; // out of switch
|
||
}
|
||
|
||
//
|
||
// Start an async read from shell
|
||
//
|
||
|
||
Result = ReadFileAsync(Session->ShellReadPipeHandle,
|
||
Session->ShellReadBuffer,
|
||
sizeof(Session->ShellReadBuffer),
|
||
Session->ShellReadAsyncHandle);
|
||
if (!Result) {
|
||
RcDbgPrint("Async read from shell failed, error = %d\n", GetLastError());
|
||
ExitCode = (DWORD)ShellEnded;
|
||
Done = TRUE;
|
||
}
|
||
|
||
break; // out of switch
|
||
|
||
|
||
case 3:
|
||
|
||
//
|
||
// Client read completed
|
||
//
|
||
|
||
CompletionCode = GetAsyncResult(ClientReadAsyncHandle,
|
||
&BytesTransferred);
|
||
|
||
if (CompletionCode != ERROR_SUCCESS) {
|
||
RcDbgPrint("Async read from client returned error, completion code = %d\n", CompletionCode);
|
||
ExitCode = (DWORD)ClientDisconnected;
|
||
Done = TRUE;
|
||
break; // out of switch
|
||
}
|
||
|
||
//
|
||
// Check for Ctrl-C from the client
|
||
//
|
||
|
||
for (i=0; i < BytesTransferred; i++) {
|
||
if (Session->ShellWriteBuffer[i] == '\003') {
|
||
|
||
//
|
||
// Generate control-C: use Ctrl-Break because ctrl-c is
|
||
// disabled for process group
|
||
//
|
||
|
||
if (!(GenerateConsoleCtrlEvent (
|
||
CTRL_BREAK_EVENT,
|
||
Session->ShellProcessGroupId))) {
|
||
RcDbgPrint("Ctrl-break event failure, error = %d\n", GetLastError());
|
||
}
|
||
|
||
//
|
||
// Remove the Ctrl-C from the buffer
|
||
//
|
||
|
||
BytesTransferred --;
|
||
|
||
for (; i < BytesTransferred; i++) {
|
||
Session->ShellWriteBuffer[i] = Session->ShellWriteBuffer[i+1];
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Start an async write to shell
|
||
//
|
||
|
||
Result = WriteFileAsync(Session->ShellWritePipeHandle,
|
||
Session->ShellWriteBuffer,
|
||
BytesTransferred,
|
||
Session->ShellWriteAsyncHandle);
|
||
if (!Result) {
|
||
RcDbgPrint("Async write to shell failed, error = %d\n", GetLastError());
|
||
ExitCode = (DWORD)ShellEnded;
|
||
Done = TRUE;
|
||
}
|
||
|
||
break; // out of switch
|
||
|
||
|
||
|
||
case 2:
|
||
|
||
//
|
||
// Shell write completed
|
||
//
|
||
|
||
CompletionCode = GetAsyncResult(Session->ShellWriteAsyncHandle,
|
||
&BytesTransferred);
|
||
|
||
if (CompletionCode != ERROR_SUCCESS) {
|
||
RcDbgPrint("Async write to shell returned error, completion code = %d\n", CompletionCode);
|
||
ExitCode = (DWORD)ShellEnded;
|
||
Done = TRUE;
|
||
break; // out of switch
|
||
}
|
||
|
||
//
|
||
// Start an async read from client
|
||
//
|
||
|
||
Result = ReadFileAsync(Session->ClientPipeHandle,
|
||
Session->ShellWriteBuffer,
|
||
sizeof(Session->ShellWriteBuffer),
|
||
ClientReadAsyncHandle);
|
||
if (!Result) {
|
||
RcDbgPrint("Async read from client failed, error = %d\n", GetLastError());
|
||
ExitCode = (DWORD)ClientDisconnected;
|
||
Done = TRUE;
|
||
}
|
||
|
||
break; // out of switch
|
||
|
||
|
||
default:
|
||
|
||
RcDbgPrint("Session thread, unexpected result from wait, result = %d\n", WaitResult);
|
||
ExitCode = (DWORD)ConnectError;
|
||
Done = TRUE;
|
||
break;
|
||
|
||
} // switch
|
||
|
||
} // while(!done)
|
||
|
||
|
||
|
||
//
|
||
// Cleanup and exit
|
||
//
|
||
|
||
//
|
||
// Closing the client pipe should interrupt any pending I/O so
|
||
// we should then be safe to close the event handles in the client
|
||
// overlapped structs
|
||
//
|
||
|
||
Result = DisconnectNamedPipe(Session->ClientPipeHandle);
|
||
if (!Result) {
|
||
RcDbgPrint("Session thread: disconnect client named pipe failed, error = %d\n", GetLastError());
|
||
}
|
||
|
||
RcCloseHandle(Session->ClientPipeHandle, "client pipe");
|
||
Session->ClientPipeHandle = NULL;
|
||
|
||
//
|
||
// Delete client async objects
|
||
//
|
||
|
||
DeleteAsync(ClientReadAsyncHandle);
|
||
DeleteAsync(ClientWriteAsyncHandle);
|
||
|
||
//
|
||
// Terminate shell process, close shell pipe handles, and free session
|
||
// structure
|
||
//
|
||
|
||
DeleteSession (Session);
|
||
|
||
|
||
//
|
||
// Return the appropriate exit code
|
||
//
|
||
|
||
ExitThread(ExitCode);
|
||
|
||
assert(FALSE);
|
||
return(ExitCode); // keep compiler happy
|
||
}
|
||
|