Sign in Join
Wiki copytemporary

Synapse: what changed

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

OldNewLine
11Synapse provides an easy to use serial port and synchronous TCP/IP library.
22
33## Overview
44
55Synapse 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.
66
77See the documentation on the official site (link below) for more details.
88
99## Installation
1010
1111Installation can be as simple as simply copying over all files to your application directory and adding the relevant Synapse units to your *uses* clause.
1212
1313A more elegant and recommended way is compiling the laz_synapse.lpk package so you can use the same units in all your projects.
1414
1515Synapse download / Wiki info page: [Download and Wiki](https://github.com/geby/synapse)
1616
1717## Support and bug reporting
1818
1919The Synapse project has a mailing list where support is given and patches can be submitted.
2020
2121Bug reports can also be mailed to the mailing list.
2222
2323See 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/)
2424
2525## SSL/TLS support
2626
2727You can use OpenSSL, CryptLib, StreamSecII or OpenStreamSecII SSL support with Synapse. By default, no SSL support is used.
2828
2929The 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).
3030
3131Synapse loads SSL library files in runtime as dynamic libraries.
3232
3333- For detailed information refer to [SSL/TLS Plugin Architecture](http://www.ararat.cz/synapse/doku.php/public:howto:sslplugin)
3434- Some crypt libraries can be obtained from: <http://synapse.ararat.cz/files/crypt/>
3535
3636### Missing library
3737
3838On 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:
3939
40-`/usr/bin/ld: cannot find -lcl`
40+```text
41+/usr/bin/ld: cannot find -lcl
42+```
4143
4244A similar message will be displayed when using other dynamic libraries.
4345
4446## Web server example
4547
4648See the [[Networking|Webserver example]].
4749
4850## QOTD server query example
4951
5052See the [[QOTD|Quote of the Day server query example]].
5153
5254## Sending email
5355
5456Refer to the [[Synapse - Email Examples]] article.
5557
5658## Downloading files
5759
5860### From an FTP server
5961
6062Given 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:
6163
6264``` pascal
6365FtpGetFile
6466```
6567
6668will get you very far.
6769
6870``` pascal
6971function DownloadFTP(URL, TargetFile: string): boolean;
7072const
7173 FTPPort=21;
7274 FTPScheme='ftp://'; //URI scheme name for FTP URLs
7375var
7476 Host: string;
7577 Port: integer;
7678 Source: string;
7779 FoundPos: integer;
7880begin
7981 // Strip out scheme info:
8082 if LeftStr(URL, length(FTPScheme))=FTPScheme then URL:=Copy(URL, length(FTPScheme)+1, length(URL));
8183
8284 // Crude parsing; could have used URI parsing code in FPC packages...
8385 FoundPos:=pos('/', URL);
8486 Host:=LeftStr(URL, FoundPos-1);
8587 Source:=Copy(URL, FoundPos+1, Length(URL));
8688
8789 //Check for port numbers:
8890 FoundPos:=pos(':', Host);
8991 Port:=FTPPort;
9092 if FoundPos>0 then
9193 begin
9294 Host:=LeftStr(Host, FoundPos-1);
9395 Port:=StrToIntDef(Copy(Host, FoundPos+1, Length(Host)),21);
9496 end;
9597 Result:=FtpGetFile(Host, IntToStr(Port), Source, TargetFile, 'anonymous', 'fpc@example.com');
9698 if result=false then writeln('DownloadFTP: error downloading '+URL+'. Details: host: '+Host+'; port: '+Inttostr(Port)+'; remote path: '+Source+' to '+TargetFile);
9799end;
98100```
99101
100102Example to get list of files in given path
101103
102104``` pascal
103105//Use ftpsend unit
104106
105107function FtpGetDir(const IP, Port, Path, User, Pass: string; DirList: TStringList): Boolean;
106108var
107109 i: Integer;
108110 s: string;
109111begin
110112 Result := False;
111113 with TFTPSend.Create do
112114 try
113115 Username := User;
114116 Password := Pass;
115117 TargetHost := IP;
116118 TargetPort := Port;
117119 if not Login then
118120 Exit;
119121 Result := List(Path, False);
120122 for i := 0 to FtpList.Count -1 do
121123 begin
122124 s := FTPList[i].FileName;
123125 DirList.Add(s);
124126 end;
125127 Logout;
126128 finally
127129 Free;
128130 end;
129131end;
130132```
131133
132134### From an HTTP server
133135
134136Given 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.
135137
136138#### Simple version
137139
138140``` pascal
139141...
140142uses httpsend,
141143...
142144function DownloadHTTP(URL, TargetFile: string): Boolean;
143145var
144146 HTTPGetResult: Boolean;
145147 HTTPSender: THTTPSend;
146148begin
147149 Result := False;
148150 HTTPSender := THTTPSend.Create;
149151 try
150152 HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
151153 if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then begin
152154 HTTPSender.Document.SaveToFile(TargetFile);
153155 Result := True;
154156 end;
155157 finally
156158 HTTPSender.Free;
157159 end;
158160end;
159161```
160162
161163#### Advanced version
162164
163165``` pascal
164166...
165167uses httpsend
166168...
167169function DownloadHTTP(URL, TargetFile: string): Boolean;
168170// Download file; retry if necessary.
169171// Could use Synapse HttpGetBinary, but that doesn't deal
170172// with result codes (i.e. it happily downloads a 404 error document)
171173const
172174 MaxRetries = 3;
173175var
174176 HTTPGetResult: Boolean;
175177 HTTPSender: THTTPSend;
176178 RetryAttempt: Integer;
177179begin
178180 Result := False;
179181 RetryAttempt := 1;
180182 HTTPSender := THTTPSend.Create;
181183 try
182184 try
183185 // Try to get the file
184186 HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
185187 while (HTTPGetResult = False) and (RetryAttempt < MaxRetries) do
186188 begin
187189 Sleep(500 * RetryAttempt);
188190 HTTPSender.Clear;
189191 HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
190192 RetryAttempt := RetryAttempt + 1;
191193 end;
192194 // If we have an answer from the server, check if the file
193195 // was sent to us.
194196 case HTTPSender.Resultcode of
195197 100..299:
196198 begin
197199 HTTPSender.Document.SaveToFile(TargetFile);
198200 Result := True;
199201 end; //informational, success
200202 300..399: Result := False; // redirection. Not implemented, but could be.
201203 400..499: Result := False; // client error; 404 not found etc
202204 500..599: Result := False; // internal server error
203205 else Result := False; // unknown code
204206 end;
205207 except
206208 // We don't care for the reason for this error; the download failed.
207209 Result := False;
208210 end;
209211 finally
210212 HTTPSender.Free;
211213 end;
212214end;
213215```
214216
215217#### Simple version with progress
216218
217219The 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.
218220
219221``` pascal
220222unit uhttpdownloader;
221223
222224// Essential to change this. The default is {$mode objfpc}{$H+} and does not work.
223225{$mode Delphi}
224226
225227interface
226228
227229uses
228230 Classes, SysUtils, httpsend, blcksock, typinfo;
229231
230232type
231233 IProgress = interface
232234 procedure ProgressNotification(Text: String; CurrentProgress : integer; MaxProgress : integer);
233235 end;
234236
235237type
236238 { THttpDownloader }
237239
238240 THttpDownloader = class
239241 public
240242 function DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
241243 private
242244 Bytes : Integer;
243245 MaxBytes : Integer;
244246 HTTPSender: THTTPSend;
245247 ProgressMonitor : IProgress;
246248 procedure Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
247249 function GetSizeFromHeader(Header: String):integer;
248250 end;
249251
250252implementation
251253
252254function THttpDownloader.DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
253255var
254256 HTTPGetResult: Boolean;
255257begin
256258 Result := False;
257259 Bytes:= 0;
258260 MaxBytes:= -1;
259261 Self.ProgressMonitor:= ProgressMonitor;
260262
261263 HTTPSender := THTTPSend.Create;
262264 try
263265 HTTPSender.Sock.OnStatus:= Status;
264266 HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
265267 if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then begin
266268 HTTPSender.Document.SaveToFile(TargetFile);
267269 Result := True;
268270 end;
269271 finally
270272 HTTPSender.Free;
271273 end;
272274end;
273275
274276procedure THttpDownloader.Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
275277var
276278 V, currentHeader: String;
277279 i: integer;
278280begin
279281 //try to get filesize from headers
280282 if (MaxBytes = -1) then
281283 begin
282284 for i:= 0 to HTTPSender.Headers.Count - 1 do
283285 begin
284286 currentHeader:= HTTPSender.Headers[i];
285287 MaxBytes:= GetSizeFromHeader(currentHeader);
286288 if MaxBytes <> -1 then break;
287289 end;
288290 end;
289291
290292 V := GetEnumName(TypeInfo(THookSocketReason), Integer(Reason)) + ' ' + Value;
291293
292294 if Reason = THookSocketReason.HR_ReadCount then
293295 begin
294296 Bytes:= Bytes + StrToInt(Value);
295297 ProgressMonitor.ProgressNotification(V, Bytes, MaxBytes);
296298 end;
297299end;
298300
299301function THttpDownloader.GetSizeFromHeader(Header: String): integer;
300302var
301303 item : TStringList;
302304begin
303305 Result:= -1;
304306
305307 if Pos('Content-Length:', Header) <> 0 then
306308 begin
307309 item:= TStringList.Create();
308310 item.Delimiter:= ':';
309311 item.StrictDelimiter:=true;
310312 item.DelimitedText:=Header;
311313 if item.Count = 2 then
312314 begin
313315 Result:= StrToInt(Trim(item[1]));
314316 end;
315317 end;
316318end;
317319
318320end.
319321```
320322
321323What are we doing here?
322324
323325First 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.
324326
325327Once 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.
326328
327329"HR_ReadCount" provides us with the information how many bytes where read since the last event.
328330
329331The progress is then reported to the UI:
330332
331333``` pascal
332334procedure TMainForm.ProgressNotification(Text: String; CurrentProgress: integer; MaxProgress: integer);
333335begin
334336 if (MaxProgress <> -1) then
335337 begin
336338 ProgressBar.Max:= MaxProgress;
337339 end;
338340
339341 ProgressBar.Position:= CurrentProgress;
340342 memoStatus.Lines.Add(Text);
341343 Application.ProcessMessages;
342344end;
343345```
344346
345347So, the final main unit will be:
346348
347349``` pascal
348350unit uMain;
349351
350352{$mode Delphi}
351353
352354interface
353355
354356uses
355357 Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ComCtrls, httpsend, blcksock, typinfo,
356358 uhttpdownloader;
357359
358360type
359361
360362 { TMainForm }
361363
362364 TMainForm = class(TForm, IProgress)
363365 btnStartDownload: TButton;
364366 edtUrl: TEdit;
365367 labelUrl: TLabel;
366368 memoStatus: TMemo;
367369 ProgressBar: TProgressBar;
368370 SaveDialog: TSaveDialog;
369371 procedure btnStartDownloadClick(Sender: TObject);
370372 private
371373 { private declarations }
372374 function GetFileNameFromURL(url: String):string;
373375 public
374376 { public declarations }
375377 procedure ProgressNotification(Text: String; CurrentProgress : integer; MaxProgress : integer);
376378 end;
377379
378380var
379381 MainForm: TMainForm;
380382
381383implementation
382384
383385{$R *.lfm}
384386
385387{ TMainForm }
386388
387389procedure TMainForm.btnStartDownloadClick(Sender: TObject);
388390var
389391 fileName: String;
390392 downloader: THttpDownloader;
391393 success: boolean;
392394begin
393395 fileName:= GetFileNameFromURL(edtUrl.Text);
394396 SaveDialog.FileName:=fileName;
395397 if (SaveDialog.Execute) then
396398 begin
397399 memoStatus.Lines.Clear;
398400 ProgressBar.Position:=0;
399401 downloader:= THttpDownloader.Create();
400402 success:= downloader.DownloadHTTP(edtUrl.Text, SaveDialog.FileName, Self);
401403
402404 ProgressBar.Position:=0;
403405 if Success then
404406 memoStatus.Lines.Add('Download successful')
405407 else
406408 memoStatus.Lines.Add('Error during download');
407409
408410 end;
409411end;
410412
411413function TMainForm.GetFileNameFromURL(url: String): string;
412414var i, l : integer;
413415 fileName, current : String;
414416begin
415417 fileName:= '';
416418 l:= Length(url);
417419 for i:= l downto 0 do begin
418420 current:= url[i];
419421 if current <> '/' then
420422 begin
421423 fileName:= current + fileName;
422424 end else begin
423425 Result:= fileName;
424426 break;
425427 end;
426428 end;
427429end;
428430
429431procedure TMainForm.ProgressNotification(Text: String; CurrentProgress: integer; MaxProgress: integer);
430432begin
431433 if (MaxProgress <> -1) then ProgressBar.Max:= MaxProgress;
432434 ProgressBar.Position:= CurrentProgress;
433435 memoStatus.Lines.Add(Text);
434436 Application.ProcessMessages;
435437end;
436438
437439end.
438440```
439441
440442Reference: <https://andydunkel.net/2015/09/09/lazarus_synapse_progress/>
441443
442444#### From an HTTP server by parsing URLs: Sourceforge
443445
444446Please see [[Download from SourceForge]] for an example of downloading from sourceforge.net.
445447
446448### From an HTTPS server
447449
448450This 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://>**.
449451
450452## SSH/Telnet client sample program
451453
452454Below 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.
453455
454456### Requirements
455457
456458Apart 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.
457459
458460There are 2 choices:
459461
460462- Cryptlib library. Advantage: stable. Apparently able to use private keys but these are in some format that is not widely supported.
461463- LibSSH2 library. Pascal bindings still in development, but you can use a file with your private key (in OpenSSH format) to authenticate.
462464
463465#### Cryptlib
464466
465467- 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.
466468- 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.
467469
468470You will also need the bindings (cryptlib.pas), present in the source distribution of cryptlib.
469471
470472The versions of the cryptlib binary and the bindings must match.
471473
472474> **Note:** It seems that cryptlib is not suitable to connect to linux machines, though AIX works. Use SSH2 instead.
473475
474476#### LibSSH2
475477
476478- 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.
477479- On Linux and macOS, install libssh2 via your package manager/other means. When distributing your application:
478480 - Linux: mark libssh2 as a requirement in your .deb/.rpm/whatever package.
479481 - macOS: include libssh2 in your [[Application Bundle]]'s Resources directory.
480482
481483You 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.
482484
483485### Synapse libssh2 SSL plugin
484486
485487> **Note:** plugin is not completed.
486488
487489``` pascal
488490{
489491 ssl_libssh2.pas version 0.2
490492
491493 SSH2 support (draft) plugin for Synapse Library (http://www.ararat.cz/synapse) by LibSSH2 (http://libssh2.org)
492494 Requires: libssh2 pascal interface - http://www.lazarus.freepascal.org/index.php/topic,15935.msg86465.html#msg86465 and
493495 libssh2.dll with OpenSSL.
494496
495497 (С) Alexey Suhinin http://x-alexey.narod.ru
496498}
497499
498500{$IFDEF FPC}
499501 {$MODE DELPHI}
500502{$ENDIF}
501503{$H+}
502504
503505unit ssl_libssh2;
504506
505507interface
506508
507509uses
508510 SysUtils,
509511 blcksock, synsock,
510512 libssh2;
511513
512514type
513515 {:@abstract(class implementing CryptLib SSL/SSH plugin.)
514516 Instance of this class will be created for each @link(TTCPBlockSocket).
515517 You not need to create instance of this class, all is done by Synapse itself!}
516518 TSSLLibSSH2 = class(TCustomSSL)
517519 protected
518520 FSession: PLIBSSH2_SESSION;
519521 FChannel: PLIBSSH2_CHANNEL;
520522 function SSHCheck(Value: integer): Boolean;
521523 function DeInit: Boolean;
522524 public
523525 {:See @inherited}
524526 constructor Create(const Value: TTCPBlockSocket); override;
525527 destructor Destroy; override;
526528 function Connect: boolean; override;
527529 function LibName: String; override;
528530 function Shutdown: boolean; override;
529531 {:See @inherited}
530532 function BiShutdown: boolean; override;
531533 {:See @inherited}
532534 function SendBuffer(Buffer: TMemory; Len: Integer): Integer; override;
533535 {:See @inherited}
534536 function RecvBuffer(Buffer: TMemory; Len: Integer): Integer; override;
535537 {:See @inherited}
536538 function WaitingData: Integer; override;
537539 {:See @inherited}
538540 function GetSSLVersion: string; override;
539541 published
540542 end;
541543
542544implementation
543545
544546{==============================================================================}
545547function TSSLLibSSH2.SSHCheck(Value: integer): Boolean;
546548var
547549 PLastError: PAnsiChar;
548550 ErrMsgLen: Integer;
549551begin
550552 Result := true;
551553 FLastError := 0;
552554 FLastErrorDesc := '';
553555 if Value<0 then
554556 begin
555557 FLastError := libssh2_session_last_error(FSession, PLastError, ErrMsglen, 0);
556558 FLastErrorDesc := PLastError;
557559 Result := false;
558560 end;
559561end;
560562
561563function TSSLLibSSH2.DeInit: Boolean;
562564begin
563565 if Assigned(FChannel) then
564566 begin
565567 libssh2_channel_free(FChannel);
566568 FChannel := nil;
567569 end;
568570 if Assigned(FSession) then
569571 begin
570572 libssh2_session_disconnect(FSession,'Goodbye');
571573 libssh2_session_free(FSession);
572574 FSession := nil;
573575 end;
574576 FSSLEnabled := False;
575577 Result := true;
576578end;
577579
578580constructor TSSLLibSSH2.Create(const Value: TTCPBlockSocket);
579581begin
580582 inherited Create(Value);
581583 FSession := nil;
582584 FChannel := nil;
583585end;
584586
585587destructor TSSLLibSSH2.Destroy;
586588begin
587589 DeInit;
588590 inherited Destroy;
589591end;
590592
591593function TSSLLibSSH2.Connect: boolean;
592594begin
593595 Result := False;
594596 if SSLEnabled then DeInit;
595597 if (FSocket.Socket <> INVALID_SOCKET) and (FSocket.SSL.SSLType = LT_SSHv2) then
596598 begin
597599 FSession := libssh2_session_init();
598600 if not Assigned(FSession) then
599601 begin
600602 FLastError := -999;
601603 FLastErrorDesc := 'Cannot initialize SSH session';
602604 exit;
603605 end;
604606 if not SSHCheck(libssh2_session_startup(FSession, FSocket.Socket)) then
605607 exit;
606608 if (FSocket.SSL.PrivateKeyFile<>'') then
607609 begin
608610 if (not SSHCheck(libssh2_userauth_publickey_fromfile(FSession, PChar(FSocket.SSL.Username), nil, PChar(FSocket.SSL.PrivateKeyFile), PChar(FSocket.SSL.KeyPassword)))) then
609611 exit;
610612 end
611613 else
612614 if (FSocket.SSL.Username<>'') and (FSocket.SSL.Password<>'') then
613615 begin
614616 if (not SSHCheck(libssh2_userauth_password(FSession, PChar(FSocket.SSL.Username), PChar(FSocket.SSL.Password)))) then
615617 exit;
616618 end;
617619 FChannel := libssh2_channel_open_session(FSession);
618620 if not assigned(FChannel) then
619621 begin
620622 SSHCheck(-1); // get error
621623 if FLastError = 0 then
622624 begin
623625 FLastError := -999; // unknown error
624626 FLastErrorDesc := 'Cannot open session';
625627 end;
626628 exit;
627629 end;
628630 if not SSHCheck(libssh2_channel_request_pty(FChannel, 'vanilla')) then
629631 exit;
630632 if not SSHCheck(libssh2_channel_shell(FChannel)) then
631633 exit;
632634 FSSLEnabled := True;
633635 Result := True;
634636 end;
635637end;
636638
637639function TSSLLibSSH2.LibName: String;
638640begin
639641 Result := 'ssl_libssh2';
640642end;
641643
642644function TSSLLibSSH2.Shutdown: boolean;
643645begin
644646 Result := DeInit;
645647end;
646648
647649function TSSLLibSSH2.BiShutdown: boolean;
648650begin
649651 Result := DeInit;
650652end;
651653
652654function TSSLLibSSH2.SendBuffer(Buffer: TMemory; Len: Integer): Integer;
653655begin
654656 Result:=libssh2_channel_write(FChannel, PChar(Buffer), Len);
655657 SSHCheck(Result);
656658end;
657659
658660function TSSLLibSSH2.RecvBuffer(Buffer: TMemory; Len: Integer): Integer;
659661begin
660662 result:=libssh2_channel_read(FChannel, PChar(Buffer), Len);
661663 SSHCheck(Result);
662664end;
663665
664666function TSSLLibSSH2.WaitingData: Integer;
665667begin
666668 if libssh2_poll_channel_read(FChannel, Result) <> 1 then Result := 0;
667669end;
668670
669671function TSSLLibSSH2.GetSSLVersion: string;
670672begin
671673 Result:=libssh2_version(0);
672674end;
673675
674676initialization
675677 if libssh2_init(0)=0 then
676678 SSLImplementation := TSSLLibSSH2;
677679
678680finalization
679681 libssh2_exit;
680682
681683end.
682684```
683685
684686### Terminal client class
685687
686688The telnetsshclient.pas unit below wraps around the Synapse tlntsend.pas unit and abstracts logging in, sending commands and receiving output and logging out.
687689
688690If 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.
689691
690692This unit has been lightly tested on a Linux ssh/telnet server. Additional tests welcome.
691693
692694``` pascal
693695unit telnetsshclient;
694696
695697{ Wrapper around Synapse libraries and SSL library (libssh2+libssl
696698is used right now)
697699Download compiled Windows dll from e.g.
698700http://alxdm.dyndns-at-work.com:808/files/windll_libssh2.zip
699701Download FreePascal interface files:
700702http://www.lazarus.freepascal.org/index.php/topic,15935.msg86465.html#msg86465
701703
702704This unit allows the user to send Telnet or SSH commands and get the output
703705Thanks to Leonardo Rame
704706http://leonardorame.blogspot.com/2010/01/synapse-based-ssh-client.html
705707and Ludo Brands.
706708
707709Written by Reinier Olislagers 2011.
708710Modified for libssh2 by Alexey Suhinin 2012.
709711
710712License of code:
711713* MIT
712714* LGPLv2 or later (with FreePascal static linking exception)
713715* GPLv2 or later
714716according to your choice.
715717Free use allowed but please don't sue or blame me.
716718
717719Uses other libraries/components; different licenses may apply that also can influence the combined/compiled work.
718720}
719721
720722{$mode objfpc}{$H+}
721723{$DEFINE HAS_SSH_SUPPORT} //comment out if only telnet support required
722724{$DEFINE LIBSSH2}
723725
724726interface
725727
726728uses
727729 Classes, SysUtils,
728730 tlntsend
729731 {$IFDEF HAS_SSH_SUPPORT}
730732 {ssl - or actually ssh - libs required by tlntsend}
731733 {$IFDEF LIBSSH2}
732734 ssl_libssh2
733735 {$ELSE}
734736 ssl_cryptlib
735737 {$ENDIF}
736738 {$ENDIF HAS_SSH_SUPPORT} ;
737739
738740type
739741 TProtocolType = (Telnet, SSH); //Different means of connecting
740742 TServerType = (Unix, Windows); //line endings, mostly
741743 { TelnetSSHClient }
742744
743745 { TTelnetSSHClient }
744746
745747 TTelnetSSHClient = class(TTelnetSend)
746748 protected
747749 FConnected: boolean;
748750 FOutputPosition: integer; //Keeps track of position in output stream
749751 FProtocolType: TProtocolType;
750752 FServerLineEnding: string; //depends on FServerType
751753 FServerType: TServerType;
752754 FWelcomeMessage, FTelnetLoginPrompt, FTelnetPasswordPrompt: string;
753755 procedure SetPrivateKeyFile(Value: string);
754756 function GetPrivateKeyFile: string;
755757 { Based on protocol and servertype, set expected serverside line ending}
756758 procedure DetermineLineEnding;
757759 { Sets port if no explicit port set. Uses protocol type: SSH or telnet}
758760 procedure DeterminePort;
759761 function GetSessionLog: string;
760762 procedure ProtocolTypeChange(Value: TProtocolType);
761763 function ReceiveData: string; //Can be used to get welcome message etc.
762764 procedure SendData(Data: string);
763765 procedure ServerTypeChange(Value: TServerType);
764766 public
765767 {All output generated during the entire session up to now}
766768 property AllOutput: string read GetSessionLog;
767769 {True if connected to server}
768770 property Connected: boolean read FConnected;
769771 {Name or IP address of host to connect to}
770772 property HostName: string read FTargetHost write FTargetHost;
771773 {Port on host used for connection. If left as 0, it will be determined by protocol type (22 for SSH, 23 for Telnet}
772774 property Port: String read FTargetPort write FTargetPort;
773775 {Location of private key file.}
774776 property PrivateKeyFile: string read GetPrivateKeyFile write SetPrivateKeyFile;
775777 {Telnet login prompt}
776778 property TelnetLoginPrompt: string read FTelnetLoginPrompt write FTelnetLoginPrompt;
777779 {Telnet password prompt}
778780 property TelnetPasswordPrompt: string read FTelnetPasswordPrompt write FTelnetPasswordPrompt;
779781 {Username used when connecting}
780782 property UserName: string read FUserName write FUserName;
781783 {Password used when connecting. Used as passphrase if PrivateKey is used}
782784 property Password: string read FPassword write FPassword;
783785 {Should we talk Telnet or SSH to the server? Defaults to SSH.}
784786 property ProtocolType: TProtocolType read FProtocolType write ProtocolTypeChange;
785787 {Windows or Unix/Linux server? Has effect on line endings. Defaults to Unix. NOTE: untested}
786788 property Servertype: TServerType read FServerType write ServerTypeChange;
787789 {Initial message displayed on logon}
788790 property WelcomeMessage: string read FWelcomeMessage;
789791 {Connect/logon to server. Requires that all authentication, protocol and hostname/port options are correct
790792 Returns descriptive result. You can then use the Connected property.}
791793 function Connect: string;
792794 {If connected, logoff from server}
793795 procedure Disconnect;
794796 {Send command to server and receive result}
795797 function CommandResult(Command: string): string; //Send command and get results
796798 constructor Create;
797799 destructor Destroy; override;
798800 end;
799801
800802implementation
801803
802804
803805{ TelnetSSHClient }
804806procedure TTelnetSSHClient.SetPrivateKeyFile(value: string);
805807begin
806808 Sock.SSL.PrivateKeyFile := value;
807809end;
808810
809811function TTelnetSSHClient.GetPrivateKeyFile: string;
810812begin
811813 Result := Sock.SSL.PrivateKeyFile;
812814end;
813815
814816procedure TTelnetSSHClient.DetermineLineEnding;
815817begin
816818 case FProtocolType of
817819 SSH:
818820 begin
819821 if FServerType = Unix then
820822 FServerLineEnding := #10 //Unix
821823 else
822824 FServerLineEnding := #13 + #10; //windows
823825 end;
824826 Telnet:
825827 begin
826828 if FServerType = Unix then
827829 FServerLineEnding := #10 //Unix
828830 else
829831 FServerLineEnding := #13 + #10; //windows
830832 end;
831833 else
832834 raise Exception.Create('Unknown protocol type');
833835 end;
834836end;
835837
836838procedure Ttelnetsshclient.DeterminePort;
837839begin
838840 if FTargetPort = '' then
839841 //Set default port for protocol
840842 begin
841843 case FProtocolType of
842844 Telnet: FTargetPort := '23';
843845 SSH: FTargetPort := '22';
844846 else
845847 raise Exception.Create('Unknown protocol type.');
846848 end;
847849
848850 end;
849851end;
850852
851853procedure TTelnetSSHClient.ServerTypeChange(Value: Tservertype);
852854begin
853855 FServerType := Value;
854856 DetermineLineEnding;
855857end;
856858
857859function TTelnetSSHClient.Connect: string;
858860var
859861 Received: string;
860862begin
861863 result:='Unknown error while connecting';
862864 FOutputPosition := 1; //First character in output stream
863865 FWelcomeMessage := '';
864866 //Just to make sure:
865867 DetermineLineEnding;
866868 DeterminePort;
867869 if FTargetPort='0' then
868870 begin
869871 result:='Port may not be 0.';
870872 exit; //jump out of function
871873 end;
872874 case FProtocolType of
873875 Telnet:
874876 begin
875877 try
876878 if Login then
877879 begin
878880 FConnected := True;
879881 result:='Connected to telnet server.';
880882 end
881883 else
882884 if Sock.LastError<>0 then raise Exception.Create(Sock.LastErrorDesc);
883885 except
884886 on E: Exception do
885887 begin
886888 FConnected:=false;
887889 result:='Error connecting to telnet server '+FTargetHost+':'+
888890 FTargetPort+' as user ' + FUserName +
889891 '. Technical details: '+E.Message;
890892 end;
891893 end;
892894 end;
893895 SSH:
894896 begin
895897 {$IFNDEF HAS_SSH_SUPPORT}
896898 raise Exception.Create(
897899 'SSH support has not been compiled into the telnetsshclient library.');
898900 {$ENDIF HAS_SSH_SUPPORT}
899901 try
900902 if (PrivateKeyFile <> '') and (FPassword <> '') then
901903 Sock.SSL.KeyPassword:=FPassword;
902904 if SSHLogin then
903905 begin
904906 FConnected := True;
905907 result:='Connected to SSH server.';
906908 end
907909 else
908910 begin
909911 if Sock.LastError<>0 then raise Exception.Create(Sock.LastErrorDesc);
910912 if Sock.SSL.LastError<0 then raise Exception.Create(Sock.SSL.LastErrorDesc);
911913 end;
912914 except
913915 on E: Exception do
914916 begin
915917 FConnected:=false;
916918 result:='Error connecting to SSH server '+FTargetHost+':'+
917919 FTargetPort+' as user ' + FUserName +
918920 '. Technical details: '+E.Message;
919921 end;
920922 end;
921923 end;
922924 else
923925 raise Exception.Create('Unknown protocol type');
924926 end;
925927 if FConnected = True then
926928 begin
927929 FWelcomeMessage := ReceiveData;
928930 if FProtocolType=Telnet then
929931 begin
930932 //Unfortunately, we'll have to extract login ourselves
931933 //Hope it applies to all server types.
932934 if (AnsiPos(AnsiLowerCase(FTelnetLoginPrompt),AnsiLowerCase(FWelcomeMessage))>0) then
933935 begin
934936 SendData(UserName);
935937 end;
936938 Received:=ReceiveData;
937939 if (AnsiPos(AnsiLowerCase(FTelnetPasswordPrompt),AnsiLowerCase(Received))>0) then
938940 begin
939941 SendData(Password);
940942 end;
941943 //Receive additional welcome message/message of the day
942944 FWelcomeMessage:=FWelcomeMessage+LineEnding+ReceiveData;
943945 end;
944946 end;
945947end;
946948
947949procedure TTelnetSSHClient.Disconnect;
948950begin
949951 Logout;
950952 FConnected := False;
951953end;
952954
953955function TTelnetSSHClient.ReceiveData: string;
954956begin
955957 Result := '';
956958 while Sock.CanRead(1000) or (Sock.WaitingData > 0) do
957959 begin
958960 Sock.RecvPacket(1000);
959961 Result := Result + Copy(SessionLog, FOutputPosition,
960962 Length(SessionLog));
961963 FOutputPosition := Length(SessionLog) + 1;
962964 end;
963965end;
964966
965967procedure Ttelnetsshclient.SendData(Data: String);
966968begin
967969 Data := Data + FServerLineEnding; //Could be linux, could be Windows
968970 Send(Data);
969971end;
970972
971973function TTelnetSSHClient.GetSessionLog: string;
972974begin
973975 // Gets complete output up to now
974976 Result := SessionLog;
975977end;
976978
977979procedure TTelnetSSHClient.ProtocolTypeChange(Value: Tprotocoltype);
978980begin
979981 FProtocolType := Value;
980982 //Auto-determine port and line ending, if necessary
981983 DeterminePort;
982984 DetermineLineEnding;
983985end;
984986
985987function TTelnetSSHClient.CommandResult(Command: string): string;
986988begin
987989 Result := '';
988990 if FConnected then
989991 begin
990992 SendData(Command);
991993 Result := ReceiveData; //gets too much
992994 end
993995 else
994996 begin
995997 //raise exception
996998 Result := '';
997999 raise Exception.Create('Can only run command when connected');
9981000 end;
9991001end;
10001002
10011003constructor TTelnetSSHClient.Create;
10021004begin
10031005 inherited;
10041006 FConnected := False;
10051007 FProtocolType := SSH; //Could be telnet, too
10061008 FServerType := Unix; //Probably a safe default.
10071009 FTelnetLoginPrompt := 'login:';
10081010 FTelnetPasswordPrompt := 'password:';
10091011 DetermineLineEnding;
10101012 DeterminePort;
10111013end;
10121014
10131015destructor TTelnetSSHClient.Destroy;
10141016begin
10151017 if FConnected then
10161018 Disconnect;
10171019 inherited Destroy;
10181020end;
10191021
10201022end.
10211023```
10221024
10231025### Example client code
10241026
10251027To 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:
10261028
10271029``` pascal
10281030program sshtest;
10291031
10301032{Test program for telnetsshclient
10311033
10321034Written by Reinier Olislagers 2011.
10331035Modified for libssh2 by Alexey Suhinin 2012.
10341036
10351037License of code:
10361038* MIT
10371039* LGPLv2 or later (with FreePascal static linking exception)
10381040* GPLv2 or later
10391041according to your choice.
10401042Free use allowed but please don't sue or blame me.
10411043
10421044Uses other libraries/components; different licenses may apply that also can influence the combined/compiled work.
10431045
10441046Run: sshtest <serverIPorhostname> [PrivateKeyFile]
10451047}
10461048{$mode objfpc}{$H+}
10471049{$APPTYPE CONSOLE}
10481050
10491051uses
10501052 telnetsshclient;
10511053var
10521054 comm: TTelnetSSHClient;
10531055 Command: string;
10541056begin
10551057 writeln('Starting.');
10561058 comm:=TTelnetSSHClient.Create;
10571059 comm.HostName:= ParamStr(1); //First argument on command line
10581060 if comm.HostName='' then
10591061 begin
10601062 writeln('Please specify hostname on command line.');
10611063 halt(1);
10621064 end;
10631065
10641066 comm.PrivateKeyFile := ParamStr(2);
10651067
10661068 comm.TargetPort:='0'; //auto determine based on protocoltype
10671069 comm.UserName:='root'; //change to your situation
10681070 comm.Password:='password'; //change to your situation
10691071 comm.ProtocolType:=SSH; //Telnet or SSH
10701072 writeln(comm.Connect); //Show result of connection
10711073 if comm.Connected then
10721074 begin
10731075 writeln('Server: ' + comm.HostName + ':'+comm.TargetPort+', user: '+comm.UserName);
10741076 writeln('Welcome message:');
10751077 writeln(comm.WelcomeMessage);
10761078 Command:='ls -al';
10771079 writeln('*** Sending ' + Command);
10781080 writeln('*** Begin result****');
10791081 writeln(comm.CommandResult(Command));
10801082 writeln('*** End result****');
10811083 writeln('');
10821084 writeln('');
10831085 Command:='df -h';
10841086 writeln('*** Sending ' + Command);
10851087 writeln('*** Begin result****');
10861088 writeln(comm.CommandResult(Command));
10871089 writeln('*** End result****');
10881090 writeln('');
10891091 writeln('');
10901092 writeln('All output:');
10911093 writeln('*** Begin result****');
10921094 writeln(comm.AllOutput);
10931095 writeln('*** End result****');
10941096 comm.Disconnect;
10951097 end
10961098 else
10971099 begin
10981100 writeln('Connection to ' +
10991101 comm.HostName + ':' +
11001102 comm.TargetPort + ' failed.');
11011103 end;
11021104 comm.Free;
11031105end.
11041106```
11051107
11061108## OAuth v1/Twitter/Plurk integration
11071109
11081110An 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.
11091111
11101112## Other Web and Networking Articles
11111113
11121114- [[Portal:Web Development|Web Development Portal]]
11131115- [[Networking]]
11141116- [[Networking libraries]] - comparison of various networking libraries
11151117- [[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.
11161118- [[Sockets]] - TCP/IP Sockets components
11171119- [[fcl-net]] - Networking library supplied with FPC
11181120- [[lNet]] - Lightweight Networking Components
11191121- [[XML Tutorial]] - XML is often utilized on network communications
11201122- [[FPC and Apache Modules]]
11211123- [[fcl-web]] - Also known as fpWeb, this is a library to develop web applications which can be deployed as cgi, fastcgi or apache modules.
11221124- [[Secure programming|Secure Programming]]
11231125- [[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
11241126
11251127## See also
11261128
11271129- [[Download from SourceForge]] Example that uses Synapse to download from an HTTP server that redirects.
11281130- [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).
11291131- [TCP/IP component based on Synapse + a small demo application](https://forum.lazarus.freepascal.org/index.php/topic,48677.0.html)
11301132
11311133## External links
11321134
11331135- [Official site](http://www.ararat.cz/synapse/)
11341136- [Official documentation](http://synapse.ararat.cz/doc/help/)
11351137- [Download and Wiki](https://github.com/geby/synapse/wiki)
11361138
11371139---
11381140*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