/****************************** 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 #include #include #include #include #include #include #include #include #include #include #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); }