AREMOTE.C
/* 
Copyright (c) 1994, 1993 Microsoft Corporation 
 
Module Name: 
    Remote.c 
 
Abstract: 
    This module contains the main() entry point for Remote. 
    Calls the Server or the Client depending on the first parameter. 
 
Author: 
    Rajivendra Nath (rajnath) 2-Jan-1993 
 
Environment: 
    Console App. User mode. 
 
Revision History: 
Alex Wetmore (t-alexwe) 6-Jun-1994 
- converted remote to use APPC with Windows SNA Server instead of named 
  pipes 
*/ 
 
#include <windows.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <winappc.h> 
#include "appclib.h" 
#include "aremote.h" 
 
TCHAR   HostName[HOSTNAMELEN]; 
TCHAR   *ChildCmd; 
TCHAR   *TPName = TEXT("AREMOTE"); 
TCHAR   *locTPName = TEXT("AREMOTC"); 
TCHAR   *LocalLU = TEXT("AREMOTE"); 
TCHAR   *ServerLU; 
TCHAR   *mode_name = TEXT("#INTER"); 
HANDLE MyOutHandle; 
BOOL   IsAdvertise=TRUE; 
DWORD  ClientToServerFlag; 
BOOL   AutoStarted; 
 
 
TCHARChildCmdBuffer[1024]; 
 
TCHAR* ColorList[]={TEXT("black") ,TEXT("blue") ,TEXT("green") ,TEXT("cyan") ,TEXT("red") ,TEXT("purple") ,TEXT("yellow") ,TEXT("white"), 
                   TEXT("lblack"),TEXT("lblue"),TEXT("lgreen"),TEXT("lcyan"),TEXT("lred"),TEXT("lpurple"),TEXT("lyellow"),TEXT("lwhite")}; 
 
int __cdecl main(void); 
 
WORD GetColorNum(TCHAR* color); 
VOID SetColor(WORD attr); 
 
CONSOLE_SCREEN_BUFFER_INFO csbiOriginal; 
 
/*************************************************************/ 
VOID ErrorExit(TCHAR *str) { 
    WRITEF((VBuff,TEXT("Error-%d:%s\n"),GetLastError(),str)); 
    ExitProcess(1); 
} 
/*************************************************************/ 
DWORD ReadFixBytes(HANDLE hRead, TCHAR*  Buffer, DWORD  ToRead, 
    DWORD  TimeOut) {   //ignore timeout for timebeing 
    DWORD xyzBytesRead=0; 
    DWORD xyzBytesToRead=ToRead; 
    TCHAR* xyzbuff=Buffer; 
 
    while(xyzBytesToRead!=0) 
    { 
        if (!ReadFile(hRead,xyzbuff,xyzBytesToRead,&xyzBytesRead,NULL)) 
        { 
            return(xyzBytesToRead); 
        } 
 
        xyzBytesToRead-=xyzBytesRead; 
        xyzbuff+=xyzBytesRead; 
    } 
    return(0); 
 
} 
/*************************************************************/ 
VOID DisplayClientHlp() { 
    WRITEF((VBuff,TEXT("\n   To Start the CLIENT end of AREMOTE\n"))); 
    WRITEF((VBuff,TEXT("   ---------------------------------\n"))); 
    WRITEF((VBuff,TEXT("   Syntax : AREMOTE /C <ServerLU> [/T <TPName>] [/P <TPName>] [/L <LocalLU>]\n"))); 
    WRITEF((VBuff,TEXT("                                  [/N <# lines>] [/M <Modename>]\n"))); 
WRITEF((VBuff,TEXT("                                  [/F <Color>] [/B <Color>]\n"))); 
    WRITEF((VBuff,TEXT("\n"))); 
 
    WRITEF((VBuff,TEXT("   <ServerLU>      SNA LU for connecting to Server\n"))); 
 
WRITEF((VBuff,TEXT("   [/T <TPName>]   TP name that server is using (default is \"%s\")\n"), TPName)); 
WRITEF((VBuff,TEXT("   [/P <TPName>]   TP name that client is using (default is \"%s\")\n"), locTPName)); 
WRITEF((VBuff,TEXT("   [/M <Modename>] The mode by which the Local LU and Server LU are partnered\n"))); 
WRITEF((VBuff,TEXT("                   (defaults to \"%s\")\n"), mode_name)); 
WRITEF((VBuff,TEXT("   [/L <LocalLU>]  LU name for local TP to use (default is \"%s\")\n"), LocalLU)); 
    WRITEF((VBuff,TEXT("   [/N <# lines>]  Number of Lines to Get.\n"))); 
    WRITEF((VBuff,TEXT("   [/F <color>]    Foreground color eg blue, red, etc.\n"))); 
    WRITEF((VBuff,TEXT("   [/B <color>]    Background color eg blue, lwhite,etc.\n"))); 
    WRITEF((VBuff,TEXT("\n"))); 
    WRITEF((VBuff,TEXT("   Example: aremote /C serverlu\n"))); 
    WRITEF((VBuff,TEXT("\n"))); 
    WRITEF((VBuff,TEXT("   To Exit: %cQ (Leaves the Remote Server Running)\n"), COMMANDCHAR)); 
    WRITEF((VBuff,TEXT("\n"))); 
} 
/*************************************************************/ 
 
