Winsock RCMD32.DLL Version 1.8 Copyright 1994 Denicomp Systems All rights reserved DESCRIPTION ----------- Winsock RCMD32.DLL is a Microsoft Win32 Dynamic Link Library (DLL) that provides a Windows Sockets compatible function that allows you to execute commands on a remote host and retreive the output of those commands for processing by your program. The function calls in Winsock RCMD32.DLL are source code compatible with the 16-bit Winsock RCMD.DLL for Windows 3.1. Winsock RCMD32.DLL is similar to the "rcmd" library function found on many Unix systems. The DLL includes the main WinsockRCmd function, plus a function to receive data over the connection from the remote host executing the command. The remote host must be a system running the rshd or rexecd daemon process, such as a Unix system or a PC running Denicomp Systems' Winsock RSHD for Windows 3.1, Winsock RSHD/95 for Windows 95, or Winsock RSHD/NT Service for Windows NT. Winsock RCMD32.DLL is designed to execute non-interactive remote commands. If you need to execute an interactive command on the remote host, use a utility like Telnet. REQUIREMENTS ------------ Winsock RCMD32.DLL requires a PC running Windows NT, Windows 95, or other Windows operating system supporting WSOCK32.DLL, a TCP/IP connection to a remote host running rshd, and any programming language that supports calls to 32-bit DLL functions, such as C. ============================================================================= FUNCTION: WinsockRCmd Syntax: ------- C: INT PASCAL WinsockRCmd (Host, Port, LocalUser, RemoteUser, Cmd, ErrorMsg, ErrorMsgLen) char *Host; int Port; char *LocalUser; char *RemoteUser; char *Cmd; char *ErrorMsg; int ErrorMsgLen; *Visual Basic: Declare Function WinsockRCmd Lib "RCMD32.DLL" (ByVal Host As String, ByVal Port As Long, ByVal LocalUser As String, ByVal RemoteUser As String, ByVal Cmd As String, ByVal ErrorMsg As String, ByVal ErrorMsgLen As Long) As Long * - for Visual Basic languages that can call 32-bit DLL's Powerbuider: (Global External Function Window) Function Int WinsockRCmd (Ref String Host, & Int Port, & Ref String LocalUser, & Ref String RemoteUser, & Ref String Cmd, & Ref String ErrorMsg, & Int ErrorMsgLen) Library "RCMD32.DLL" Usage: ------ The WinsockRCmd function initiates a connection to the remote host and executes the specified command if access is permitted. You can then use the RCmdRead or RCmdReadByte function to receive the standard output and standard error output of the command. Parameters: ----------- Host: Specifies the name of a remote host that is listed in the "hosts" file. You will receive an error if the host name is not found in the hosts file or the hosts file itself is not found. Port: Specifies the port to use for the connection. Normally, this is the well-known port number of 514 for the rshd daemon. You may also use port 512 for the rexecd daemon. The rshd and rexecd daemons are very similar. The only difference is that the rexecd daemon requires you to specify the user's correct password in the RemoteUser parameter. LocalUser: The user name of the user on the local host (the PC). This can be NULL or an empty string if you want Winsock RCMD32.DLL to determine the name. The method it uses is described later. This name is sent as the local user to the rshd daemon on the remote host. In general, it should be the same as the RemoteUser parameter. RemoteUser: When using rshd (port 514), this is the user name to be used at the remote host. This must be a valid user name at the remote host. It can be NULL or an empty string if you want Winsock RCMD32.DLL to detemrine the name. The method is uses it described later. When using rexecd (port 512), you must specify the password of the user specified in the LocalUser parameter here. If the password is invalid, an error will be returned from the remote host. Note that your program is responsible for supplying the password. For example, you could ask the user to enter it or it could be stored in a configuration file. Winsock RCMD32.DLL will not request it. Cmd: The command to be executed at the remote host. ErrorMsg: A pointer to an area that can be used to store an error message from WinsockRCmd. If an error occurs while connecting to the remote host, WinsockRCmd will return a negative result and will store an error message here. You can then optionally use the error message as diagnostic output in your program. ErrorMsgLen: The maximum length of the error message returned. This is the number of characters available in ErrorMsg. Return Value: ------------- If WinsockRCmd sucessfully connects to the remote host and begins executing the specified command, it will return an integer "handle" that must be used by the RCmdRead, RCmdReadByte, RCmdHandle, and RCmdClose functions. This handle will always be greater than or equal to zero. If WinsockRCmd is not successful, it will return a negative number. If the number is -1, an error internal to WinsockRCmd occurred and a message describing the error will be stored in ErrorMsg. If the number is not -1, it is a Windows Sockets error code (but negative). ErrorMsg will also contain a message attempting to describe the error. Notes: ------ The handle MAY be zero; if fact, most times it will be zero. Do not treat a handle value of zero as an error. Errors will return negative values. If the host name specified in the Host parameter is invalid, you will normally receive a Windows Sockets error -11004. This means that either the host name is not in the hosts file or the hosts file does not exist. Refer to the Windows manual for infomation about setting up a hosts file. The error message returned in ErrorMsg may contain newline characters (ASCII 10). The message is suitable for display using the standard Windows MessageBox function or the Visual Basic MsgBox command/function. Execution of interactive commands (commands requiring keyboard input) is not supported. Unlike the Unix "rcmd" function, WinsockRCmd does not have the ability to separate the standard output and the standard error output of the remote command. Output to the standard output and standard error will be intermixed. Declaring the Winsock RCMD32.DLL Functions in Other Languages ------------------------------------------------------------- Declarations are provided for the functions in RCMD32.DLL for C, Visual Basic, and Powerbuilder. If you are using some other language, please keep in mind that the ErrorMsg parameter in the WinsockRCmd() function call and the DataIn parameter in the RCmdRead() function call are modified by the DLL. Be sure when creating declarations in your language for the RCMD32.DLL functions you keep this in mind. Some languages require that you specify which parameters passed to DLL's will be modified and should be passed as a pointer, not as a copy of the variable value in the program. You may receive errors if you do not do this. Languages using Dynamically Allocated Strings --------------------------------------------- The WinsockRCmd function requires you to pass a pointer to an area of memory to store an error message should an error occur (the ErrorMsg parameter). Also, the RCmdRead function (explained later) requires you to pass a pointer to an area of memory that will hold the data returned from the command executed on the remote host. When using C, you can simply pass a pointer to a statically allocated char array (for example, char errmsg[128]) or a pointer to a dynamically allocated area of memory (using malloc() or HeapAlloc()). When using a language that uses dymamically allocated strings, such as Visual Basic or Powerbuilder, you must first "force" memory to be allocated to a string before passing it as the ErrorMsg parameter in WinsockRCmd() or as the DataIn parameter in RCmdRead(). If you do not do this, your program will most likely abort with an error. This can be done in Visual Basic with the String$() function. For example, to allocate 128 bytes in the string EMsg, use the following: Dim EMsg As String EMsg = String$(128,Chr$(0)) This will allocate 128 bytes in EMsg and fill it with null characters. If you just tried to use EMsg without doing this, your program would abort with a memory access error if WinsockRCmd tried to store an error message in EMsg. Powerbuilder has a similar requirement, but you use the Space() function. For example: String EMsg EMsg = Space(128) This will allocate 128 bytes in EMsg and fill it with space characters. This is acceptable because WinsockRCmd() will return the error message as a null-terminated string. However, when using RCmdRead(), be sure to only use the number of bytes specified by the return value of the function or you will find unwanted spaces in your data stream. Windows Sockets "Shutdown" or "Connection Refused" Errors --------------------------------------------------------- Occasionally, you may experience Windows Sockets "Shutdown" errors (-10058) or "Connection Refused" errors (-10061) from the WinsockRCmd function call in definite patterns. For example, you may receive the error every other call or every third call. If you do, you should make provisions in your program to retry the call to WinsockRCmd on the error (with a limit on the number of retries to avoid an endless loop on a true problem). For example: #define MAX_RETRIES 10 int retries = 0; do { result = WinsockRCmd(...); ++retries; } while ((result == -10058 || result == -10061) && retries < MAX_RETRIES); Using the rexecd Daemon ----------------------- If you wish to use the rexecd daemon instead of rshd, specify a port number of 512 instead of 514 in the WinsockRCmd function call. The rexecd daemon is similar to the rshd server, except that it requires a password to be supplied. You must make provisions in your software to allow the user to enter the password at the appropriate time or retrieve it from some storage area (i.e. an initialization file). Winsock RCMD32.DLL does NOT ask for the password. When using rexecd, in addition to using port 512 instead of 514, you must pass the password in the RemoteUser parameter in the WinsockRCmd function call (the fourth parameter). All other parameters and the remaining functions described in this manual function in the same manner. ============================================================================= FUNCTION: RCmdRead Syntax: ------- C: INT PASCAL RCmdRead (hRCmd, DataIn, DataLen) int hRCmd; char *DataIn; int DataLen; Visual Basic: Declare Function RCmdRead Lib "RCMD32.DLL" (ByVal hRCmd As Long, ByVal DataIn As String, ByVal DataLen As Long) As Long Powerbuilder: Function Int RCmdRead (Int hRCmd, & Ref String DataIn, & Int DataLen) Library "RCMD32.DLL" Usage: ------ The RCmdRead function reads the output of the command executed with WinsockRCmd. This allows you to capture the standard output and standard error output of the command you executed. Parameters: ----------- hRCmd: This is the integer "handle" returned from the WinsockRCmd function. DataIn: A pointer to an area of memory to hold the data received. DataLen: The maximum number of bytes to receive. This must be an integer between 1 and 8192. Return Value: ------------- If RCmdRead is sucessful, it returns the number of bytes read. It will be a number greater than zero. If RCmdRead returns zero, there is no more data to read. The command has ended and you should call RCmdClose to terminate the connection. If RCmdRead returns a negative number, an error has occurred. The number will be the Windows Sockets error number (but negative). You should call RCmdClose even if an error occurs to free all resources used by the connection. On a successful call to RCmdRead (a positive return value), you should only use the number of bytes specified by the return value of this function in the memory pointed to by the DataIn parameter. It is not guaranteed that the data returned will be null-terminated. ============================================================================= FUNCTION: RCmdReadByte Syntax: ------- C: INT PASCAL RCmdReadByte(hRCmd) int hRCmd; Visual Basic: Declare Function RCmdReadByte Lib "RCMD32.DLL" (ByVal hRCmd As Long) As Long Powerbuilder: Function Int RCmdReadByte (Int hRCmd) Library "RCMD32.DLL" Usage: ------ The RCmdReadByte function, like the RCmdRead function, reads the output of the command executed with WinsockRCmd. However, it instead reads one character at a time and returns each character as an integer return value. The RCmdReadByte function is useful when it is not possible or not convenient to pass a pointer to an area of memory as required by RCmdRead. Parameters: ----------- hRCmd: This is the integer "handle" returned from the WinsockRCmd function. Return Value: ------------- If RCmdReadByte is sucessful, it returns the next character read from the standard output or standard error of the command executed. It will be a positive number between 1 and 255. If RCmdReadByte returns zero, there is no more data to read. The command has ended and you should call RCmdClose to terminate the connection. If RCmdReadByte returns a negative number, an error has occurred. The number will be the Windows Sockets error number (but negative). You should call RCmdClose even if an error occurs to free all resources used by the connection. ============================================================================= Syntax: ------- C: INT PASCAL RCmdClose (hRCmd) int hRCmd; Visual Basic: Declare Function RCmdClose Lib "RCMD32.DLL" (ByVal hRCmd As Long) As Long Powerbuilder: Function Int RCmdClose (Int hRCmd) Library "RCMD32.DLL" Usage: ------ The RCmdClose function closes the connection to the remote host and frees all resources used by the connection. You should call RCmdClose for each successful use of the WinsockRCmd function. Parameters: ----------- hRCmd: This is the integer "handle" returned from the WinsockRCmd function. Return Value: ------------ If RCmdClose is sucessful, it returns zero. If it is unsuccessful, it returns a negative number that is the Windows Sockets error number (but negative). ============================================================================= Syntax: ------- C: INT PASCAL RCmdHandle (hRCmd) int hRCmd; Visual Basic: Declare Function RCmdHandle Lib "RCMD32.DLL" (ByVal hRCmd As Long) As Long Powerbuilder: Function Int RCmdHandle (Int hRCmd) Library "RCMD32.DLL" Usage: ------ The RCmdHandle function returns the Windows Sockets handle for the connection, which can be used to call any Windows Socket function. Parameters: ----------- hRCmd: This is the integer "handle" returned from the WinsockRCmd function. Return Value: ------------- RCmdHandle returns a Windows Sockets handle. If you call RCmdHandle on a WinsockRCmd connection that is not opened, the return value is undefined. Keep in mind that the handle returned by the WinsockRCmd function is a handle internally used by Winsock RCMD32.DLL; it is NOT a handle to be used to call Windows API functions. You must obtain the actual handle of the socket using this function to use the Winsock API calls on the socket created by WinsockRCmd. ============================================================================ DETERMINING THE USER NAME ------------------------- You can pass a NULL in either the LocalUser or RemoteUser parameters in the WinsockRCmd function call and Winsock RCMD32.DLL will determine the user name. The following ONLY applies if you pass a NULL in one of these parameters. The local user name is normally the name you used when logging in to Windows NT or Windows 95. For example, if you logged in to Windows as the user "joed", WinsockRCmd will use "joed" as the user name at the remote host. WinsockRCmd will always convert this name to all lowercase characters. To maintain some compatibility with the 16-bit version, WinsockRCmd will also continue to look at the file WIN.INI in the Windows directory (e.g. \WINNT35 or \WIN95) for an alternate user name. If WIN.INI contains a section named "[RCMD]" and contains an entry named "User" in that section, the name specified there will be used as the local user name. For example, WIN.INI might contain: [RCMD] User=joe If this appeared in WIN.INI, the local user name would be "joe" and WinsockRCmd would use this name at the remote host. To support multiple users, WinsockRCmd will also look for a section named "[RCMD-user]" in WIN.INI first for an alternate user name, where the "user" in the section name is the name used to log in to Windows NT/95. WinsockRCmd will look at this section first; if it does not exist, it will then look at the "[RCMD]" section. For example, if "Mary Jones" and "John Smith" are both users on the Windows PC, but their user names at the remote host are "mary" and "john" respectively, WIN.INI might look like this: [RCMD-John Smith] User=john [RCMD-Mary Jones] User=mary When the Windows user "John Smith" runs a program using WinsockRCmd, "john" will be used at the remote host. When the Windows user "Mary Jones" runs the program, "mary" will be used instead. Please note that the use of WIN.INI is supported only to maintain some compatibility with the 16-bit version of RCMD.DLL. It is highly recommended that you maintain the same user names across your systems. SECURITY -------- Security is enforced at the *HOST* system, not by Winsock RCMD32.DLL. If you are receiving errors pertaining to security, such as "Login incorrect" or "Connection refused", the problem is most likely on the host system. If the host system is a Unix system and you are using port 512 (the rshd daemon), the remote host allows access only if at least one of the following conditions is satisfied: * The name of the local host is listed as an equivalent host in the /etc/hosts.equiv file on the remote host. The name of the local host must also be listed in the /etc/hosts file along with it's proper IP address. * If the local host is not in the /etc/hosts.equiv file, the user's home directory on the remote host must contain a .rhosts file that lists the local host and user name. The .rhosts file in the user's home directory must be owned by either the user specified or root, and must have permissions of 0600. * The user's login on the remote host does not require a password. If you are using port 514 (the rexecd daemon), then the above conditions do not apply. You must simply supply the correct password for the user. EXAMPLE - C ----------- For a complete example of the use of WinsockRCmd in C, see the CRSH program provided in the distribution. See the file PB.DOC for an example using Powersoft's Powerbuilder. // We will let WinsockRCmd user our login name as the user name by // passing NULL values. int hRCmd; char c; char *rhost = "unix486"; char *cmd = "ls -x /usr" char errmsg[128]; hRCmd = WinsockRCmd(rhost,514,NULL,NULL,cmd,errmsg,sizeof(errmsg)); if (hRCmd < 0) MessageBox(NULL,errmsg,"Remote Shell",MB_OK); else { while(RCmdRead(hRCmd,&c,1) > 0) putchar(c); } RCmdClose(hRCmd); EXAMPLE - VISUAL BASIC ---------------------- This requires that you use a version of Visual Basic that can call 32-bit DLL functions. Host$ = "unix486" User$ = "fred" Cmd$ = "cat /etc/inittab" EMsg$ = String$(128,Chr$(0)) ' Allocate 128 bytes ELen% = 128 RetryCount% = 0 ' Execute the command. We will retry on the Winsock errors ' -10058 and ' -10061. These are the Shutdown and Connection Refused errors that can ' be returned occasionally. A retry will usually give success. Do hRCmd% = WinsockRCmd(Host$, 514, User$, User$, Cmd$, EMsg$, ELen%) RetryCount% = RetryCount% + 1 Loop While (hRCmd% = -10058 Or hRCmd% = -10061) And RetryCount% < 10 If hRCmd% < 0 Then Beep e$ = "Command Was Unsuccessful" & Chr$(10) & Trim$(EMsg$) MsgBox e$ Exit End If Do c$ = String$(64, Chr$(0)) result% = RCmdRead(hRCmd%, c$, 64) If result% > 0 Then Print Left$(c$, result%); End If Loop While result% > 0 result% = RCmdClose(hRCmd%) MsgBox "Command Was Successful" SUPPORT ------- Support is available via U.S. Mail and Compuserve/Internet: Denicomp Systems P.O. Box 731 Exton, PA 19341 Compuserve: 71612,2333 Internet: 71612.2333@compuserve.com