/****************************** Module Header ******************************\
* Module Name: rcmd.c
*
* Copyright (c) 1991, Microsoft Corporation
*
* Remote shell NT client main module
*
* History:
* 05-20-92 Davidc       Created.
* 05-01-94 DaveTh       Modified for remote command service (single cmd mode)
\***************************************************************************/

// #define UNICODE	// BUGBUG - Unicode support not complete

#include <nt.h>
#include <ntrtl.h>
#include <windef.h>
#include <nturtl.h>
#include <windows.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <assert.h>
#include <conio.h>

#include <rcmd.h>

#define PIPE_NAME   TEXT("%hs\\pipe\\rcmdsvc")
#define BUFFER_SIZE 1000
#define MAX_SERVER_NAME_LENGTH	15

//
// Globals
//

HANDLE PipeHandle = NULL;	    // These are used by Ctrl-C handler
BOOLEAN SessionConnected = FALSE;
BOOLEAN MultiServerMode = FALSE;
HANDLE ReadThreadHandle;
HANDLE WriteThreadHandle;

BOOLEAN RcDbgPrintEnable = FALSE;   // If TRUE, enables DbgPrint

LPTSTR    ServerName = NULL;	    // Name of remote server, for messages


//
// Private prototypes
//

DWORD
ReadThreadProc(
    LPVOID Parameter
    );

DWORD
WriteThreadProc(
    LPVOID Parameter
    );

BOOL
CtrlHandler(
    DWORD CtrlType
    );

int RcPrintf (
    const char *format,
    ...
    );

int RcDbgPrint (
    const char *format,
    ...
    );

void Usage(
    void
    );

long ParseCommandLine(
    LPTSTR lpszServer,
    COMMAND_HEADER *chCommandHeader,
    LPTSTR *aszArgv,
    int iArgc
    );


void
Usage(
    void
    )
/*++

Routine Description:

    Prints a usage message and exits the program

Arguments:

    None

Return Value:

    void

--*/
{
	RcPrintf("\nUsage: rcmd [server_name [command] ]\n\n");
	RcPrintf("Prompts for server_name if  not supplied.   Session is\n");
	RcPrintf("interactive and is terminated by ctrl-Break or Exit of\n");
	RcPrintf("remote shell.   Program is terminated by ctrl-Break or\n");
	RcPrintf("ctrl-C when no session is in progress.\n\n");
	RcPrintf("If no command supplied,  session is interactive and is\n");
	RcPrintf("terminated by ctrl-Break  or Exit  of remote cmd shell\n\n");
	RcPrintf("If command is supplied,  remote shell executes  single\n");
	RcPrintf("command on specified server and exits.\n\n");
	RcPrintf("Note : Command line server_name requires leading '\\\\'s\n");

    exit(0);
}



LONG ParseCommandLine(
    LPTSTR lpszServer,
    COMMAND_HEADER *chCommandHeader,
    LPTSTR *aszArgv,
    int iArgc
    )