VOID DisplayServerHlp() { 
    WRITEF((VBuff,TEXT("\n   To Start the SERVER end of AREMOTE\n"))); 
    WRITEF((VBuff,TEXT("   ---------------------------------\n"))); 
    WRITEF((VBuff,TEXT("   Syntax : AREMOTE /S \"<Cmd>\" [/T <TPName>] [/F <color>] [/B <color>]\n"))); 
    WRITEF((VBuff,TEXT("\n"))); 
 
    WRITEF((VBuff,TEXT("   \"<Cmd>\"         A Text-Mode program that you want to control\n"))); 
    WRITEF((VBuff,TEXT("                   from another computer.\n"))); 
 
WRITEF((VBuff,TEXT("   [/T <TPName>]   TP name that server is using (default is \"%s\")\n"), TPName)); 
    WRITEF((VBuff,TEXT("   [/F <color>]    Foreground color eg blue, red, etc.\n"))); 
    WRITEF((VBuff,TEXT("   [/B <color>]    Background color eg blue, lwhite, etc.\n"))); 
 
    WRITEF((VBuff,TEXT("\n"))); 
    WRITEF((VBuff,TEXT("   Example: aremote /S \"cmd\"\n"))); 
    WRITEF((VBuff,TEXT("\n"))); 
    WRITEF((VBuff,TEXT("   To Exit: %cK from Client\n"), COMMANDCHAR)); 
    WRITEF((VBuff,TEXT("\n"))); 
 
} 
 
WORD GetColorNum(TCHAR *color) { 
    int i; 
 
    _wcslwr(color); 
    for (i=0;i<16;i++) 
    { 
        if (lstrcmp(ColorList[i],color)==0) 
        { 
            return(i); 
        } 
    } 
    return 0xffff; 
} 
 
VOID 
SetColor( 
    WORD attr 
    ) 
{ 
COORD  origin={0,0}; 
    DWORD  dwrite; 
    FillConsoleOutputAttribute 
    ( 
    MyOutHandle,attr,csbiOriginal.dwSize. 
    X*csbiOriginal.dwSize.Y,origin,&dwrite 
    ); 
    SetConsoleTextAttribute(MyOutHandle,attr); 
} 
 
/*************************************************************/ 
VOID 
Errormsg( 
    TCHAR* str 
    ) 
{ 
    WRITEF((VBuff,TEXT("Error (%d) - %s\n"),GetLastError(),str)); 
} 
 
/*************************************************************/ 
 
/* 
 * this main procedure gets called by the appropriate main depending on 
 * if AREMOTE is setup as a service or not. 
 */ 
