/****************************** Module Header ******************************\
* Module Name: session.c
*
* Copyright (c) 1991, Microsoft Corporation
*
* Remote shell session module
*
* History:
* 06-28-92 Davidc       Created.
\***************************************************************************/

#include "rcmdsrv.h"

#include <io.h>

//
// Global pointer to generate console ctrl event fn
// Dynamically link to this api so exe will run
// on pre build-304 systems.
//

typedef BOOL (APIENTRY * GENERATE_CONSOLE_CTRL_EVENT_FN)(DWORD dwCtrlEvent,
                                                         DWORD dwProcessGroupId);

static GENERATE_CONSOLE_CTRL_EVENT_FN GenerateConsoleCtrlEventfn = NULL;

#define GENERATE_CONSOLE_CTRL_EVENT_MODULE  TEXT("kernel32.dll")
#define GENERATE_CONSOLE_CTRL_EVENT_NAME  "GenerateConsoleCtrlEvent"

//
// Define standard handles
//

#define STDIN    0
#define STDOUT   1
#define STDERROR 2

//
// Define shell command line
//

#define SHELL_COMMAND_LINE  TEXT("cmd /q")

//
// Define buffer size for reads/writes to/from shell
//

#define SHELL_BUFFER_SIZE   1000


//
// 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

    //
    // 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
    BOOL    ShellReadPending;

    BYTE    ShellWriteBuffer[SHELL_BUFFER_SIZE]; // Data for shell writes goes here
    HANDLE  ShellWriteAsyncHandle; // Object used for async writes to shell
    BOOL    ShellWritePending;

    //
    // 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
    HANDLE  SessionThreadSignalEventHandle; // Handle to event used to signal thread


} SESSION_DATA, *PSESSION_DATA;




//
// Private prototypes
//

HANDLE
StartShell(
    int StdinCrtHandle,
    int StdoutCrtHandle
    );

DWORD
SessionThreadFn(
    LPVOID Parameter
    );


//
// 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(
    VOID
    )
{
    PSESSION_DATA Session = NULL;
    BOOL Result;
    SECURITY_ATTRIBUTES SecurityAttributes;
    HANDLE ShellStdinPipe = NULL;
    HANDLE ShellStdoutPipe = NULL;
    int ShellStdinCrtHandle;
    int ShellStdoutCrtHandle;

    //
    // 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
    //

    SecurityAttributes.nLength = sizeof(SecurityAttributes);
    SecurityAttributes.lpSecurityDescriptor = NULL; // Use default ACL
    SecurityAttributes.bInheritHandle = TRUE; // Shell will inherit handles

    Result = MyCreatePipe(&Session->ShellReadPipeHandle,
                          &ShellStdoutPipe,
                          &SecurityAttributes,
                          0,            // Default pipe size
                          0,            // Default timeout
                          FILE_FLAG_OVERLAPPED, // shell read flags
                          0              // shell stdout flags
                          );
    if (!Result) {
        DbgPrint("Failed to create shell stdout pipe, error = %d\n", GetLastError());
        goto Failure;
    }

    Result = MyCreatePipe(&ShellStdinPipe,
                          &Session->ShellWritePipeHandle,
                          &SecurityAttributes,
                          0,            // Default pipe size
                          0,            // Default timeout
                          0,            // shell stdin flags
                          FILE_FLAG_OVERLAPPED // shell write flags
                          );
    if (!Result) {
        DbgPrint("Failed to create shell stdin pipe, error = %d\n", GetLastError());
        goto Failure;
    }


    //
    // Initialize async objects
    //

    Session->ShellReadAsyncHandle = CreateAsync(FALSE);
    if (Session->ShellReadAsyncHandle == NULL) {
        DbgPrint("Failed to create shell read async object, error = %d\n", GetLastError());
        goto Failure;
    }

    Session->ShellWriteAsyncHandle = CreateAsync(FALSE);
    if (Session->ShellWriteAsyncHandle == NULL) {
        DbgPrint("Failed to create shell write async object, error = %d\n", GetLastError());
        goto Failure;
    }

    Session->ShellReadPending = FALSE;
    Session->ShellWritePending = FALSE;


    //
    // Create a runtime handle for shell pipes
    //

    ShellStdinCrtHandle = _open_osfhandle((long)ShellStdinPipe, 0);
    assert(ShellStdinCrtHandle != -1);
    ShellStdoutCrtHandle = _open_osfhandle((long)ShellStdoutPipe, 0);
    assert(ShellStdoutCrtHandle != -1);


    //
    // Start the shell
    //

    Session->ShellProcessHandle = StartShell(ShellStdinCrtHandle, ShellStdoutCrtHandle);

    //
    // We're finished with our copy of the shell pipe handles
    // Closing the runtime handles will close the pipe handles for us.
    //

    close(ShellStdinCrtHandle);
    ShellStdinPipe = NULL;
    close(ShellStdoutCrtHandle);
    ShellStdoutPipe = NULL;

    //
    // Check result of shell start
    //

    if (Session->ShellProcessHandle == NULL) {
        DbgPrint("Failed to execute shell\n");
        goto Failure;
    }


    //
    // Get the address of the GenerateConsoleCtrlEvent function
    // if it's available
    //

    if (GenerateConsoleCtrlEventfn == NULL) {

        HANDLE hMod = LoadLibrary(GENERATE_CONSOLE_CTRL_EVENT_MODULE);

        if (hMod != NULL) {

            GenerateConsoleCtrlEventfn = (GENERATE_CONSOLE_CTRL_EVENT_FN)
                    GetProcAddress(hMod, GENERATE_CONSOLE_CTRL_EVENT_NAME);

            if (GenerateConsoleCtrlEventfn == NULL) {
                DbgPrint("Failed to get address of %s function\n", GENERATE_CONSOLE_CTRL_EVENT_NAME);
            }

            FreeLibrary(hMod);

        } else {
            DbgPrint("Load library failed on kernel32.dll!, error = %d\n", GetLastError());
        }
    }


    //
    // If any code is added here, remember to cleanup process handle
    // in failure code
    //


    //
    // The session is not 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) {
        MyCloseHandle(ShellStdinPipe, "shell stdin pipe (shell side)");
    }

    if (ShellStdoutPipe != NULL) {
        MyCloseHandle(ShellStdoutPipe, "shell stdout pipe (shell side)");
    }

    if (Session->ShellReadPipeHandle != NULL) {
        MyCloseHandle(Session->ShellReadPipeHandle, "shell read pipe (session side)");
    }

    if (Session->ShellWritePipeHandle != NULL) {
        MyCloseHandle(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);
}




