Sign in Join
Wiki copytemporary

Synapse: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Synapse

OldNewLine
1+Synapse provides an easy to use serial port and synchronous TCP/IP library.
2+
3+## Overview
4+
5+Synapse offers [[Hardware Access|serial]] port and TCP/IP connectivity. It differs from other libraries that you only require to add some Synapse Pascal source code files to your code; no need for installing packages etc. The only exception is that you will need an external crypto library if you want to use encryption such as SSL/TLS/SSH.
6+
7+See the documentation on the official site (link below) for more details.
8+
9+## Installation
10+
11+Installation can be as simple as simply copying over all files to your application directory and adding the relevant Synapse units to your *uses* clause.
12+
13+A more elegant and recommended way is compiling the laz_synapse.lpk package so you can use the same units in all your projects.
14+
15+Synapse download / Wiki info page: [Download and Wiki](https://github.com/geby/synapse)
16+
17+## Support and bug reporting
18+
19+The Synapse project has a mailing list where support is given and patches can be submitted.
20+
21+Bug reports can also be mailed to the mailing list.
22+
23+See the [Synapse support page](http://www.ararat.cz/synapse/doku.php/support) and some well presented [on line help](http://synapse.ararat.cz/doc/help/)
24+
25+## SSL/TLS support
26+
27+You can use OpenSSL, CryptLib, StreamSecII or OpenStreamSecII SSL support with Synapse. By default, no SSL support is used.
28+
29+The support is activated by putting the chosen unit name in the uses section in your project. You also have to put the binary library file in your project path (Windows), or install it into your library search path (Linux, macOS, FreeBSD).
30+
31+Synapse loads SSL library files in runtime as dynamic libraries.
32+
33+- For detailed information refer to [SSL/TLS Plugin Architecture](http://www.ararat.cz/synapse/doku.php/public:howto:sslplugin)
34+- Some crypt libraries can be obtained from: <http://synapse.ararat.cz/files/crypt/>
35+
36+### Missing library
37+
38+On Linux you need to make sure the required dynamic library is present/installed on your system. In case of cryptlib if the library is not present on the system, an error message appears during linking:
39+
40+`/usr/bin/ld: cannot find -lcl`
41+
42+A similar message will be displayed when using other dynamic libraries.
43+
44+## Web server example
45+
46+See the [[Networking|Webserver example]].
47+
48+## QOTD server query example
49+
50+See the [[QOTD|Quote of the Day server query example]].
51+
52+## Sending email
53+
54+Refer to the [[Synapse - Email Examples]] article.
55+
56+## Downloading files
57+
58+### From an FTP server
59+
60+Given an URL and a (path and) file name, this will download it from an FTP server. It's mostly a wrapper around the Synapse code meant to make downloading easier when handling arbitrary files. If you know exactly what you're going to download where, just a call to Synapse:
61+
62+``` pascal
63+FtpGetFile
64+```
65+
66+will get you very far.
67+
68+``` pascal
69+function DownloadFTP(URL, TargetFile: string): boolean;
70+const
71+ FTPPort=21;
72+ FTPScheme='ftp://'; //URI scheme name for FTP URLs
73+var
74+ Host: string;
75+ Port: integer;
76+ Source: string;
77+ FoundPos: integer;
78+begin
79+ // Strip out scheme info:
80+ if LeftStr(URL, length(FTPScheme))=FTPScheme then URL:=Copy(URL, length(FTPScheme)+1, length(URL));
81+
82+ // Crude parsing; could have used URI parsing code in FPC packages...
83+ FoundPos:=pos('/', URL);
84+ Host:=LeftStr(URL, FoundPos-1);
85+ Source:=Copy(URL, FoundPos+1, Length(URL));
86+
87+ //Check for port numbers:
88+ FoundPos:=pos(':', Host);
89+ Port:=FTPPort;
90+ if FoundPos>0 then
91+ begin
92+ Host:=LeftStr(Host, FoundPos-1);
93+ Port:=StrToIntDef(Copy(Host, FoundPos+1, Length(Host)),21);
94+ end;
95+ Result:=FtpGetFile(Host, IntToStr(Port), Source, TargetFile, 'anonymous', 'fpc@example.com');
96+ if result=false then writeln('DownloadFTP: error downloading '+URL+'. Details: host: '+Host+'; port: '+Inttostr(Port)+'; remote path: '+Source+' to '+TargetFile);
97+end;
98+```
99+
100+Example to get list of files in given path
101+
102+``` pascal
103+//Use ftpsend unit
104+
105+function FtpGetDir(const IP, Port, Path, User, Pass: string; DirList: TStringList): Boolean;
106+var
107+ i: Integer;
108+ s: string;
109+begin
110+ Result := False;
111+ with TFTPSend.Create do
112+ try
113+ Username := User;
114+ Password := Pass;
115+ TargetHost := IP;
116+ TargetPort := Port;
117+ if not Login then
118+ Exit;
119+ Result := List(Path, False);
120+ for i := 0 to FtpList.Count -1 do
121+ begin
122+ s := FTPList[i].FileName;
123+ DirList.Add(s);
124+ end;
125+ Logout;
126+ finally
127+ Free;
128+ end;
129+end;
130+```
131+
132+### From an HTTP server
133+
134+Given an URL and a (path and) file name, this will download it from an HTTP server. Note that this code checks the HTTP status code (like 200, 404) to see if the document we got back from the server is the desired file or an error page.
135+
136+#### Simple version
137+
138+``` pascal
139+...
140+uses httpsend,
141+...
142+function DownloadHTTP(URL, TargetFile: string): Boolean;
143+var
144+ HTTPGetResult: Boolean;
145+ HTTPSender: THTTPSend;
146+begin
147+ Result := False;
148+ HTTPSender := THTTPSend.Create;
149+ try
150+ HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
151+ if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then begin
152+ HTTPSender.Document.SaveToFile(TargetFile);
153+ Result := True;
154+ end;
155+ finally
156+ HTTPSender.Free;
157+ end;
158+end;
159+```
160+
161+#### Advanced version
162+
163+``` pascal
164+...
165+uses httpsend
166+...
167+function DownloadHTTP(URL, TargetFile: string): Boolean;
168+// Download file; retry if necessary.
169+// Could use Synapse HttpGetBinary, but that doesn't deal
170+// with result codes (i.e. it happily downloads a 404 error document)
171+const
172+ MaxRetries = 3;
173+var
174+ HTTPGetResult: Boolean;
175+ HTTPSender: THTTPSend;
176+ RetryAttempt: Integer;
177+begin
178+ Result := False;
179+ RetryAttempt := 1;
180+ HTTPSender := THTTPSend.Create;
181+ try
182+ try
183+ // Try to get the file
184+ HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
185+ while (HTTPGetResult = False) and (RetryAttempt < MaxRetries) do
186+ begin
187+ Sleep(500 * RetryAttempt);
188+ HTTPSender.Clear;
189+ HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
190+ RetryAttempt := RetryAttempt + 1;
191+ end;
192+ // If we have an answer from the server, check if the file
193+ // was sent to us.
194+ case HTTPSender.Resultcode of
195+ 100..299:
196+ begin
197+ HTTPSender.Document.SaveToFile(TargetFile);
198+ Result := True;
199+ end; //informational, success
200+ 300..399: Result := False; // redirection. Not implemented, but could be.
201+ 400..499: Result := False; // client error; 404 not found etc
202+ 500..599: Result := False; // internal server error
203+ else Result := False; // unknown code
204+ end;
205+ except
206+ // We don't care for the reason for this error; the download failed.
207+ Result := False;
208+ end;
209+ finally
210+ HTTPSender.Free;
211+ end;
212+end;
213+```
214+
215+#### Simple version with progress
216+
217+The following example shows how to get progress information from the HTTP download, as well as the file size. The file size is retrieved from the header information.
218+
219+``` pascal
220+unit uhttpdownloader;
221+
222+// Essential to change this. The default is {$mode objfpc}{$H+} and does not work.
223+{$mode Delphi}
224+
225+interface
226+
227+uses
228+ Classes, SysUtils, httpsend, blcksock, typinfo;
229+
230+type
231+ IProgress = interface
232+ procedure ProgressNotification(Text: String; CurrentProgress : integer; MaxProgress : integer);
233+ end;
234+
235+type
236+ { THttpDownloader }
237+
238+ THttpDownloader = class
239+ public
240+ function DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
241+ private
242+ Bytes : Integer;
243+ MaxBytes : Integer;
244+ HTTPSender: THTTPSend;
245+ ProgressMonitor : IProgress;
246+ procedure Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
247+ function GetSizeFromHeader(Header: String):integer;
248+ end;
249+
250+implementation
251+
252+function THttpDownloader.DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
253+var
254+ HTTPGetResult: Boolean;
255+begin
256+ Result := False;
257+ Bytes:= 0;
258+ MaxBytes:= -1;
259+ Self.ProgressMonitor:= ProgressMonitor;
260+
261+ HTTPSender := THTTPSend.Create;
262+ try
263+ HTTPSender.Sock.OnStatus:= Status;
264+ HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
265+ if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then begin
266+ HTTPSender.Document.SaveToFile(TargetFile);
267+ Result := True;
268+ end;
269+ finally
270+ HTTPSender.Free;
271+ end;
272+end;
273+
274+procedure THttpDownloader.Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
275+var
276+ V, currentHeader: String;
277+ i: integer;
278+begin
279+ //try to get filesize from headers
280+ if (MaxBytes = -1) then
281+ begin
282+ for i:= 0 to HTTPSender.Headers.Count - 1 do
283+ begin
284+ currentHeader:= HTTPSender.Headers[i];
285+ MaxBytes:= GetSizeFromHeader(currentHeader);
286+ if MaxBytes <> -1 then break;
287+ end;
288+ end;
289+
290+ V := GetEnumName(TypeInfo(THookSocketReason), Integer(Reason)) + ' ' + Value;
291+
292+ if Reason = THookSocketReason.HR_ReadCount then
293+ begin
294+ Bytes:= Bytes + StrToInt(Value);
295+ ProgressMonitor.ProgressNotification(V, Bytes, MaxBytes);
296+ end;
297+end;
298+
299+function THttpDownloader.GetSizeFromHeader(Header: String): integer;
300+var
301+ item : TStringList;
302+begin
303+ Result:= -1;
304+
305+ if Pos('Content-Length:', Header) <> 0 then
306+ begin
307+ item:= TStringList.Create();
308+ item.Delimiter:= ':';
309+ item.StrictDelimiter:=true;
310+ item.DelimitedText:=Header;
311+ if item.Count = 2 then
312+ begin
313+ Result:= StrToInt(Trim(item[1]));
314+ end;
315+ end;
316+end;
317+
318+end.
319+```
320+
321+What are we doing here?
322+
323+First of all we look into the headers to get the file size. We have to wait and check if the header is there. The first events do not contain the Content-Length: information.
324+
325+Once found, we extract that information. There are several events popping up here, which you can react to. But we only check for THookSocketReason.HR_ReadCount in that example.
326+
327+"HR_ReadCount" provides us with the information how many bytes where read since the last event.
328+
329+The progress is then reported to the UI:
330+
331+``` pascal
332+procedure TMainForm.ProgressNotification(Text: String; CurrentProgress: integer; MaxProgress: integer);
333+begin
334+ if (MaxProgress <> -1) then
335+ begin
336+ ProgressBar.Max:= MaxProgress;
337+ end;
338+
339+ ProgressBar.Position:= CurrentProgress;
340+ memoStatus.Lines.Add(Text);
341+ Application.ProcessMessages;
342+end;
343+```
344+
345+So, the final main unit will be:
346+
347+``` pascal
348+unit uMain;
349+
350+{$mode Delphi}
351+
352+interface
353+
354+uses
355+ Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ComCtrls, httpsend, blcksock, typinfo,
356+ uhttpdownloader;
357+
358+type
359+
360+ { TMainForm }
361+
362+ TMainForm = class(TForm, IProgress)
363+ btnStartDownload: TButton;
364+ edtUrl: TEdit;
365+ labelUrl: TLabel;
366+ memoStatus: TMemo;
367+ ProgressBar: TProgressBar;
368+ SaveDialog: TSaveDialog;
369+ procedure btnStartDownloadClick(Sender: TObject);
370+ private
371+ { private declarations }
372+ function GetFileNameFromURL(url: String):string;
373+ public
374+ { public declarations }
375+ procedure ProgressNotification(Text: String; CurrentProgress : integer; MaxProgress : integer);
376+ end;
377+
378+var
379+ MainForm: TMainForm;
380+
381+implementation
382+
383+{$R *.lfm}
384+
385+{ TMainForm }
386+
387+procedure TMainForm.btnStartDownloadClick(Sender: TObject);
388+var
389+ fileName: String;
390+ downloader: THttpDownloader;
391+ success: boolean;
392+begin
393+ fileName:= GetFileNameFromURL(edtUrl.Text);
394+ SaveDialog.FileName:=fileName;
395+ if (SaveDialog.Execute) then
396+ begin
397+ memoStatus.Lines.Clear;
398+ ProgressBar.Position:=0;
399+ downloader:= THttpDownloader.Create();
400+ success:= downloader.DownloadHTTP(edtUrl.Text, SaveDialog.FileName, Self);
401+
402+ ProgressBar.Position:=0;
403+ if Success then
404+ memoStatus.Lines.Add('Download successful')
405+ else
406+ memoStatus.Lines.Add('Error during download');
407+
408+ end;
409+end;
410+
411+function TMainForm.GetFileNameFromURL(url: String): string;
412+var i, l : integer;
413+ fileName, current : String;
414+begin
415+ fileName:= '';
416+ l:= Length(url);
417+ for i:= l downto 0 do begin
418+ current:= url[i];
419+ if current <> '/' then
420+ begin
421+ fileName:= current + fileName;
422+ end else begin
423+ Result:= fileName;
424+ break;
425+ end;
426+ end;
427+end;
428+
429+procedure TMainForm.ProgressNotification(Text: String; CurrentProgress: integer; MaxProgress: integer);
430+begin
431+ if (MaxProgress <> -1) then ProgressBar.Max:= MaxProgress;
432+ ProgressBar.Position:= CurrentProgress;
433+ memoStatus.Lines.Add(Text);
434+ Application.ProcessMessages;
435+end;
436+
437+end.
438+```
439+
440+Reference: <https://andydunkel.net/2015/09/09/lazarus_synapse_progress/>
441+
442+#### From an HTTP server by parsing URLs: Sourceforge
443+
444+Please see [[Download from SourceForge]] for an example of downloading from sourceforge.net.
445+
446+### From an HTTPS server
447+
448+This is similar to downloading from an HTTP server. In addition you need to [[Synapse|activate SSL/TLS support]] and obtain the binary file(s) for the needed library. Then you can use the same DownloadHTTP function for downloading a file from a URL starting with **<https://>**.
449+
450+## SSH/Telnet client sample program
451+
452+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](http://leonardorame.blogspot.com/2010/01/synapse-based-ssh-client.html). His example cannot use telnet and only sends one command, though.
453+
454+### Requirements
455+
456+Apart from the Synapse sources (of which you only need a couple), if you want to use SSH functionality, you will need an encryption library that Synapse uses. If you only use Telnet, you don't need this.
457+
458+There are 2 choices:
459+
460+- Cryptlib library. Advantage: stable. Apparently able to use private keys but these are in some format that is not widely supported.
461+- LibSSH2 library. Pascal bindings still in development, but you can use a file with your private key (in OpenSSH format) to authenticate.
462+
463+#### Cryptlib
464+
465+- 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.
466+- On Linux and OSX, install cryptlib via your package manager/other means. When distributing your application, mark cryptlib as a requirement in your .deb/.rpm/whatever package.
467+
468+You will also need the bindings (cryptlib.pas), present in the source distribution of cryptlib.
469+
470+The versions of the cryptlib binary and the bindings must match.
471+
472+> **Note:** It seems that cryptlib is not suitable to connect to linux machines, though AIX works. Use SSH2 instead.
473+
474+#### LibSSH2
475+
476+- On Windows, download a binary version of the libssh2 DLL (LIBSSH2.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.
477+- On Linux and macOS, install libssh2 via your package manager/other means. When distributing your application:
478+ - Linux: mark libssh2 as a requirement in your .deb/.rpm/whatever package.
479+ - macOS: include libssh2 in your [[Application Bundle]]'s Resources directory.
480+
481+You will also need ssl_libssh2.pas (see below) and the bindings: (libssh2.pas, see [this forum post](http://www.lazarus.freepascal.org/index.php/topic,15935.msg86465.html#msg86465)). The libssh2 binary and the bindings must match.
482+
483+### Synapse libssh2 SSL plugin
484+
485+> **Note:** plugin is not completed.
486+
487+``` pascal
488+{
489+ ssl_libssh2.pas version 0.2
490+
491+ SSH2 support (draft) plugin for Synapse Library (http://www.ararat.cz/synapse) by LibSSH2 (http://libssh2.org)
492+ Requires: libssh2 pascal interface - http://www.lazarus.freepascal.org/index.php/topic,15935.msg86465.html#msg86465 and
493+ libssh2.dll with OpenSSL.
494+
495+ (С) Alexey Suhinin http://x-alexey.narod.ru
496+}
497+
498+{$IFDEF FPC}
499+ {$MODE DELPHI}
500+{$ENDIF}
501+{$H+}
502+
503+unit ssl_libssh2;
504+
505+interface
506+
507+uses
508+ SysUtils,
509+ blcksock, synsock,
510+ libssh2;
511+
512+type
513+ {:@abstract(class implementing CryptLib SSL/SSH plugin.)
514+ Instance of this class will be created for each @link(TTCPBlockSocket).
515+ You not need to create instance of this class, all is done by Synapse itself!}
516+ TSSLLibSSH2 = class(TCustomSSL)
517+ protected
518+ FSession: PLIBSSH2_SESSION;
519+ FChannel: PLIBSSH2_CHANNEL;
520+ function SSHCheck(Value: integer): Boolean;
521+ function DeInit: Boolean;
522+ public
523+ {:See @inherited}
524+ constructor Create(const Value: TTCPBlockSocket); override;
525+ destructor Destroy; override;
526+ function Connect: boolean; override;
527+ function LibName: String; override;
528+ function Shutdown: boolean; override;
529+ {:See @inherited}
530+ function BiShutdown: boolean; override;
531+ {:See @inherited}
532+ function SendBuffer(Buffer: TMemory; Len: Integer): Integer; override;
533+ {:See @inherited}
534+ function RecvBuffer(Buffer: TMemory; Len: Integer): Integer; override;
535+ {:See @inherited}
536+ function WaitingData: Integer; override;
537+ {:See @inherited}
538+ function GetSSLVersion: string; override;
539+ published
540+ end;
541+
542+implementation
543+
544+{==============================================================================}
545+function TSSLLibSSH2.SSHCheck(Value: integer): Boolean;
546+var
547+ PLastError: PAnsiChar;
548+ ErrMsgLen: Integer;
549+begin
550+ Result := true;
551+ FLastError := 0;
552+ FLastErrorDesc := '';
553+ if Value<0 then
554+ begin
555+ FLastError := libssh2_session_last_error(FSession, PLastError, ErrMsglen, 0);
556+ FLastErrorDesc := PLastError;
557+ Result := false;
558+ end;
559+end;
560+
561+function TSSLLibSSH2.DeInit: Boolean;
562+begin
563+ if Assigned(FChannel) then
564+ begin
565+ libssh2_channel_free(FChannel);
566+ FChannel := nil;
567+ end;
568+ if Assigned(FSession) then
569+ begin
570+ libssh2_session_disconnect(FSession,'Goodbye');
571+ libssh2_session_free(FSession);
572+ FSession := nil;
573+ end;
574+ FSSLEnabled := False;
575+ Result := true;
576+end;
577+
578+constructor TSSLLibSSH2.Create(const Value: TTCPBlockSocket);
579+begin
580+ inherited Create(Value);
581+ FSession := nil;
582+ FChannel := nil;
583+end;
584+
585+destructor TSSLLibSSH2.Destroy;
586+begin
587+ DeInit;
588+ inherited Destroy;
589+end;
590+
591+function TSSLLibSSH2.Connect: boolean;
592+begin
593+ Result := False;
594+ if SSLEnabled then DeInit;
595+ if (FSocket.Socket <> INVALID_SOCKET) and (FSocket.SSL.SSLType = LT_SSHv2) then
596+ begin
597+ FSession := libssh2_session_init();
598+ if not Assigned(FSession) then
599+ begin
600+ FLastError := -999;
601+ FLastErrorDesc := 'Cannot initialize SSH session';
602+ exit;
603+ end;
604+ if not SSHCheck(libssh2_session_startup(FSession, FSocket.Socket)) then
605+ exit;
606+ if (FSocket.SSL.PrivateKeyFile<>'') then
607+ begin
608+ if (not SSHCheck(libssh2_userauth_publickey_fromfile(FSession, PChar(FSocket.SSL.Username), nil, PChar(FSocket.SSL.PrivateKeyFile), PChar(FSocket.SSL.KeyPassword)))) then
609+ exit;
610+ end
611+ else
612+ if (FSocket.SSL.Username<>'') and (FSocket.SSL.Password<>'') then
613+ begin
614+ if (not SSHCheck(libssh2_userauth_password(FSession, PChar(FSocket.SSL.Username), PChar(FSocket.SSL.Password)))) then
615+ exit;
616+ end;
617+ FChannel := libssh2_channel_open_session(FSession);
618+ if not assigned(FChannel) then
619+ begin
620+ SSHCheck(-1); // get error
621+ if FLastError = 0 then
622+ begin
623+ FLastError := -999; // unknown error
624+ FLastErrorDesc := 'Cannot open session';
625+ end;
626+ exit;
627+ end;
628+ if not SSHCheck(libssh2_channel_request_pty(FChannel, 'vanilla')) then
629+ exit;
630+ if not SSHCheck(libssh2_channel_shell(FChannel)) then
631+ exit;
632+ FSSLEnabled := True;
633+ Result := True;
634+ end;
635+end;
636+
637+function TSSLLibSSH2.LibName: String;
638+begin
639+ Result := 'ssl_libssh2';
640+end;
641+
642+function TSSLLibSSH2.Shutdown: boolean;
643+begin
644+ Result := DeInit;
645+end;
646+
647+function TSSLLibSSH2.BiShutdown: boolean;
648+begin
649+ Result := DeInit;
650+end;
651+
652+function TSSLLibSSH2.SendBuffer(Buffer: TMemory; Len: Integer): Integer;
653+begin
654+ Result:=libssh2_channel_write(FChannel, PChar(Buffer), Len);
655+ SSHCheck(Result);
656+end;
657+
658+function TSSLLibSSH2.RecvBuffer(Buffer: TMemory; Len: Integer): Integer;
659+begin
660+ result:=libssh2_channel_read(FChannel, PChar(Buffer), Len);
661+ SSHCheck(Result);
662+end;
663+
664+function TSSLLibSSH2.WaitingData: Integer;
665+begin
666+ if libssh2_poll_channel_read(FChannel, Result) <> 1 then Result := 0;
667+end;
668+
669+function TSSLLibSSH2.GetSSLVersion: string;
670+begin
671+ Result:=libssh2_version(0);
672+end;
673+
674+initialization
675+ if libssh2_init(0)=0 then
676+ SSLImplementation := TSSLLibSSH2;
677+
678+finalization
679+ libssh2_exit;
680+
681+end.
682+```
683+
684+### Terminal client class
685+
686+The telnetsshclient.pas unit below wraps around the Synapse tlntsend.pas unit and abstracts logging in, sending commands and receiving output and logging out.
687+
688+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 libssh2 dll.
689+
690+This unit has been lightly tested on a Linux ssh/telnet server. Additional tests welcome.
691+
692+``` pascal
693+unit telnetsshclient;
694+
695+{ Wrapper around Synapse libraries and SSL library (libssh2+libssl
696+is used right now)
697+Download compiled Windows dll from e.g.
698+http://alxdm.dyndns-at-work.com:808/files/windll_libssh2.zip
699+Download FreePascal interface files:
700+http://www.lazarus.freepascal.org/index.php/topic,15935.msg86465.html#msg86465
701+
702+This unit allows the user to send Telnet or SSH commands and get the output
703+Thanks to Leonardo Rame
704+http://leonardorame.blogspot.com/2010/01/synapse-based-ssh-client.html
705+and Ludo Brands.
706+
707+Written by Reinier Olislagers 2011.
708+Modified for libssh2 by Alexey Suhinin 2012.
709+
710+License of code:
711+* MIT
712+* LGPLv2 or later (with FreePascal static linking exception)
713+* GPLv2 or later
714+according to your choice.
715+Free use allowed but please don't sue or blame me.
716+
717+Uses other libraries/components; different licenses may apply that also can influence the combined/compiled work.
718+}
719+
720+{$mode objfpc}{$H+}
721+{$DEFINE HAS_SSH_SUPPORT} //comment out if only telnet support required
722+{$DEFINE LIBSSH2}
723+
724+interface
725+
726+uses
727+ Classes, SysUtils,
728+ tlntsend
729+ {$IFDEF HAS_SSH_SUPPORT}
730+ {ssl - or actually ssh - libs required by tlntsend}
731+ {$IFDEF LIBSSH2}
732+ ssl_libssh2
733+ {$ELSE}
734+ ssl_cryptlib
735+ {$ENDIF}
736+ {$ENDIF HAS_SSH_SUPPORT} ;
737+
738+type
739+ TProtocolType = (Telnet, SSH); //Different means of connecting
740+ TServerType = (Unix, Windows); //line endings, mostly
741+ { TelnetSSHClient }
742+
743+ { TTelnetSSHClient }
744+
745+ TTelnetSSHClient = class(TTelnetSend)
746+ protected
747+ FConnected: boolean;
748+ FOutputPosition: integer; //Keeps track of position in output stream
749+ FProtocolType: TProtocolType;
750+ FServerLineEnding: string; //depends on FServerType
751+ FServerType: TServerType;
752+ FWelcomeMessage, FTelnetLoginPrompt, FTelnetPasswordPrompt: string;
753+ procedure SetPrivateKeyFile(Value: string);
754+ function GetPrivateKeyFile: string;
755+ { Based on protocol and servertype, set expected serverside line ending}
756+ procedure DetermineLineEnding;
757+ { Sets port if no explicit port set. Uses protocol type: SSH or telnet}
758+ procedure DeterminePort;
759+ function GetSessionLog: string;
760+ procedure ProtocolTypeChange(Value: TProtocolType);
761+ function ReceiveData: string; //Can be used to get welcome message etc.
762+ procedure SendData(Data: string);
763+ procedure ServerTypeChange(Value: TServerType);
764+ public
765+ {All output generated during the entire session up to now}
766+ property AllOutput: string read GetSessionLog;
767+ {True if connected to server}
768+ property Connected: boolean read FConnected;
769+ {Name or IP address of host to connect to}
770+ property HostName: string read FTargetHost write FTargetHost;
771+ {Port on host used for connection. If left as 0, it will be determined by protocol type (22 for SSH, 23 for Telnet}
772+ property Port: String read FTargetPort write FTargetPort;
773+ {Location of private key file.}
774+ property PrivateKeyFile: string read GetPrivateKeyFile write SetPrivateKeyFile;
775+ {Telnet login prompt}
776+ property TelnetLoginPrompt: string read FTelnetLoginPrompt write FTelnetLoginPrompt;
777+ {Telnet password prompt}
778+ property TelnetPasswordPrompt: string read FTelnetPasswordPrompt write FTelnetPasswordPrompt;
779+ {Username used when connecting}
780+ property UserName: string read FUserName write FUserName;
781+ {Password used when connecting. Used as passphrase if PrivateKey is used}
782+ property Password: string read FPassword write FPassword;
783+ {Should we talk Telnet or SSH to the server? Defaults to SSH.}
784+ property ProtocolType: TProtocolType read FProtocolType write ProtocolTypeChange;
785+ {Windows or Unix/Linux server? Has effect on line endings. Defaults to Unix. NOTE: untested}
786+ property Servertype: TServerType read FServerType write ServerTypeChange;
787+ {Initial message displayed on logon}
788+ property WelcomeMessage: string read FWelcomeMessage;
789+ {Connect/logon to server. Requires that all authentication, protocol and hostname/port options are correct
790+ Returns descriptive result. You can then use the Connected property.}
791+ function Connect: string;
792+ {If connected, logoff from server}
793+ procedure Disconnect;
794+ {Send command to server and receive result}
795+ function CommandResult(Command: string): string; //Send command and get results
796+ constructor Create;
797+ destructor Destroy; override;
798+ end;
799+
800+implementation
801+
802+
803+{ TelnetSSHClient }
804+procedure TTelnetSSHClient.SetPrivateKeyFile(value: string);
805+begin
806+ Sock.SSL.PrivateKeyFile := value;
807+end;
808+
809+function TTelnetSSHClient.GetPrivateKeyFile: string;
810+begin
811+ Result := Sock.SSL.PrivateKeyFile;
812+end;
813+
814+procedure TTelnetSSHClient.DetermineLineEnding;
815+begin
816+ case FProtocolType of
817+ SSH:
818+ begin
819+ if FServerType = Unix then
820+ FServerLineEnding := #10 //Unix
821+ else
822+ FServerLineEnding := #13 + #10; //windows
823+ end;
824+ Telnet:
825+ begin
826+ if FServerType = Unix then
827+ FServerLineEnding := #10 //Unix
828+ else
829+ FServerLineEnding := #13 + #10; //windows
830+ end;
831+ else
832+ raise Exception.Create('Unknown protocol type');
833+ end;
834+end;
835+
836+procedure Ttelnetsshclient.DeterminePort;
837+begin
838+ if FTargetPort = '' then
839+ //Set default port for protocol
840+ begin
841+ case FProtocolType of
842+ Telnet: FTargetPort := '23';
843+ SSH: FTargetPort := '22';
844+ else
845+ raise Exception.Create('Unknown protocol type.');
846+ end;
847+
848+ end;
849+end;
850+
851+procedure TTelnetSSHClient.ServerTypeChange(Value: Tservertype);
852+begin
853+ FServerType := Value;
854+ DetermineLineEnding;
855+end;
856+
857+function TTelnetSSHClient.Connect: string;
858+var
859+ Received: string;
860+begin
861+ result:='Unknown error while connecting';
862+ FOutputPosition := 1; //First character in output stream
863+ FWelcomeMessage := '';
864+ //Just to make sure:
865+ DetermineLineEnding;
866+ DeterminePort;
867+ if FTargetPort='0' then
868+ begin
869+ result:='Port may not be 0.';
870+ exit; //jump out of function
871+ end;
872+ case FProtocolType of
873+ Telnet:
874+ begin
875+ try
876+ if Login then
877+ begin
878+ FConnected := True;
879+ result:='Connected to telnet server.';
880+ end
881+ else
882+ if Sock.LastError<>0 then raise Exception.Create(Sock.LastErrorDesc);
883+ except
884+ on E: Exception do
885+ begin
886+ FConnected:=false;
887+ result:='Error connecting to telnet server '+FTargetHost+':'+
888+ FTargetPort+' as user ' + FUserName +
889+ '. Technical details: '+E.Message;
890+ end;
891+ end;
892+ end;
893+ SSH:
894+ begin
895+ {$IFNDEF HAS_SSH_SUPPORT}
896+ raise Exception.Create(
897+ 'SSH support has not been compiled into the telnetsshclient library.');
898+ {$ENDIF HAS_SSH_SUPPORT}
899+ try
900+ if (PrivateKeyFile <> '') and (FPassword <> '') then
901+ Sock.SSL.KeyPassword:=FPassword;
902+ if SSHLogin then
903+ begin
904+ FConnected := True;
905+ result:='Connected to SSH server.';
906+ end
907+ else
908+ begin
909+ if Sock.LastError<>0 then raise Exception.Create(Sock.LastErrorDesc);
910+ if Sock.SSL.LastError<0 then raise Exception.Create(Sock.SSL.LastErrorDesc);
911+ end;
912+ except
913+ on E: Exception do
914+ begin
915+ FConnected:=false;
916+ result:='Error connecting to SSH server '+FTargetHost+':'+
917+ FTargetPort+' as user ' + FUserName +
918+ '. Technical details: '+E.Message;
919+ end;
920+ end;
921+ end;
922+ else
923+ raise Exception.Create('Unknown protocol type');
924+ end;
925+ if FConnected = True then
926+ begin
927+ FWelcomeMessage := ReceiveData;
928+ if FProtocolType=Telnet then
929+ begin
930+ //Unfortunately, we'll have to extract login ourselves
931+ //Hope it applies to all server types.
932+ if (AnsiPos(AnsiLowerCase(FTelnetLoginPrompt),AnsiLowerCase(FWelcomeMessage))>0) then
933+ begin
934+ SendData(UserName);
935+ end;
936+ Received:=ReceiveData;
937+ if (AnsiPos(AnsiLowerCase(FTelnetPasswordPrompt),AnsiLowerCase(Received))>0) then
938+ begin
939+ SendData(Password);
940+ end;
941+ //Receive additional welcome message/message of the day
942+ FWelcomeMessage:=FWelcomeMessage+LineEnding+ReceiveData;
943+ end;
944+ end;
945+end;
946+
947+procedure TTelnetSSHClient.Disconnect;
948+begin
949+ Logout;
950+ FConnected := False;
951+end;
952+
953+function TTelnetSSHClient.ReceiveData: string;
954+begin
955+ Result := '';
956+ while Sock.CanRead(1000) or (Sock.WaitingData > 0) do
957+ begin
958+ Sock.RecvPacket(1000);
959+ Result := Result + Copy(SessionLog, FOutputPosition,
960+ Length(SessionLog));
961+ FOutputPosition := Length(SessionLog) + 1;
962+ end;
963+end;
964+
965+procedure Ttelnetsshclient.SendData(Data: String);
966+begin
967+ Data := Data + FServerLineEnding; //Could be linux, could be Windows
968+ Send(Data);
969+end;
970+
971+function TTelnetSSHClient.GetSessionLog: string;
972+begin
973+ // Gets complete output up to now
974+ Result := SessionLog;
975+end;
976+
977+procedure TTelnetSSHClient.ProtocolTypeChange(Value: Tprotocoltype);
978+begin
979+ FProtocolType := Value;
980+ //Auto-determine port and line ending, if necessary
981+ DeterminePort;
982+ DetermineLineEnding;
983+end;
984+
985+function TTelnetSSHClient.CommandResult(Command: string): string;
986+begin
987+ Result := '';
988+ if FConnected then
989+ begin
990+ SendData(Command);
991+ Result := ReceiveData; //gets too much
992+ end
993+ else
994+ begin
995+ //raise exception
996+ Result := '';
997+ raise Exception.Create('Can only run command when connected');
998+ end;
999+end;
1000+
1001+constructor TTelnetSSHClient.Create;
1002+begin
1003+ inherited;
1004+ FConnected := False;
1005+ FProtocolType := SSH; //Could be telnet, too
1006+ FServerType := Unix; //Probably a safe default.
1007+ FTelnetLoginPrompt := 'login:';
1008+ FTelnetPasswordPrompt := 'password:';
1009+ DetermineLineEnding;
1010+ DeterminePort;
1011+end;
1012+
1013+destructor TTelnetSSHClient.Destroy;
1014+begin
1015+ if FConnected then
1016+ Disconnect;
1017+ inherited Destroy;
1018+end;
1019+
1020+end.
1021+```
1022+
1023+### Example client code
1024+
1025+To use the TTelnetSSHClient 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:
1026+
1027+``` pascal
1028+program sshtest;
1029+
1030+{Test program for telnetsshclient
1031+
1032+Written by Reinier Olislagers 2011.
1033+Modified for libssh2 by Alexey Suhinin 2012.
1034+
1035+License of code:
1036+* MIT
1037+* LGPLv2 or later (with FreePascal static linking exception)
1038+* GPLv2 or later
1039+according to your choice.
1040+Free use allowed but please don't sue or blame me.
1041+
1042+Uses other libraries/components; different licenses may apply that also can influence the combined/compiled work.
1043+
1044+Run: sshtest <serverIPorhostname> [PrivateKeyFile]
1045+}
1046+{$mode objfpc}{$H+}
1047+{$APPTYPE CONSOLE}
1048+
1049+uses
1050+ telnetsshclient;
1051+var
1052+ comm: TTelnetSSHClient;
1053+ Command: string;
1054+begin
1055+ writeln('Starting.');
1056+ comm:=TTelnetSSHClient.Create;
1057+ comm.HostName:= ParamStr(1); //First argument on command line
1058+ if comm.HostName='' then
1059+ begin
1060+ writeln('Please specify hostname on command line.');
1061+ halt(1);
1062+ end;
1063+
1064+ comm.PrivateKeyFile := ParamStr(2);
1065+
1066+ comm.TargetPort:='0'; //auto determine based on protocoltype
1067+ comm.UserName:='root'; //change to your situation
1068+ comm.Password:='password'; //change to your situation
1069+ comm.ProtocolType:=SSH; //Telnet or SSH
1070+ writeln(comm.Connect); //Show result of connection
1071+ if comm.Connected then
1072+ begin
1073+ writeln('Server: ' + comm.HostName + ':'+comm.TargetPort+', user: '+comm.UserName);
1074+ writeln('Welcome message:');
1075+ writeln(comm.WelcomeMessage);
1076+ Command:='ls -al';
1077+ writeln('*** Sending ' + Command);
1078+ writeln('*** Begin result****');
1079+ writeln(comm.CommandResult(Command));
1080+ writeln('*** End result****');
1081+ writeln('');
1082+ writeln('');
1083+ Command:='df -h';
1084+ writeln('*** Sending ' + Command);
1085+ writeln('*** Begin result****');
1086+ writeln(comm.CommandResult(Command));
1087+ writeln('*** End result****');
1088+ writeln('');
1089+ writeln('');
1090+ writeln('All output:');
1091+ writeln('*** Begin result****');
1092+ writeln(comm.AllOutput);
1093+ writeln('*** End result****');
1094+ comm.Disconnect;
1095+ end
1096+ else
1097+ begin
1098+ writeln('Connection to ' +
1099+ comm.HostName + ':' +
1100+ comm.TargetPort + ' failed.');
1101+ end;
1102+ comm.Free;
1103+end.
1104+```
1105+
1106+## OAuth v1/Twitter/Plurk integration
1107+
1108+An OAuth v1 library written in FPC that uses Synapse (and is ready for other network libraries like lNet) is available in [[fpctwit]]. FPCtwit also contains FPC Twitter and Plurk example client programs and a Lazarus Twitter client.
1109+
1110+## Other Web and Networking Articles
1111+
1112+- [[Portal:Web Development|Web Development Portal]]
1113+- [[Networking]]
1114+- [[Networking libraries]] - comparison of various networking libraries
1115+- [[Brook Framework]] - The perfect Free Pascal framework for your web applications. It's pure Pascal. You don't need to leave your preferred programming language.
1116+- [[Sockets]] - TCP/IP Sockets components
1117+- [[fcl-net]] - Networking library supplied with FPC
1118+- [[lNet]] - Lightweight Networking Components
1119+- [[XML Tutorial]] - XML is often utilized on network communications
1120+- [[FPC and Apache Modules]]
1121+- [[fcl-web]] - Also known as fpWeb, this is a library to develop web applications which can be deployed as cgi, fastcgi or apache modules.
1122+- [[Secure programming|Secure Programming]]
1123+- [[Internet Tools]] - A wrapper around Synapse/wininet/Android's http components simplifying https and redirections, and a XPath/XQuery/CSS Selector/JSONiq engine to process the downloaded pages
1124+
1125+## See also
1126+
1127+- [[Download from SourceForge]] Example that uses Synapse to download from an HTTP server that redirects.
1128+- [Visual Synapse](https://sourceforge.net/projects/visualsynapse/) component wrappers for many parts of Synapse serial and networking library (TvsComPort, TvsWebClient, TvsSniffer, TvsHTTPServer, TvsFTPServer, TvsAuthentication, TvsVisualDNS, TvsVisualHTTP, TvsVisualDUP, TvsVisualTCP, TvsVisualICMP, TvsSocksProxyInfo, TvsIPHelper, TvsSendMail and TvsSynPing).
1129+- [TCP/IP component based on Synapse + a small demo application](https://forum.lazarus.freepascal.org/index.php/topic,48677.0.html)
1130+
1131+## External links
1132+
1133+- [Official site](http://www.ararat.cz/synapse/)
1134+- [Official documentation](http://synapse.ararat.cz/doc/help/)
1135+- [Download and Wiki](https://github.com/geby/synapse/wiki)
1136+
1137+---
1138+*From the Free Pascal wiki, page "Synapse", as it stood on 2024-02-20 after a change by Dbannon, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history