int remote_main(int argc, TCHAR **argv) { 
    WORD  RunType;              // Server or Client end of Remote 
    DWORD len=HOSTNAMELEN-1; 
    int   i; 
 
    TCHAR  sTitle[100];          // New Title 
    TCHAR  orgTitle[100];        // Old Title 
    BOOL  bSetAttrib=FALSE;     // Change Console Attributes 
    WORD  wAttrib;              // Console Attributes 
char  abuff[1024];// temporary ansi buffer 
 
    GetComputerName((LPTSTR)HostName,&len); 
 
    MyOutHandle=GetStdHandle(STD_OUTPUT_HANDLE); 
 
    GetConsoleScreenBufferInfo(MyOutHandle,&csbiOriginal); 
 
    // Parameter Processing 
    // 
    // For Server: 
    // Remote /S <Executable> [Optional Params] 
// 
// For AutoStarted TP: 
// Remote /A <Executable> [Optional Params] 
//   - this case is handled in main() 
    // 
    // For Client: 
    // Remote /C <Server LU> [Optional Params] 
 
    if ((argc < 2)||((argv[1][0] != TEXT('/'))&&(argv[1][0] != TEXT('-')))) { 
        DisplayServerHlp(); 
        DisplayClientHlp(); 
WRITEF((VBuff, TEXT("\nFor server help only: aremote /s"))); 
WRITEF((VBuff, TEXT("\nFor client help only: aremote /c"))); 
        return(1); 
    } 
 
    switch(tolower(argv[1][1])) { 
    case TEXT('c'): 
        // Is Client End of Remote 
        if ((argc < 3) || ((argv[1][0] != TEXT('/')) && (argv[1][0] != TEXT('-')))) { 
            DisplayClientHlp(); 
            return 1; 
        } 
 
ServerLU = _wcsupr(argv[2]); 
 
        RunType=REMOTE_CLIENT; 
        break; 
 
    case TEXT('s'): 
        // Is Server End of Remote 
        if ((argc < 3) || ((argv[1][0] != TEXT('/')) && (argv[1][0] != TEXT('-')))) { 
            DisplayServerHlp(); 
            return 1; 
        } 
 
        ChildCmd = argv[2]; 
 
        RunType = REMOTE_SERVER; 
        break; 
 
    default: 
        DisplayServerHlp(); 
        DisplayClientHlp(); 
WRITEF((VBuff, TEXT("\nFor server help only: aremote /s"))); 
WRITEF((VBuff, TEXT("\nFor client help only: aremote /c"))); 
 return(1); 
    } 
 
    // 
    // Save Existing Values 
    // 
 
    // 
    //Colors /f   <ForeGround> /b <BackGround> 
    // 
 
    wAttrib = csbiOriginal.wAttributes; 
 
    GetConsoleTitle(orgTitle,sizeof(orgTitle)); 
 
    if (RunType == REMOTE_SERVER) { 
    // 
    // Base Name of Executable 
    // For setting the title 
    // 
 
        TCHAR *tcmd=ChildCmd; 
 
        while ((*tcmd!=TEXT(' '))    &&(*tcmd!=0))   tcmd++; 
        while ((tcmd!=ChildCmd)&&(*tcmd!=TEXT('\\')))tcmd--; 
 
        wsprintf(sTitle,TEXT("%8.8s [Remote /C %s %s]"),tcmd,HostName,TPName); 
    } 
 
    // 
    //Process Common (Optional) Parameters 
    // 
 
    for (i = 3; i < argc; i++) { 
 
        if ((argv[i][0] != TEXT('/')) && (argv[i][0] != TEXT('-'))) { 
            WRITEF((VBuff,TEXT("Invalid parameter %s:Ignoring\n"),argv[i])); 
            continue; 
        } 
 
        switch(tolower(argv[i][1])) { 
        case TEXT('n'):   // Only Valid for client End, max number of lines to get from server 
            i++; 
            if (i >= argc) { 
                WRITEF((VBuff,TEXT("Incomplete Param %s..Ignoring\n"),argv[i-1])); 
                break; 
            } 
wcstombs(abuff, argv[i], 1024); 
            LinesToSend=(DWORD)atoi(abuff)+1; 
            break; 
 
case TEXT('l'):// name of LU for client to use 
i++; 
if (i >= argc) { 
                WRITEF((VBuff,TEXT("Incomplete Param %s..Ignoring\n"),argv[i-1])); 
                break; 
            } 
LocalLU = _wcsupr(argv[i]); 
break; 
         
case TEXT('m'):// name of LU-LU connection mode 
i++; 
if (i >= argc) { 
                WRITEF((VBuff,TEXT("Incomplete Param %s..Ignoring\n"),argv[i-1])); 
                break; 
            } 
mode_name = _wcsupr(argv[i]); 
break; 
         
case TEXT('t'):// name of tp 
i++; 
if (i >= argc) { 
                WRITEF((VBuff,TEXT("Incomplete Param %s..Ignoring\n"),argv[i-1])); 
                break; 
            } 
TPName = _wcsupr(argv[i]); 
break; 
         
   case TEXT('p'):// name of tp 
i++; 
if (i >= argc) { 
                WRITEF((VBuff,TEXT("Incomplete Param %s..Ignoring\n"),argv[i-1])); 
                break; 
            } 
locTPName = _wcsupr(argv[i]); 
break; 
         
        case TEXT('b'):   // Background color 
            i++; 
            if (i >= argc) { 
                WRITEF((VBuff,TEXT("Incomplete Param %s..Ignoring\n"),argv[i-1])); 
                break; 
            } 
            { 
                WORD col = GetColorNum(argv[i]); 
                if (col != 0xffff) { 
                    bSetAttrib = TRUE; 
                    wAttrib = col << 4 | (wAttrib & 0x000f); 
                } 
                break; 
            } 
 
        case TEXT('f'):   // Foreground color 
            i++; 
            if (i>=argc) { 
                WRITEF((VBuff,TEXT("Incomplete Param %s..Ignoring\n"),argv[i-1])); 
                break; 
            } 
            { 
                WORD col = GetColorNum(argv[i]); 
                if (col != 0xffff) { 
                    bSetAttrib = TRUE; 
                    wAttrib = col | (wAttrib & 0x00f0); 
                } 
                break; 
            } 
 
        case TEXT('q'): 
            IsAdvertise = FALSE; 
            ClientToServerFlag |= 0x80000000; 
            break; 
 
        default: 
            WRITEF((VBuff,TEXT("Unknown Parameter=%s %s\n"),argv[i-1],argv[i])); 
            break; 
 
        } 
    } 
 
    // 
    //Now Set various Parameters 
    // 
 
    // 
    //Colors 
    // 
 
    SetColor(wAttrib); 
 
    if (RunType==REMOTE_CLIENT) { 
        // Start Client (Client.C) 
        Client(LocalLU, ServerLU, TPName, locTPName, mode_name); 
    } 
 
    if (RunType==REMOTE_SERVER) { 
        // Start Server (Server.C) 
        Server(ChildCmd, TPName); 
    } 
 
    // 
    //Reset Colors 
    // 
    SetColor(csbiOriginal.wAttributes); 
 
    ExitProcess(0); 
 
return0;// satisfy the ppc compiler; statement never reached 
} 
 
