Synapse

From Lazarus wiki
Revision as of 08:55, 22 November 2011 by BigChimp (talk | contribs) (Added SSH/telnet client example program)

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>

External links