/////////////////////////////////////////////////////////////////////////////
//
// DeleteSession
//
// Deletes the session specified by SessionHandle.
//
// Returns nothing
//
/////////////////////////////////////////////////////////////////////////////

VOID
DeleteSession(
    HANDLE  SessionHandle
    )
{
    PSESSION_DATA   Session = (PSESSION_DATA)SessionHandle;
    BOOL Result;

    //
    // Disconnect session first
    //

    if (SESSION_CONNECTED(Session)) {
        DisconnectSession(SessionHandle);
    }


    //
    // Kill off the shell process
    //

    Result = TerminateProcess(Session->ShellProcessHandle, 1);
    if (!Result) {
        DbgPrint("Failed to terminate shell, error = %d\n", GetLastError());
    }

    MyCloseHandle(Session->ShellProcessHandle, "shell process");


    //
    // Close the shell pipe handles
    //

    MyCloseHandle(Session->ShellReadPipeHandle, "shell read pipe (session side)");
    MyCloseHandle(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.
// Calling DisconnectSession will return the disconnect notification code.
//
/////////////////////////////////////////////////////////////////////////////

HANDLE
ConnectSession(
    HANDLE  SessionHandle,
    HANDLE  ClientPipeHandle
    )
{
    PSESSION_DATA   Session = (PSESSION_DATA)SessionHandle;
    SECURITY_ATTRIBUTES SecurityAttributes;
    DWORD ThreadId;

    assert(ClientPipeHandle != NULL);

    //
    // Fail if the session is already connected
    //

    if (SESSION_CONNECTED(Session)) {
        DbgPrint("Attempted to connect session already connected\n");
        return(NULL);
    }

    //
    // Create the thread signal event. We'll use this to tell the
    // thread to exit during disconnection.
    //

    SecurityAttributes.nLength = sizeof(SecurityAttributes);
    SecurityAttributes.lpSecurityDescriptor = NULL; // Use default ACL
    SecurityAttributes.bInheritHandle = FALSE; // No inheritance

    Session->SessionThreadSignalEventHandle = CreateEvent(&SecurityAttributes,
                                                      TRUE, // Manual reset
                                                      FALSE, // Initially clear
                                                      NULL); // No name
    if (Session->SessionThreadSignalEventHandle == NULL) {
        DbgPrint("Failed to create thread signal event, error = %d\n", GetLastError());
        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(
                                     &SecurityAttributes,
                                     0,                 // Default stack size
             (LPTHREAD_START_ROUTINE)SessionThreadFn,   // Start address
                             (LPVOID)Session,           // Parameter
                                     0,                 // Creation flags
                                     &ThreadId          // Thread id
                                     );
    if (Session->SessionThreadHandle == NULL) {

        DbgPrint("Failed to create session thread, error = %d\n", GetLastError());

        //
        // Close the thread signal event
        //

        MyCloseHandle(Session->SessionThreadSignalEventHandle, "thread signal event");

        //
        // Reset the client pipe handle to indicate this session is disconnected
        //

        Session->ClientPipeHandle = NULL;
    }


    return(Session->SessionThreadHandle);
}




/////////////////////////////////////////////////////////////////////////////
//
// DisconnectSession
//
// Disconnects the session specified by SessionHandle for its client.
//
// Returns a disconnect notification code (DisconnectError on failure)
//
/////////////////////////////////////////////////////////////////////////////

SESSION_DISCONNECT_CODE
DisconnectSession(
    HANDLE  SessionHandle
    )
{
    PSESSION_DATA   Session = (PSESSION_DATA)SessionHandle;
    DWORD TerminationCode;
    SESSION_DISCONNECT_CODE DisconnectCode;
    BOOL Result;
    DWORD WaitResult;

    //
    // Signal the thread to terminate (if it hasn't already)
    //

    Result = SetEvent(Session->SessionThreadSignalEventHandle);
    if (!Result) {
        DbgPrint("Failed to set thread signal event, error = %d\n", GetLastError());
    }

    //
    // Wait for the thread to terminate
    //

    DbgPrint("Waiting for session thread to terminate...");

    WaitResult = WaitForSingleObject(Session->SessionThreadHandle, INFINITE);
    if (WaitResult != 0) {
        DbgPrint("Unexpected result from infinite wait on thread handle, result = %d\n", WaitResult);
    }

    DbgPrint("done\n");


    //
    // Get the thread termination code
    //

    Result = GetExitCodeThread(Session->SessionThreadHandle, &TerminationCode);
    if (!Result) {
        DbgPrint("Failed to get termination code for thread, error = %d\n", GetLastError());
        TerminationCode = (DWORD)DisconnectError;
    } else {
        if (TerminationCode == STILL_ACTIVE) {
            DbgPrint("Got termination code for thread, it's still active!\n");
            TerminationCode = (DWORD)DisconnectError;
        }
    }

    DisconnectCode = (SESSION_DISCONNECT_CODE)TerminationCode;



    //
    // Close the thread handle and thread signal event handle
    //

    MyCloseHandle(Session->SessionThreadHandle, "session thread");
    MyCloseHandle(Session->SessionThreadSignalEventHandle, "thread signal event");


    //
    // Reset the client pipe handle to signal that this session is disconnected
    // The pipe handle will have been closed by the session thread on exit
    //

    Session->ClientPipeHandle = NULL;


    //
    // We're done
    //

    return(DisconnectCode);
}








/////////////////////////////////////////////////////////////////////////////
//
// StartShell
//
// Execs the shell with the specified handle as stdin, stdout/err
//
// Returns process handle or NULL on failure
//
/////////////////////////////////////////////////////////////////////////////

HANDLE
StartShell(
    int ShellStdinCrtHandle,
    int ShellStdoutCrtHandle
    )
{
    int StdInputHandle;
    int StdOutputHandle;
    int StdErrorHandle;
    int crtResult;
    PROCESS_INFORMATION ProcessInformation;
    STARTUPINFO si;
    HANDLE ProcessHandle = NULL;


    //
    // Replace std handles with appropriate pipe handles and exec the
    // shell process. It will inherit our std handles and we can then
    // reset them to normal
    //


    //
    // Store away our normal i/o handles
    //

    StdInputHandle = dup(STDIN);
    assert(StdInputHandle != -1);
    StdOutputHandle = dup(STDOUT);
    assert(StdOutputHandle != -1);
    StdErrorHandle = dup(STDERROR);
    assert(StdErrorHandle != -1);

    //
    // Replace std handles with pipe handle.
    //

    crtResult = dup2(ShellStdinCrtHandle, STDIN);
    assert(crtResult == 0);
    crtResult = dup2(ShellStdoutCrtHandle, STDOUT);
    assert(crtResult == 0);
    crtResult = dup2(ShellStdoutCrtHandle, STDERROR);
    assert(crtResult == 0);

    //
    // Initialize process startup info
    //

    si.cb = sizeof(STARTUPINFO);
    si.lpReserved = NULL;
    si.lpTitle = NULL;
    si.lpDesktop = NULL;
    si.dwX = si.dwY = si.dwXSize = si.dwYSize = si.dwFlags = 0L;
    si.wShowWindow = SW_SHOW;
    si.lpReserved2 = NULL;
    si.cbReserved2 = 0;

    if (CreateProcess(NULL,
                      SHELL_COMMAND_LINE,
                      NULL,
                      NULL,
                      TRUE, // Inherit handles
                      0,
                      NULL,
                      NULL,
                      &si,
                      &ProcessInformation)) {

        ProcessHandle = ProcessInformation.hProcess;
        MyCloseHandle(ProcessInformation.hThread, "process thread");

    } else {
        DbgPrint("Failed to execute shell, error = %d\n", GetLastError());
    }



    //
    // Restore std handles to normal
    //

    crtResult = dup2(StdInputHandle, STDIN);
    assert(crtResult == 0);
    crtResult = dup2(StdOutputHandle, STDOUT);
    assert(crtResult == 0);
    crtResult = dup2(StdErrorHandle, STDERROR);
    assert(crtResult == 0);

    //
    // Close any handles we created
    //

    crtResult = close(StdInputHandle);
    assert(crtResult == 0);
    crtResult = close(StdOutputHandle);
    assert(crtResult == 0);
    crtResult = close(StdErrorHandle);
    assert(crtResult == 0);


    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[5];
    BOOL    Done;
    DWORD   i;

    if (Session->ShellWritePending) {
        printf("SessionThread started - SHELL-WRITE-PENDING\n");
    }
    if (Session->ShellReadPending) {
        printf("SessionThread started - SHELL-READ-PENDING\n");
    }

    //
    // Initialize the client async structures
    //

    ClientReadAsyncHandle = CreateAsync(!Session->ShellWritePending);
    if (ClientReadAsyncHandle == NULL) {
        DbgPrint("Failed to create client read async object, error = %d\n", GetLastError());
        return((DWORD)ConnectError);
    }

    ClientWriteAsyncHandle = CreateAsync(!Session->ShellReadPending);
    if (ClientWriteAsyncHandle == NULL) {
        DbgPrint("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] = Session->SessionThreadSignalEventHandle;
    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(5, WaitHandles, FALSE, INFINITE);

        if (WaitResult == 0xffffffff) {
            DbgPrint("Session thread wait failed, error = %d\n", GetLastError());
            ExitCode = (DWORD)ConnectError;
            break; // out of while
        }


        switch (WaitResult) {
        case 0:

            //
            // Our thread was signalled
            //
            ExitCode = (DWORD)ClientDisconnected;
            Done = TRUE;
            break; // out of switch

        case 1:

            //
            // Shell read completed
            //

            Session->ShellReadPending = FALSE;

            CompletionCode = GetAsyncResult(Session->ShellReadAsyncHandle,
                                            &BytesTransferred);

            if (CompletionCode != ERROR_SUCCESS) {
                DbgPrint("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) {
                DbgPrint("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) {
                DbgPrint("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) {
                DbgPrint("Async read from shell failed, error = %d\n", GetLastError());
                ExitCode = (DWORD)ShellEnded;
                Done = TRUE;
            } else {
                Session->ShellReadPending = TRUE;
            }

            break; // out of switch


        case 3:

            //
            // Client read completed
            //

            CompletionCode = GetAsyncResult(ClientReadAsyncHandle,
                                            &BytesTransferred);

            if (CompletionCode != ERROR_SUCCESS) {
                DbgPrint("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 a Ctrl-C if we have the technology
                    //

                    if (GenerateConsoleCtrlEventfn != NULL) {
                        (*GenerateConsoleCtrlEventfn)(CTRL_C_EVENT, 0);
                    }

                    //
                    // 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) {
                DbgPrint("Async write to shell failed, error = %d\n", GetLastError());
                ExitCode = (DWORD)ShellEnded;
                Done = TRUE;
            } else {
                Session->ShellWritePending = TRUE;
            }

            break; // out of switch



        case 2:

            //
            // Shell write completed
            //

            Session->ShellWritePending = FALSE;

            CompletionCode = GetAsyncResult(Session->ShellWriteAsyncHandle,
                                            &BytesTransferred);

            if (CompletionCode != ERROR_SUCCESS) {
                DbgPrint("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) {
                DbgPrint("Async read from client failed, error = %d\n", GetLastError());
                ExitCode = (DWORD)ClientDisconnected;
                Done = TRUE;
            }

            break; // out of switch


        default:

            DbgPrint("Session thread, unexpected result from wait, result = %d\n", WaitResult);
            ExitCode = (DWORD)ConnectError;
            Done = TRUE;
            break;

        }
    }



    //
    // 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) {
        DbgPrint("Session thread: disconnect client named pipe failed, error = %d\n", GetLastError());
    }

    MyCloseHandle(Session->ClientPipeHandle, "client pipe");
    Session->ClientPipeHandle = NULL;


    DeleteAsync(ClientReadAsyncHandle);
    DeleteAsync(ClientWriteAsyncHandle);


    //
    // Return the appropriate exit code
    //

    ExitThread(ExitCode);

    assert(FALSE);
    return(ExitCode); // keep compiler happy
}