/*****************************************************************************/ 
/* The following code makes this TP invokable as an NT service. There are 3  */ 
/* routines.                                                                 */ 
/*                                                                           */ 
/* 1. main. This is the entry point for the process, it sets up a service    */ 
/*          table entry and then calls StartServiceCtrlDispatcher. This call */ 
/*          doesn't return, but uses the thread which called it as a         */ 
/*          control dispatcher for all the services implemented by this      */ 
/*          process (in this case, just the TP itself).                      */ 
/*                                                                           */ 
/* 2. ServiceMain. This is the main entry point for the service itself, the  */ 
/*          service control dispatcher creates a thread to start at this     */ 
/*          routine. It must register a service control handler for the      */ 
/*          service which will be called by the control dispatcher when it   */ 
/*          has control instructions for the service. It then informs the    */ 
/*          service control manager that the service is running and finally  */ 
/*          calls the start of the TP itself. This routine should not return */ 
/*          until the service is ready to die.                               */ 
/*                                                                           */ 
/* 3. ControlHandler. This routine is called by the control dispatcher when  */ 
/*          it has instructions for the service. We do not respond to any    */ 
/*          of the instructions as this service should be transitory and not */ 
/*          actually run for more than a few seconds so we don't need to do  */ 
/*          anything with the STOP or SHUTDOWN requests.                     */ 
/*          Note that we MUST call SetServiceStatus, even if the status      */ 
/*          hasn't changed.                                                  */ 
/*****************************************************************************/ 
 
VOID WINAPI ServiceMain(DWORD dwNumServiceArgs, LPTSTR * lpServiceArgs); 
VOID WINAPI ControlHandler(DWORD dwControl); 
SERVICE_STATUS_HANDLE stat_hand; 
SERVICE_STATUS servstat; 
 