/*++

Routine Description:

    Parses command line of the form:
    rcmd [server_name [[command] | ["command"]]

Arguments:

    lpszServer      - on exit gets the name of the server from the command line
    chCommandHeader - information to pass to rcmdsvc on exit
    aszArgv         - array of zero terminated strings (passed in from command line)
    iArgc           - number of strings in argv (passed in from command line)

Return Value:

    LONG

--*/
{

    LPTSTR buf = NULL;
    LONG nChars = 0;
    int i;

    //
    // get the first argument (either the server name or a [-/][?hH])
    //
    if (iArgc > 1)
    {
        //
        // convert argument to lower case
        //
        CharLowerBuff(aszArgv[1], lstrlen(aszArgv[1]));

        //
        // check for switch (only ?Hh are valid)
        //
        if ((*aszArgv[1] == TEXT('-')) ||
            (*aszArgv[1] == TEXT('/'))) {
            //
            // check the switch
            //
            if ( (aszArgv[1][1] == TEXT('h')) ||
                 (aszArgv[1][1] == TEXT('?')) ) {
                Usage();
            }
            else {
                RcPrintf(TEXT("Unknown switch %s\n"), aszArgv[1]);
                Usage();
            }
        }
        else if ( (*aszArgv[1] == TEXT('\\'))  && (aszArgv[1][1] == TEXT('\\'))) {
            //
            // first argument is a server name
            //
            lstrcpy(lpszServer, aszArgv[1]);
        }
        else {
            //
            // usage error
            //
            Usage();
        }

    }
    else {
        //
        // user failed to enter a server name
        // default to local machine
        //
        lstrcpy(lpszServer, "\\\\.");
    }

    //
    // If user entered anything beyond the server name, save it for passing to
    // to rcmdsvc.
    //

    // init
    chCommandHeader->CommandFixedHeader.CommandLength = 0;
    buf = chCommandHeader->Command;
    buf[0] = TEXT('\0');
    for (i = 2; i < iArgc; i++) {
        //
        // append each argument to saved command line
        //
        if (NULL != strchr(aszArgv[i], ' '))
        {
            nChars = wsprintf(buf, "\"%s\" ", aszArgv[i]);
        }
        else
        {
            nChars = wsprintf(buf, "%s ", aszArgv[i]);
        }
        buf += nChars;
        chCommandHeader->CommandFixedHeader.CommandLength += nChars;
    }
    //
    // in case we went too far, truncate the string
    //
    chCommandHeader->Command[MAX_CMD_LENGTH] = TEXT('\0');

    return (long)iArgc;
}



/***************************************************************************\
* FUNCTION: Main
*
* PURPOSE:  Main entry point.
*
* RETURNS:  0 on success, 1 on failure
*
* HISTORY:
*
*   07-10-92 Davidc       Created.
*
\***************************************************************************/

