Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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
}