int __cdecl main(void) { 
SERVICE_TABLE_ENTRY stab[2]; 
int argc, i; 
TCHAR *argv[50]; 
TCHARcmdline[1024]; 
 
// read the command line and break it out into argc/argv.  We have to do 
// this because the standard command line passed into main isn't in Unicode 
lstrcpy(cmdline, GetCommandLine()); 
i = 0; argc = 0; 
argv[argc] = &(cmdline[i]); 
while (cmdline[i] != 0) { 
if (cmdline[i] == TEXT(' ')) { 
cmdline[i++] = 0; 
argv[++argc] = &(cmdline[i]); 
} 
i++; 
} 
argc++; 
 
if ((argc >= 3) && 
    (tolower(argv[1][1]) == TEXT('a')) && 
    ((argv[1][0] == TEXT('/')) || (argv[1][0] == TEXT('-')))) { 
 
AutoStarted = TRUE; 
 
// 
// hack to fix NET START AREMOTE bug. Since we're not autostarted 
// by an SNA Server component we have no control over the service 
// cmd line.  Therefore we copy out the 3rd parameter passed to the 
// service process and use it if and only there are less than 3 params 
// passed to StartService 
// 
lstrcpy( ChildCmdBuffer, argv[2] ); 
 
// 
// 
// This is being run as a service 
// 
// 
// Start the control dispatcher. This call gives the SCManager this 
// thread for the entire period that this service is running, so that 
// it can call us back with service controls. It will spawn a new 
// thread to run the service itself, starting at entrypoint ServiceMain 
// 
stab[0].lpServiceName = TEXT("AREMOTE\0"); 
stab[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION) ServiceMain; 
 
stab[1].lpServiceName = NULL; 
stab[1].lpServiceProc = NULL; 
 
if (!StartServiceCtrlDispatcher(stab)) { 
WRITEF((VBuff, TEXT("Remote: This was run as an service\n\n"))); 
WRITEF((VBuff, TEXT("For command line usage type \"Remote\"\n"))); 
} 
} else { 
// 
// This is being run as a user-invoked program 
// 
AutoStarted = FALSE; 
remote_main(argc, argv); 
} 
 
return 0; 
} 
 
 
/*****************************************************************************/ 
/* This routine is the entry-point for the service itself the service        */ 
/* control dispatcher creates a thread to start here when we issue           */ 
/* StartServiceControlDispatcher.                                            */ 
/*                                                                           */ 
/* Inputs:  number of arguments to services, array of strings.               */ 
/*                                                                           */ 
/* Outputs: none                                                             */ 
/*                                                                           */ 
/*****************************************************************************/ 
VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv) { 
DWORD rc; 
 
stat_hand = RegisterServiceCtrlHandler(TEXT("AREMOTE\0"), 
(LPHANDLER_FUNCTION) ControlHandler); 
 
if (stat_hand == (SERVICE_STATUS_HANDLE)NULL) { 
rc = GetLastError(); 
DebugBreak(); 
} 
 
/*************************************************************************/ 
/* Let the SCManager know that we are running.                           */ 
/*************************************************************************/ 
servstat.dwServiceType              = SERVICE_WIN32; 
servstat.dwCurrentState             = SERVICE_RUNNING; 
servstat.dwControlsAccepted= SERVICE_ACCEPT_STOP | 
SERVICE_ACCEPT_SHUTDOWN; 
servstat.dwWin32ExitCode            = NO_ERROR; 
servstat.dwServiceSpecificExitCode  = NO_ERROR; 
servstat.dwCheckPoint               = 0; 
servstat.dwWaitHint                 = 0; 
 
rc = SetServiceStatus(stat_hand, &servstat); 
 
if (!rc) { 
rc = GetLastError(); 
DebugBreak(); 
} 
 
{ 
int new_argc = 3; 
TCHAR *new_argv[3]; 
 
new_argv[0] = argv[0]; 
new_argv[1] = (TCHAR *) LocalAlloc(0, 16); 
lstrcpy(new_argv[1], TEXT("/s")); 
 
if ( argc < 3 ) 
{ 
// 
// hack to fix NET START AREMOTE bug. Since we're not autostarted 
// by an SNA Server component we have no control over the service 
// cmd line.  Therefore we copy out the 3rd parameter passed to the 
// service process and use it if and only there are less than 3 params 
// passed to StartService 
// 
new_argv[2] = ChildCmdBuffer; 
} 
else 
{ 
new_argv[2] = argv[2]; 
} 
 
 
remote_main(new_argc, (TCHAR **) new_argv); 
 
LocalFree(new_argv[1]); 
} 
} 
 
/***************************************************************************/ 
/* This routine is the callback from the SCManager to handle specific      */ 
/* service control requests. It MUST call SetServiceStatus before it       */ 
/* returns, regardless of whether the status has changed.                  */ 
/*                                                                         */ 
/* Inputs: service control requested                                       */ 
/*                                                                         */ 
/* Outputs: none                                                           */ 
/*                                                                         */ 
/***************************************************************************/ 
VOID WINAPI ControlHandler(DWORD dwControl) { 
DWORD rc; 
 
switch (dwControl) { 
case SERVICE_CONTROL_STOP : 
servstat.dwCurrentState = SERVICE_STOP_PENDING; 
servstat.dwWaitHint     = 24000; 
break; 
 
case SERVICE_CONTROL_PAUSE : 
case SERVICE_CONTROL_CONTINUE : 
case SERVICE_CONTROL_INTERROGATE : 
servstat.dwWaitHint     = 0; 
break; 
 
case SERVICE_CONTROL_SHUTDOWN: 
servstat.dwCurrentState = SERVICE_STOP_PENDING; 
servstat.dwWaitHint     = 10000; 
break; 
} 
 
rc=SetServiceStatus(stat_hand, &servstat); 
if (!rc) { 
rc=GetLastError(); 
} 
}