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.
1032 lines
27 KiB
1032 lines
27 KiB
/****************************** 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
|
|
}
|
|
|