Synapse
Provides a serial port and synchronous TCP/IP Library.
Linux
Unit cryptlib and the SSL part of Synapse require the cryptlib library. If library is not present on system an error message appears during linking:
/usr/bin/ld: cannot find -lcl
Note: it may very well be possible to use OpenSSL libraries for the SSL part of Synapse. You will also need a library and the openssl_* units from synapse.
SSH/Telnet client sample program
Below you will find a unit that allows you to use telnet/SSH client functionality that uses the synapse tlntsend.pas unit. An example program shows how to use this. A different, simpler way is illustrated by Leonardo Ramé at [1]. His example cannot use telnet and only sends one command, though.
Requirements
Apart from the Synapse sources (of which you only need a couple), you will need the cryptlib library. Suggestion:
- On Windows, download a binary version of the cryptlib DLL (CL32.DLL) and put it in your source directory. If you compile to a different directory or distribute your program, you will need to distribute the DLL as well.
- On Linux and OSX, install cryptlib via your package manager/other means (untested). When distributing your application, mark cryptlib as a requirement in your .deb/.rpm/whatever package.
The cryptlib binary and the Synapse source must match; as of November 2011, the Synapse sources require Cryptlib v3.3.
Terminal client class
The telnetsshclient.pas unit below wraps around the Synapse tlntsend.pas unit and abstracts logging in, sending commands and receiving output and logging out.
If you only need a telnet client and can live without SSH support, comment out {$DEFINE HAS_SSH_SUPPORT} below so you don't need to have the cryptlib dll.
This unit has been lightly tested on a Linux ssh/telnet server. Additional tests welcome.
<delphi> unit telnetsshclient;
{ Wrapper around Synapse libraries and SSL library (cryptlib is used right now) Download compiled Windows dll from e.g. http://dl.free.fr/izHgBttba Click on "Télécharger ce fichier"
This unit allows the user to send Telnet or SSH commands and get the output Thanks to Leonardo Ramé http://leonardorame.blogspot.com/2010/01/synapse-based-ssh-client.html and Ludo Brands.
Written by Reinier Olislagers 2011. License of my code:
- MIT
- LGPLv2 or later (with FreePascal static linking exception)
- GPLv2 or later
according to your choice. Free use allowed but please don't sue or blame me.
Uses other libraries/components; different licenses may apply that also can influence the combined/compiled work. }
{$mode objfpc}{$H+} {$DEFINE HAS_SSH_SUPPORT} //comment out if only telnet support required
interface
uses
Classes, SysUtils, tlntsend {$IFDEF HAS_SSH_SUPPORT} {ssl - or actually ssh - libs required by tlntsend} , ssl_cryptlib {Please include cryptlib dll in executable directory/install cryptlib .so/.dylib} {$ENDIF HAS_SSH_SUPPORT} ;
type
TProtocolType = (Telnet, SSH); //Different means of connecting TServerType = (Unix, Windows); //line endings, mostly { TelnetSSHClient }
{ TTelnetSSHClient }
TTelnetSSHClient = class(TObject) protected FTelnetSend: TTelnetSend; FConnected: boolean; FHostName: string; FOutputPosition: integer; //Keeps track of position in output stream FPort: integer; FPrivateKey: string; FPassword: string; FProtocolType: TProtocolType; FServerLineEnding: string; //depends on FServerType FServerType: TServerType; FUserName: string; FWelcomeMessage: string; { Based on protocol and servertype, set expected serverside line ending} procedure DetermineLineEnding; { Sets port if no explicit port set. Uses protocol type: SSH or telnet} procedure DeterminePort; function GetSessionLog: string; procedure ProtocolTypeChange(Value: TProtocolType); function ReceiveData: string; //Can be used to get welcome message etc. procedure SendData(Data: string); procedure ServerTypeChange(Value: TServerType); public {All output generated during the entire session up to now} property AllOutput: string read GetSessionLog; {True if connected to server} property Connected: boolean read FConnected; {Name or IP address of host to connect to} property HostName: string read FHostName write FHostName; {Port on host used for connection. If left as 0, it will be determined by protocol type (22 for SSH, 23 for Telnet} property Port: integer read FPort write FPort; {Location of private key file. NOTE: not supported yet} property PrivateKey: string read FPrivateKey write FPrivateKey; {Username used when connecting} property UserName: string read FUserName write FUserName; {Password used when connecting. Used as passphrase if PrivateKey is used} property Password: string read FPassword write FPassword; {Should we talk Telnet or SSH to the server? Defaults to SSH.} property ProtocolType: TProtocolType read FProtocolType write ProtocolTypeChange; {Windows or Unix/Linux server? Has effect on line endings. Defaults to Unix. NOTE: untested} property Servertype: TServerType read FServerType write ServerTypeChange; {Initial message displayed on logon} property WelcomeMessage: string read FWelcomeMessage; {Connect/logon to server. Requires that all authentication, protocol and hostname/port options are correct Returns descriptive result. You can then use the Connected property.} function Connect: string; {If connected, logoff from server} procedure Disconnect; {Send command to server and receive result} function CommandResult(Command: string): string; //Send command and get results constructor Create; destructor Destroy; override; end;
implementation
{ TelnetSSHClient }
procedure TTelnetSSHClient.DetermineLineEnding;
begin
case FProtocolType of SSH: begin if FServerType = Unix then FServerLineEnding := #10 //Unix else FServerLineEnding := #13 + #10; //windows end; Telnet: begin if FServerType = Unix then FServerLineEnding := #10 //Unix else FServerLineEnding := #13 + #10; //windows end; else raise Exception.Create('Unknown protocol type'); end;
end;
procedure Ttelnetsshclient.DeterminePort; begin
if Port = 0 then //Set default port for protocol begin case ProtocolType of Telnet: Port := 23; SSH: Port := 22; else raise Exception.Create('Unknown protocol type.'); end;
end;
end;
procedure TTelnetSSHClient.ServerTypeChange(Value: Tservertype); begin
FServerType := Value; DetermineLineEnding;
end;
function TTelnetSSHClient.Connect: string; const
TelnetLoginPrompt='login:'; //Must be lower case TelnetPasswordPrompt='password:'; //Must be lower case
var
Received: string;
begin
result:='Unknown error while connecting'; FOutputPosition := 1; //First character in output stream FWelcomeMessage := ; //Just to make sure: DetermineLineEnding; DeterminePort; if Port=0 then begin result:='Port may not be 0.'; exit; //jump out of function end; FTelnetSend.TargetHost := HostName; FTelnetSend.TargetPort := IntToStr(Port); FTelnetSend.UserName := UserName; if PrivateKey <> then begin result:='Private key use not supported.'; if Password <> then begin //Assume the password is the passphrase for the private key //todo: implement this. end; end else begin FTelnetSend.Password := Password; end; case FProtocolType of Telnet: begin try if FTelnetSend.Login then begin FConnected := True; result:='Connected to telnet server.'; end; except on E: Exception do begin FConnected:=false; result:='Error connecting to telnet server '+HostName+':'+ inttostr(Port)+' as user ' + UserName + '. Technical details: '+E.Message; end; end; end; SSH: begin {$IFNDEF HAS_SSH_SUPPORT} raise Exception.Create( 'SSH support has not been compiled into the telnetsshclient library.'); {$ENDIF HAS_SSH_SUPPORT} try if FTelnetSend.SSHLogin then begin FConnected := True; result:='Connected to SSH server.'; end; except on E: Exception do begin FConnected:=false; result:='Error connecting to SSH server '+HostName+':'+ inttostr(Port)+' as user ' + UserName + '. Technical details: '+E.Message; end; end; case FTelnetSend.Sock.SSL.LastError of -1: begin FConnected := False; raise Exception.Create( 'Cannot find cryptlib library or invalid version. Technical error description: ' + FTelnetSend.Sock.SSL.LastErrorDesc); end; 0: begin end;//everything hunky-dory. else begin end; //unknown error, let's continue for now.; end; end; else raise Exception.Create('Unknown protocol type'); end; if FConnected = True then begin FWelcomeMessage := ReceiveData; if ProtocolType=Telnet then begin //Unfortunately, we'll have to extract login ourselves //Hope it applies to all server types. if (AnsiPos(TelnetLoginPrompt,AnsiLowerCase(FWelcomeMessage))>0) then begin SendData(UserName); end; Received:=ReceiveData; if (AnsiPos(TelnetPasswordPrompt,AnsiLowerCase(Received))>0) then begin SendData(Password); end; //Receive additional welcome message/message of the day FWelcomeMessage:=FWelcomeMessage+LineEnding+ReceiveData; end; end;
end;
procedure TTelnetSSHClient.Disconnect; begin
FTelnetSend.Logout; FConnected := False;
end;
function TTelnetSSHClient.ReceiveData: string; begin
Result := ; while FTelnetSend.Sock.CanRead(1000) or (FTelnetSend.Sock.WaitingData > 0) do begin FTelnetSend.Sock.RecvPacket(1000); Result := Result + Copy(FTelnetSend.SessionLog, FOutputPosition, Length(FTelnetSend.SessionLog)); FOutputPosition := Length(FTelnetSend.SessionLog) + 1; end;
end;
procedure Ttelnetsshclient.SendData(Data: String); begin
Data := Data + FServerLineEnding; //Could be linux, could be Windows FTelnetSend.Send(Data);
end;
function TTelnetSSHClient.GetSessionLog: string; begin
// Gets complete output up to now Result := FTelnetSend.SessionLog;
end;
procedure TTelnetSSHClient.ProtocolTypeChange(Value: Tprotocoltype); begin
FProtocolType := Value; //Auto-determine port and line ending, if necessary DeterminePort; DetermineLineEnding;
end;
function TTelnetSSHClient.CommandResult(Command: string): string; begin
Result := ; if Connected then begin SendData(Command); Result := ReceiveData; //gets too much end else begin //raise exception Result := ; raise Exception.Create('Can only run command when connected'); end;
end;
constructor TTelnetSSHClient.Create; begin
FConnected := False; HostName := '127.0.0.1'; //Maybe we've got a local ssh server running ;) Port := 0; //if 0, gets automatically switched depending on terminal type UserName := 'root'; //default value Password := 'password'; //default value PrivateKey := ; ProtocolType := SSH; //Could be telnet, too ServerType := Unix; //Probably a safe default. DetermineLineEnding; DeterminePort; FTelnetSend := TTelnetSend.Create();
end;
destructor TTelnetSSHClient.Destroy; begin
if FConnected then Disconnect; FTelnetSend.Free; inherited Destroy;
end;
end. </delphi>
Example program
To use the class we just made, you can use this example application, sshtest.lpr. Note that it needs to be compiled by Lazarus as it needs the LCL components to work with Synapse: <delphi> program sshtest; {Test program for telnetsshclient
Written by Reinier Olislagers 2011. License of my code:
- MIT
- LGPLv2 or later (with FreePascal static linking exception)
- GPLv2 or later
according to your choice. Free use allowed but please don't sue or blame me.
Uses other libraries/components; different licenses may apply that also can influence the combined/compiled work.
Run: sshtest <serverIPorhostname> } {$mode objfpc}{$H+} {$APPTYPE CONSOLE}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF} Classes, Interfaces, // this includes the LCL widgetset SysUtils, Forms, telnetsshclient;
var
comm: TTelnetSSHClient; Command: string;
begin
writeln('Starting.'); comm:=TTelnetSSHClient.Create; comm.HostName:= ParamStr(1); //First argument on command line if comm.HostName= then begin writeln('Please specify hostname on command line.'); halt(1); end;
//comm.Port:=0; //auto determine based on protocoltype comm.UserName:='root'; //change to your situation comm.Password:='password'; //change to your situation comm.ProtocolType:=SSH; //Telnet or SSH writeln(comm.Connect); //Show result of connection if comm.Connected then begin writeln('Server: ' + comm.HostName + ':'+inttostr(comm.Port)+', user: '+comm.UserName); writeln('Welcome message:'); writeln(comm.WelcomeMessage); Command:='ls -al'; writeln('*** Sending ' + Command); writeln('*** Begin result****'); writeln(comm.CommandResult(Command)); writeln('*** End result****'); writeln(); writeln(); Command:='df -h'; writeln('*** Sending ' + Command); writeln('*** Begin result****'); writeln(comm.CommandResult(Command)); writeln('*** End result****'); writeln(); writeln(); writeln('All output:'); writeln('*** Begin result****'); writeln(comm.AllOutput); writeln('*** End result****'); comm.Disconnect; end else begin writeln('Connection to ' + comm.HostName + ':' + inttostr(comm.Port) + ' failed.'); end; comm.Free;
end. </delphi>