int
__cdecl main(
    int argc,
    char **argv
    )
{

    SECURITY_ATTRIBUTES SecurityAttributes;
    HANDLE StdInputHandle;
    HANDLE StdOutputHandle;
    HANDLE StdErrorHandle;
    CHAR  PipeName[MAX_PATH];
    //WCHAR  PipeName[MAX_PATH];
    DWORD ThreadId;
    HANDLE HandleArray[2];
    COMMAND_HEADER CommandHeader;
    RESPONSE_HEADER ResponseHeader;
    DWORD BytesWritten, BytesRead;
    DWORD Result;
    CHAR ServerNameBuffer[MAX_SERVER_NAME_LENGTH+3];  // +3 for gets counts, null
    CHAR FullServerNameBuffer[MAX_SERVER_NAME_LENGTH+3];  // +3 for "\\", null
    LONG nArgs = 0;
    BOOLEAN bBadServerName = TRUE;

    //
    // Install a handler for Ctrl-C
    //

    if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE) &CtrlHandler, TRUE)) {
    	RcPrintf("Error:1 - Internal error = %d\n", GetLastError());
    	return(1);
    }

    //
    // Command line parsing
    //
    nArgs = ParseCommandLine(FullServerNameBuffer, &CommandHeader, argv, argc);
    ServerName = FullServerNameBuffer;

    if (nArgs == 1)  {
    	MultiServerMode = TRUE;
        ServerNameBuffer[0] = MAX_SERVER_NAME_LENGTH;
       	FullServerNameBuffer[0] = '\\';
        FullServerNameBuffer[1] = '\\';
        }

    //
    //  Loop for MultServerMode case (will return appropriately if not)
    //

    while (TRUE) {

        //
        //	If MultiServerMode, prompt for server name until it's right (enough)
        //
        while (MultiServerMode) {

            //
            //  BUGBUG - call netapi to validate server name
            //

            RcPrintf("\nEnter Server Name : ");
            FullServerNameBuffer[2] = '\0';  // re-terminate "\\" string
            ServerNameBuffer[0] = MAX_SERVER_NAME_LENGTH;
            ServerName = strcat(FullServerNameBuffer, _cgets(ServerNameBuffer));

            if (strlen(ServerName) < 3) {
                RcPrintf("\nError - Invalid Server Name\n");
            } else {
                break;	// valid name, go on
            }
        }


        //
        // Construct server pipe name
        //

        wsprintf(PipeName, PIPE_NAME, ServerName);


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

        if (((StdInputHandle = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) ||
            ((StdOutputHandle = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE) ||
            ((StdErrorHandle = GetStdHandle(STD_ERROR_HANDLE)) == INVALID_HANDLE_VALUE))  {

            RcPrintf("Error:2 - Internal error = %d\n", GetLastError());
            return(1);  // catastrophic error - exit
        }


        //
        // Open the named pipe - need security flags to pass all privileges, not
        // just effective during impersonation
        //

        SecurityAttributes.nLength = sizeof(SecurityAttributes);
        SecurityAttributes.lpSecurityDescriptor = NULL; // Use default SD
        SecurityAttributes.bInheritHandle = FALSE;

        PipeHandle = CreateFile(PipeName,                     // pipe to server
                         GENERIC_READ | GENERIC_WRITE, // read/write
                         0,                            // No sharing
                         &SecurityAttributes,          // default Security Descriptor
                         OPEN_EXISTING,                // open existing pipe if it exists
                         FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED |
                         SECURITY_SQOS_PRESENT |
                         SECURITY_IMPERSONATION | SECURITY_CONTEXT_TRACKING,
                         NULL                       // Template file
                         );

        if (PipeHandle == INVALID_HANDLE_VALUE ) {
    	    Result = GetLastError();
    	    RcDbgPrint("Failed to open named pipe, error = %d\n", Result);

        	switch (Result) {

        	case ERROR_FILE_NOT_FOUND:
        	    RcPrintf("Error - Failed to connect to <%s>, system not found or service not active\n", ServerName);
        	    break;

        	case ERROR_PIPE_BUSY:
        	    RcPrintf("Error - Failed to connect to <%s>, remote command server busy\n", ServerName);
        	    break;

        	default:
        	    RcPrintf("Error - Failed to connect to <%s>, Error = %d\n", ServerName, GetLastError());

    	    }
            goto ServerError;
        }

        //
        //  Send command header - if single command mode, send command for
        //  excecute and return.  else 0 length indicates no command present
        //  Specify BASIC level support desired.
        //

        CommandHeader.CommandFixedHeader.Signature = RCMD_SIGNATURE;
        CommandHeader.CommandFixedHeader.RequestedLevel =
            RC_LEVEL_REQUEST | RC_LEVEL_BASIC;

        if (!WriteFile(
             PipeHandle,
    	     &CommandHeader,
    	     sizeof(CommandHeader.CommandFixedHeader) +
    		 CommandHeader.CommandFixedHeader.CommandLength,
    	     &BytesWritten,
    	     NULL ))  {
    	RcPrintf("Error - Failed to send to remote command server, Error = %ld\n", GetLastError());
    	goto ServerError;
        }

        //
        //  Get response header.  Will specify reported level or any error.
        //

        if ((!ReadFile(
    	    PipeHandle,
    	    &ResponseHeader,
    	    sizeof(ResponseHeader),
    	    &BytesRead,
    	    NULL)) || (BytesRead != sizeof(ResponseHeader)))  {

    	RcPrintf("Error - Remote command server failed to respond, Error = %ld\n", GetLastError());
    	goto ServerError;
        }

        if (ResponseHeader.Signature != RCMD_SIGNATURE)  {
    	RcPrintf("Error - Incompatible remote command server\n");
    	goto ServerError;
        }

        //
        //  Check for returned errors or supported level
        //

        if (!(ResponseHeader.SupportedLevel ==
    	    (RC_LEVEL_RESPONSE | RC_LEVEL_BASIC)))  {

    	if (ResponseHeader.SupportedLevel & RC_ERROR_RESPONSE)  {

    	    //
    	    //  Error returned
    	    //

    	    switch  (ResponseHeader.SupportedLevel & ~RC_ERROR_RESPONSE)	 {

    	    case ERROR_ACCESS_DENIED:
    		RcPrintf("Error - You have insufficient access on the remote system\n");
    		break;

    	    default:
    		RcPrintf("Error - Failed to establish remote session, Error = %d\n",
    		    (ResponseHeader.SupportedLevel & ~RC_ERROR_RESPONSE));
    		break;

    	    } //switch

    	    goto ServerError;

    	} else if (ResponseHeader.SupportedLevel & RC_LEVEL_RESPONSE)  {

    	    //
    	    //  Supported level returned - but not a valid value (not BASIC)
    	    //

    	    RcPrintf("Error - Invalid support level returned\n");
    	    goto ServerError;

    	}  else  {

    	    //
    	    //  Neither error nor supported level returned
    	    //

    	    RcPrintf("Error - Invalid response from remote server\n");
    	    goto ServerError;

    	}
        }

        //
        // All is well - Session is connected
        //

        SessionConnected = TRUE;

        if (CommandHeader.CommandFixedHeader.CommandLength == 0) {
            RcPrintf("Connected to %s\n\n", ServerName);
        } else {
            RcPrintf("Executing on %s: %s\n\n", ServerName, CommandHeader.Command);
        }

        //
        // Exec 2 threads - 1 copies data from stdin to pipe, the other
        // copies data from the pipe to stdout.
        //

        ReadThreadHandle = CreateThread(
    			NULL,                       // Default security
    			0,                          // Default Stack size
    			(LPTHREAD_START_ROUTINE) ReadThreadProc,
    			(PVOID)PipeHandle,
    			0,
    			&ThreadId);

        if (ReadThreadHandle == NULL) {
    	RcPrintf("Error:3 - Internal error = %ld\n", GetLastError());
    	return(1);  // catastrophic error - exit
        }


        //
        // Create the write thread
        //

        WriteThreadHandle = CreateThread(
    			NULL,                       // Default security
    			0,                          // Default Stack size
    			(LPTHREAD_START_ROUTINE) WriteThreadProc,
    			(PVOID)PipeHandle,
    			0,
    			&ThreadId);

        if (WriteThreadHandle == NULL) {
    	RcPrintf("Error:4 - Internal error = %ld\n", GetLastError());
    	TerminateThread(ReadThreadHandle, 0);
    	CloseHandle(ReadThreadHandle);
    	return(1);  // catastrophic error, exit
        }



        //
        // Wait for either thread to finish
        //

        HandleArray[0] = ReadThreadHandle;
        HandleArray[1] = WriteThreadHandle;

        Result = WaitForMultipleObjects(
    			    2,
    			    HandleArray,
    			    FALSE,              // Wait for either to finish
    			    0xffffffff
    			   );			// Wait forever

        //
        //	Finished - terminate other thread and close pipe handle
        //


        if (Result == (WAIT_OBJECT_0 + 0))	{    // Read thread finished - terminate write
    	TerminateThread(WriteThreadHandle, 0);
        }

        if (Result == (WAIT_OBJECT_0 + 1))	{    // Write thread finished - terminate read
    	TerminateThread(ReadThreadHandle, 0);
        }

        RcDbgPrint("Read or write thread terminated\n");


        //
        // Close our pipe handle
        //

        CloseHandle(PipeHandle);
        PipeHandle = NULL;


        //
        //	Re-enable normal ctrl-C processing
        //

        SessionConnected = FALSE;

        //
        //	Normal completion - return if not MultServerMode
        //

        if (!MultiServerMode)  {
            //
            // return - process exit will terminate threads and close thread handles
            //
            return(1);
        }

ServerError:

        if (PipeHandle != NULL) {
            CloseHandle(PipeHandle);
        }

        if (!MultiServerMode) {
            //
            //  return false on error - process exit terminates threads/closes handles
            //
            return(0);
        }

        //
        //	Multi-service mode exits occurs with ctrl-C/break only
        //
    }

}


/***************************************************************************\
* FUNCTION: ReadPipe
*
* PURPOSE:  Implements an overlapped read such that read and write operations
*           to the same pipe handle don't deadlock.
*
* RETURNS:  TRUE on success, FALSE on failure (GetLastError() has error)
*
* HISTORY:
*
*   05-27-92 Davidc       Created.
*
\***************************************************************************/

BOOL
ReadPipe(
    HANDLE PipeHandle,
    LPVOID lpBuffer,
    DWORD nNumberOfBytesToRead,
    LPDWORD lpNumberOfBytesRead
    )
{
    DWORD Result;
    OVERLAPPED Overlapped;
    HANDLE  EventHandle;
    DWORD Error;

    //
    // Create an event for the overlapped operation
    //

    EventHandle = CreateEvent(
			      NULL,         // no security
			      TRUE,         // Manual reset
			      FALSE,        // Initial state
			      NULL          // Name
			     );
    if (EventHandle == NULL) {
	RcDbgPrint("ReadPipe: CreateEvent failed, error = %d\n", GetLastError());
	return(FALSE);
    }

    Overlapped.hEvent = EventHandle;
    Overlapped.Internal = 0;
    Overlapped.InternalHigh = 0;
    Overlapped.Offset = 0;
    Overlapped.OffsetHigh = 0;


    Result = ReadFile(
		      PipeHandle,
		      lpBuffer,
		      nNumberOfBytesToRead,
		      lpNumberOfBytesRead,
		      &Overlapped
		     );
    if (Result) {

	//
	// Success without waiting - it's too easy !
	//

	CloseHandle(EventHandle);

    } else {

	//
	// Read failed, if it's overlapped io, go wait for it
	// If failure due to server, print appropriate message.
	//

	Error = GetLastError();

	switch (Error)  {

	case ERROR_IO_PENDING:
	    break;

	case ERROR_PIPE_NOT_CONNECTED:
	case ERROR_BROKEN_PIPE:
	    RcPrintf("\nRemote server %s disconnected\n", ServerName);
	    CloseHandle(EventHandle);
	    return(FALSE);

	default:
	    RcPrintf("Error:5 - Internal error = %d\n", Error);
	    RcDbgPrint("ReadPipe: ReadFile failed, error = %d\n", Error);
	    CloseHandle(EventHandle);
	    return(FALSE);

	}

	//
	// Wait for the I/O to complete
	//

	Result = WaitForSingleObject(EventHandle, (DWORD)-1);
	if (Result != 0) {
	    RcDbgPrint("ReadPipe: event wait failed, result = %d, last error = %d\n", Result, GetLastError());
	    CloseHandle(EventHandle);
	    return(FALSE);
	}

	//
	// Go get the I/O result
	//

	Result = GetOverlappedResult( PipeHandle,
				      &Overlapped,
				      lpNumberOfBytesRead,
				      FALSE
				    );
	//
	// We're finished with the event handle
	//

	CloseHandle(EventHandle);

	//
	// Check result of GetOverlappedResult
	//

	if (!Result) {
	    Error = GetLastError();

	    switch (Error)  {

	    case ERROR_PIPE_NOT_CONNECTED:
	    case ERROR_BROKEN_PIPE:
		RcPrintf("\nRemote server %s disconnected\n", ServerName);
		return(FALSE);

	    default:
		RcPrintf("Error:9 - Internal error = %d\n", Error);
		RcDbgPrint("ReadPipe: GetOverLappedRsult failed, error = %d\n", Error);
		return(FALSE);
	    }

	}
    }

    return(TRUE);
}


/***************************************************************************\
* FUNCTION: WritePipe
*
* PURPOSE:  Implements an overlapped write such that read and write operations
*           to the same pipe handle don't deadlock.
*
* RETURNS:  TRUE on success, FALSE on failure (GetLastError() has error)
*
* HISTORY:
*
*   05-27-92 Davidc       Created.
*
\***************************************************************************/

BOOL
WritePipe(
    HANDLE PipeHandle,
    CONST VOID *lpBuffer,
    DWORD nNumberOfBytesToWrite,
    LPDWORD lpNumberOfBytesWritten
    )
{
    DWORD Result;
    OVERLAPPED Overlapped;
    HANDLE  EventHandle;
    DWORD Error;

    //
    // Create an event for the overlapped operation
    //

    EventHandle = CreateEvent(
			      NULL,         // no security
			      TRUE,         // Manual reset
			      FALSE,        // Initial state
			      NULL          // Name
			     );
    if (EventHandle == NULL) {
	RcDbgPrint("WritePipe: CreateEvent failed, error = %d\n", GetLastError());
	return(FALSE);
    }

    Overlapped.hEvent = EventHandle;
    Overlapped.Internal = 0;
    Overlapped.InternalHigh = 0;
    Overlapped.Offset = 0;
    Overlapped.OffsetHigh = 0;

    Result = WriteFile(
		      PipeHandle,
		      lpBuffer,
		      nNumberOfBytesToWrite,
		      lpNumberOfBytesWritten,
		      &Overlapped
		     );
    if (Result) {

	//
	// Success without waiting - it's too easy !
	//

	CloseHandle(EventHandle);

    } else {

	//
	// Write failed, if it's overlapped io, go wait for it
	// If failure due to server, print appropriate message.
	//

	Error = GetLastError();

	switch (Error)  {

	case ERROR_IO_PENDING:
	    break;

	case ERROR_PIPE_NOT_CONNECTED:
	case ERROR_BROKEN_PIPE:
	    RcPrintf("\nRemote server %s disconnected\n", ServerName);
	    CloseHandle(EventHandle);
	    return(FALSE);

	default:
	    RcPrintf("Error:6 - Internal error = %d\n", Error);
	    RcDbgPrint("WritePipe: WriteFile failed, error = %d\n", Error);
	    CloseHandle(EventHandle);
	    return(FALSE);

	}

	//
	// Wait for the I/O to complete
	//

	Result = WaitForSingleObject(EventHandle, (DWORD)-1);
	if (Result != 0) {
	    RcDbgPrint("WritePipe: event wait failed, result = %d, last error = %d\n", Result, GetLastError());
	    CloseHandle(EventHandle);
	    return(FALSE);
	}

	//
	// Go get the I/O result
	//

	Result = GetOverlappedResult( PipeHandle,
				      &Overlapped,
				      lpNumberOfBytesWritten,
				      FALSE
				    );
	//
	// We're finished with the event handle
	//

	CloseHandle(EventHandle);

	//
	// Check result of GetOverlappedResult
	//

	if (!Result) {
	    Error = GetLastError();

	    switch (Error)  {

	    case ERROR_PIPE_NOT_CONNECTED:
	    case ERROR_BROKEN_PIPE:
		RcPrintf("\nRemote server %s disconnected\n", ServerName);
		return(FALSE);

	    default:
		RcPrintf("Error:10 - Internal error = %d\n", Error);
		RcDbgPrint("WritePipe: GetOverLappedRsult failed, error = %d\n", Error);
		return(FALSE);
	    }
	}
    }

    return(TRUE);
}


/***************************************************************************\
* FUNCTION: ReadThreadProc
*
* PURPOSE:  The read thread procedure. Reads from pipe and writes to STD_OUT
*
* RETURNS:  Nothing
*
* HISTORY:
*
*   05-21-92 Davidc       Created.
*
\***************************************************************************/

DWORD
ReadThreadProc(
    LPVOID Parameter
    )
{
    HANDLE  PipeHandle = Parameter;
    BYTE    Buffer[BUFFER_SIZE];
    DWORD   BytesRead;
    DWORD   BytesWritten;

    while (ReadPipe(PipeHandle, Buffer, sizeof(Buffer), &BytesRead)) {
		if (!WriteFile(
			    GetStdHandle(STD_OUTPUT_HANDLE),
			    Buffer,
			    BytesRead,
			    &BytesWritten,
			    NULL
			    )) {

		    RcPrintf("Error:7 - Internal error = %d\n", GetLastError());
		    RcDbgPrint("ReadThreadProc exitting, WriteFile error = %d\n",
		       GetLastError());
		    ExitThread((DWORD)0);
		    assert(FALSE);  // Should never get here
		    break;
		}
    }

    //
    //	ReadPipe issues more error information to user, debugger
    //	falls through here on read error
    //

    RcDbgPrint("WriteThreadProc exitting, ReadPipe failed\n");

    ExitThread((DWORD)0);

    return(0);
}


/***************************************************************************\
* FUNCTION: WriteThreadProc
*
* PURPOSE:  The write thread procedure. Reads from STD_INPUT and writes to pipe
*
* RETURNS:  Nothing
*
* HISTORY:
*
*   05-21-92 Davidc       Created.
*
\***************************************************************************/

DWORD
WriteThreadProc(
    LPVOID Parameter
    )
{
    HANDLE PipeHandle = Parameter;
    BYTE    Buffer[BUFFER_SIZE];
    DWORD   BytesRead;
    DWORD   BytesWritten;
    DWORD   Result;

    while (ReadFile(
		    GetStdHandle(STD_INPUT_HANDLE),
		    Buffer,
		    sizeof(Buffer),
		    &BytesRead,
		    NULL
		   )) {

	if ((DWORD)Buffer[0] == 0x0A0D0A0D)  {
	    RcPrintf("\nDouble crlf sent\n");
	    }

	if (!WritePipe(
		    PipeHandle,
		    Buffer,
		    BytesRead,
		    &BytesWritten
		 )) {

		//
		//  WritePipe issues more error information to user, debugger
		//

		RcDbgPrint("WriteThreadProc exitted due to WritePipe\n");
		ExitThread((DWORD)0);
		break;

	}
    }

    //
    //	Falls out if read fails
    //

    RcDbgPrint("WriteThreadProc, ReadFile error = %d\n", GetLastError());

    RcPrintf("Error:8 - Internal error = %d\n", GetLastError());
    ExitThread((DWORD)0);

    return(0);
}


/***************************************************************************\
* FUNCTION: CtrlHandler
*
* PURPOSE:  Handles console event notifications.
*
* RETURNS:  TRUE if the event has been handled, otherwise FALSE.
*
* HISTORY:
*
*   05-21-92 Davidc       Created.
*
\***************************************************************************/

BOOL
CtrlHandler(
    DWORD CtrlType
    )
{
    //
    // We'll handle Ctrl-C, Ctl-Break events if session is connected
    //

    if (SessionConnected)  {

	if (CtrlType == CTRL_C_EVENT)  {

	    //
	    //	Session established - pass ctl-C to remote server
	    //

	    if (PipeHandle != NULL) {

		//
		// Send a Ctrl-C to the server, don't care if it fails
		//

		CHAR	CtrlC = '\003';
		DWORD	BytesWritten;

		WriteFile(PipeHandle,
		      &CtrlC,
		      sizeof(CtrlC),
		      &BytesWritten,
		      NULL
		     );

		return(TRUE);  // we handled it
	    }

	    return(FALSE);  // no pipe - not handled (when in doubt, bail out)

	}  else if (CtrlType == CTRL_BREAK_EVENT)  {

	    if (MultiServerMode)  {

		//
		//   If ctl-Break in session w/MultiServerMode, break session
		//   BUGBUG - eliminate terminate thread
		//

		TerminateThread(ReadThreadHandle, 0);
		CloseHandle(ReadThreadHandle);
		TerminateThread(WriteThreadHandle, 0);
		CloseHandle(WriteThreadHandle);

		return(TRUE);  // we handled it

	    }  else  {

		//
		//  Not MultiServer mode - handle normally
		//

		return(FALSE);
	    }

	}  else {

	    return(FALSE);  // not ctl-c or break - we didn't handle it

	}

    }

    //
    // Not connected - we didn't handle it
    //

    return(FALSE);
}


/***************************************************************************\
* FUNCTION: RcPrintf
*
* PURPOSE:  Printf that uses low-level io.
*
* HISTORY:
*
*   07-15-92 Davidc       Created.
*
\***************************************************************************/

int RcPrintf (
    const char *format,
    ...
    )
{
    CHAR Buffer[MAX_PATH];
    va_list argpointer;
    int Result;
    DWORD BytesWritten;

    va_start(argpointer, format);

    Result = vsprintf(Buffer, format, argpointer);

    if (!WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), Buffer, Result, &BytesWritten, NULL)) {
	RcDbgPrint("RcPrintf : Write file to stdout failed, error = %d\n", GetLastError());
	Result = 0;
    }

    va_end(argpointer);

    return(Result);
}

/***************************************************************************\
* FUNCTION: RcDbgPrint
*
* PURPOSE:  DbgPrint enabled at runtime by setting RcDbgPrintEnable
*
* HISTORY:
*
*   05-22-92 DaveTh   Created.
*
\***************************************************************************/

int RcDbgPrint (
    const char *format,
    ...
    )
{
    CHAR Buffer[MAX_PATH];
    va_list argpointer;
    int iRetval = 0;

    if (RcDbgPrintEnable)  {

        va_start(argpointer, format);
        iRetval = vsprintf(Buffer, format, argpointer);
        assert (iRetval >= 0);
        va_end(argpointer);
        OutputDebugString(Buffer);

    }

    return(0);
}