Synapse/pl
│
English (en) │
polski (pl) │
русский (ru) │
Synapse ułatwia użycie portu szeregowego i dostarcza synchroniczną bibliotekę TCP/IP.
Przegląd
Synapse oferuje obsługę portu szeregowego oraz łączność TCP/IP. Różni się od innych bibliotek tym, że wymaga jedynie dodania do kodu kilku plików kodu źródłowego Synapse Pascal; nie ma potrzeby instalowania całych pakietów itp. Jedynym wyjątkiem jest to, że będziesz potrzebować zewnętrznej biblioteki kryptograficznej, jeśli chcesz używać szyfrowania, takiego jak SSL/TLS/SSH.
Zobacz dokumentację na oficjalnej stronie (link poniżej), aby uzyskać więcej informacji.
Instalacja
Instalacja może być tak prosta, jak po prostu skopiowanie wszystkich plików do katalogu aplikacji i dodanie odpowiednich modułów Synapse do klauzuli uses.
Bardziej eleganckim i zalecanym sposobem jest kompilacja pakietu laz_synapse.lpk, dzięki czemu możesz używać tych samych modułów we wszystkich swoich projektach.
Strona informacyjna pobierania/SVN Synapse: Strona pobierania Synapse
Wsparcie i zgłaszanie błędów
Projekt Synapse posiada listę mailingową, na której udzielane jest wsparcie i można przesyłać łatki.
Raporty o błędach można również przesyłać pocztą na listę dyskusyjną.
Zobacz strona pomocy Synapse
Obsługa SSL/TLS
Z Synapse możesz korzystać z obsługi OpenSSL, CryptLib, StreamSecII lub OpenStreamSecII SSL. Domyślnie nie jest używana obsługa SSL.
Wsparcie jest aktywowane poprzez umieszczenie wybranej nazwy modułu w sekcji uses w swoim projekcie. Musisz także umieścić plik biblioteki binarnej w ścieżce projektu (Windows) lub zainstalować go w ścieżce wyszukiwania biblioteki (Linux, macOS, FreeBSD).
Synapse ładuje pliki bibliotek SSL w środowisku wykonawczym jako biblioteki dynamiczne.
- Szczegółowe informacje można znaleźć na stronie Architektura wtyczek SSL/TLS
- Niektóre biblioteki kryptograficzne można uzyskać pod adresem: http://synapse.ararat.cz/files/crypt/
Brakująca biblioteka
W systemie Linux musisz upewnić się, że wymagana biblioteka dynamiczna jest obecna/zainstalowana w twoim systemie. Jeśli biblioteki nie ma w systemie, to w przypadku cryptlib, podczas linkowania pojawia się komunikat o błędzie:
/usr/bin/ld: cannot find -lcl
Podobny komunikat zostanie wyświetlony podczas korzystania z innych bibliotek dynamicznych.
Przykład serwera WWW
Zobacz ten przykład serwera internetowego (www).
Przykład zapytania serwera QOTD
Zobacz ten przykład zapytania serwera - Cytat dnia.
Wysyłanie email
Artykuł opisujący wysyłanie e-maili wraz z załącznikami za pomocą Synapse: http://www.freepascal.org/~michael/articles/lazmail/lazmail-en.pdf
Przykład z postu na forum; współpracuje np. z Gmailem (czy to jest aktualne?):
{Ten kod obsługuje szyfrowanie TLS/SSL; jeśli wysyłasz do portu 25, SMTP używa zwykłego tekstu.}
uses
..., smtpsend,ssl_openssl; //prawdopodobnie można użyć również inne moduły SSL.
// MailData to tekst wiadomości.
function SendMail(
User, Password,
MailFrom, MailTo,
SMTPHost, SMTPPort: string;
MailData: string): Boolean;
var
SMTP: TSMTPSend;
sl:TStringList;
begin
Result:=False;
SMTP:=TSMTPSend.Create;
sl:=TStringList.Create;
try
sl.text:=Maildata;
SMTP.UserName:=User;
SMTP.Password:=Password;
SMTP.TargetHost:=SMTPHost;
SMTP.TargetPort:=SMTPPort;
SMTP.AutoTLS:=true;
if Trim(SMTPPort)<>'25' then
SMTP.FullSSL:=true; // jeśli wysyłasz na port 25, nie używaj szyfrowania
if SMTP.Login then
begin
result:=SMTP.MailFrom(MailFrom, Length(MailData)) and
SMTP.MailTo(MailTo) and
SMTP.MailData(sl);
SMTP.Logout;
end;
finally
SMTP.Free;
sl.Free;
end;
end;
Wysyłanie załączników
Proszę zapoznać się z tą dokumentacją Synapse.
Pobieranie plików
Z serwera FTP
Po podaniu adresu URL (i ścieżki) oraz nazwy pliku, zostanie on pobrany z serwera FTP. Jest to głównie opakowanie wokół kodu Synapse, które ma ułatwić pobieranie dowolnych plików. Jeśli dokładnie wiesz, co i gdzie chcesz pobrać, wystarczy wywołać Synapse:
FtpGetFile
zaprowadzi cię bardzo daleko.
function DownloadFTP(URL, TargetFile: string): boolean;
const
FTPPort=21;
FTPScheme='ftp://'; //Nazwa schematu URI dla adresów URL FTP
var
Host: string;
Port: integer;
Source: string;
FoundPos: integer;
begin
// Usuń informacje o schemacie:
if LeftStr(URL, length(FTPScheme))=FTPScheme then URL:=Copy(URL, length(FTPScheme)+1, length(URL));
// Surowa analiza; można użyć kodu parsującego URI z pakietów FPC...
FoundPos:=pos('/', URL);
Host:=LeftStr(URL, FoundPos-1);
Source:=Copy(URL, FoundPos+1, Length(URL));
//Sprawdź numery portów:
FoundPos:=pos(':', Host);
Port:=FTPPort;
if FoundPos>0 then
begin
Host:=LeftStr(Host, FoundPos-1);
Port:=StrToIntDef(Copy(Host, FoundPos+1, Length(Host)),21);
end;
Result:=FtpGetFile(Host, IntToStr(Port), Source, TargetFile, 'anonymous', 'fpc@example.com');
if result=false then writeln('DownloadFTP: error downloading '+URL+'. Details: host: '+Host+'; port: '+Inttostr(Port)+'; remote path: '+Source+' to '+TargetFile);
end;
Przykład pobrania listy plików dostępnych w podanej ścieżce
//Użyj modułu ftpsend
function FtpGetDir(const IP, Port, Path, User, Pass: string; DirList: TStringList): Boolean;
var
i: Integer;
s: string;
begin
Result := False;
with TFTPSend.Create do
try
Username := User;
Password := Pass;
TargetHost := IP;
TargetPort := Port;
if not Login then
Exit;
Result := List(Path, False);
for i := 0 to FtpList.Count -1 do
begin
s := FTPList[i].FileName;
DirList.Add(s);
end;
Logout;
finally
Free;
end;
end;
Z serwera HTTP
Po podaniu adresu URL (i ścieżki) oraz nazwy pliku, zostanie on pobrany z serwera HTTP. Zauważ, że ten kod sprawdza kod statusu HTTP (np. 200, 404), aby sprawdzić, czy dokument, który otrzymaliśmy z serwera, jest żądanym plikiem czy stroną błędu.
Wersja prosta
...
uses httpsend,
...
function DownloadHTTP(URL, TargetFile: string): Boolean;
var
HTTPGetResult: Boolean;
HTTPSender: THTTPSend;
begin
Result := False;
HTTPSender := THTTPSend.Create;
try
HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then begin
HTTPSender.Document.SaveToFile(TargetFile);
Result := True;
end;
finally
HTTPSender.Free;
end;
end;
Wersja zaawansowana
...
uses httpsend
...
function DownloadHTTP(URL, TargetFile: string): Boolean;
// Pobieranie pliku; w razie potrzeby spróbuj ponownie.
// Można próbować użyć funkcji Synapse HttpGetBinary, ale to nie działa
// z kodami wyników (tzn. szczęśliwie pobiera dokument błędu 404)
const
MaxRetries = 3;
var
HTTPGetResult: Boolean;
HTTPSender: THTTPSend;
RetryAttempt: Integer;
begin
Result := False;
RetryAttempt := 1;
HTTPSender := THTTPSend.Create;
try
try
// Spróbuj pobrać plik
HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
while (HTTPGetResult = False) and (RetryAttempt < MaxRetries) do
begin
Sleep(500 * RetryAttempt);
HTTPSender.Clear;
HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
RetryAttempt := RetryAttempt + 1;
end;
// Jeśli mamy odpowiedź z serwera, sprawdź, czy plik został do nas wysłany.
case HTTPSender.Resultcode of
100..299:
begin
HTTPSender.Document.SaveToFile(TargetFile);
Result := True;
end; //informational, success
300..399: Result := False; // przekierowanie. Nie zaimplementowano, ale może się zdarzyć.
400..499: Result := False; // błąd klienta; 404 nie znaleziono itp.
500..599: Result := False; // wewnętrzny błąd serwera
else Result := False; // kod nieznany
end;
except
// Nie obchodzi nas przyczyna tego błędu; pobieranie nie powiodło się.
Result := False;
end;
finally
HTTPSender.Free;
end;
end;
Prosta wersja z paskiem postępu
Poniższy przykład pokazuje, jak uzyskać informacje o postępie z pobierania HTTP, a także rozmiar pliku. Rozmiar pliku jest pobierany z informacji zawartej w nagłówku.
unit uhttpdownloader;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, httpsend, blcksock, typinfo;
type
IProgress = interface
procedure ProgressNotification(Text: String; CurrentProgress : integer; MaxProgress : integer);
end;
type
{ THttpDownloader }
THttpDownloader = class
public
function DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
private
Bytes : Integer;
MaxBytes : Integer;
HTTPSender: THTTPSend;
ProgressMonitor : IProgress;
procedure Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
function GetSizeFromHeader(Header: String):integer;
end;
implementation
function THttpDownloader.DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
var
HTTPGetResult: Boolean;
begin
Result := False;
Bytes:= 0;
MaxBytes:= -1;
Self.ProgressMonitor:= ProgressMonitor;
HTTPSender := THTTPSend.Create;
try
HTTPSender.Sock.OnStatus:= Status;
HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then begin
HTTPSender.Document.SaveToFile(TargetFile);
Result := True;
end;
finally
HTTPSender.Free;
end;
end;
procedure THttpDownloader.Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
var
V, currentHeader: String;
i: integer;
begin
//spróbuj pobrać rozmiar pliku z nagłówków
if (MaxBytes = -1) then
begin
for i:= 0 to HTTPSender.Headers.Count - 1 do
begin
currentHeader:= HTTPSender.Headers[i];
MaxBytes:= GetSizeFromHeader(currentHeader);
if MaxBytes <> -1 then break;
end;
end;
V := GetEnumName(TypeInfo(THookSocketReason), Integer(Reason)) + ' ' + Value;
if Reason = THookSocketReason.HR_ReadCount then
begin
Bytes:= Bytes + StrToInt(Value);
ProgressMonitor.ProgressNotification(V, Bytes, MaxBytes);
end;
end;
function THttpDownloader.GetSizeFromHeader(Header: String): integer;
var
item : TStringList;
begin
Result:= -1;
if Pos('Content-Length:', Header) <> 0 then
begin
item:= TStringList.Create();
item.Delimiter:= ':';
item.StrictDelimiter:=true;
item.DelimitedText:=Header;
if item.Count = 2 then
begin
Result:= StrToInt(Trim(item[1]));
end;
end;
end;
end.
Co my tu robimy?
Przede wszystkim zaglądamy do nagłówków, aby uzyskać rozmiar pliku. Musimy poczekać i sprawdzić, czy nagłówek tam jest. Pierwsze zdarzenia nie zawierają informacji o treści Content-Length.
Po znalezieniu wyodrębniamy te informacje. Pojawia się tutaj kilka zdarzeń, na które możesz zareagować. Ale w tym przykładzie sprawdzamy tylko THookSocketReason.HR_ReadCount.
„HR_ReadCount” dostarcza nam informacji ile bajtów zostało odczytanych od ostatniego zdarzenia.
Postęp jest następnie zgłaszany do interfejsu użytkownika:
procedure TMainForm.ProgressNotification(Text: String; CurrentProgress: integer; MaxProgress: integer);
begin
if (MaxProgress <> -1) then
begin
ProgressBar.Max:= MaxProgress;
end;
ProgressBar.Position:= CurrentProgress;
memoStatus.Lines.Add(Text);
Application.ProcessMessages;
end;
Tak więc ostatecznym głównym modułem będzie:
unit uMain;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ComCtrls, httpsend, blcksock, typinfo,
uhttpdownloader;
type
{ TMainForm }
TMainForm = class(TForm, IProgress)
btnStartDownload: TButton;
edtUrl: TEdit;
labelUrl: TLabel;
memoStatus: TMemo;
ProgressBar: TProgressBar;
SaveDialog: TSaveDialog;
procedure btnStartDownloadClick(Sender: TObject);
private
{ private declarations }
function GetFileNameFromURL(url: String):string;
public
{ public declarations }
procedure ProgressNotification(Text: String; CurrentProgress : integer; MaxProgress : integer);
end;
var
MainForm: TMainForm;
implementation
{$R *.lfm}
{ TMainForm }
procedure TMainForm.btnStartDownloadClick(Sender: TObject);
var
fileName: String;
downloader: THttpDownloader;
success: boolean;
begin
fileName:= GetFileNameFromURL(edtUrl.Text);
SaveDialog.FileName:=fileName;
if (SaveDialog.Execute) then
begin
memoStatus.Lines.Clear;
ProgressBar.Position:=0;
downloader:= THttpDownloader.Create();
success:= downloader.DownloadHTTP(edtUrl.Text, SaveDialog.FileName, Self);
ProgressBar.Position:=0;
if Success then
memoStatus.Lines.Add('Download successful')
else
memoStatus.Lines.Add('Error during download');
end;
end;
function TMainForm.GetFileNameFromURL(url: String): string;
var i, l : integer;
fileName, current : String;
begin
fileName:= '';
l:= Length(url);
for i:= l downto 0 do begin
current:= url[i];
if current <> '/' then
begin
fileName:= current + fileName;
end else begin
Result:= fileName;
break;
end;
end;
end;
procedure TMainForm.ProgressNotification(Text: String; CurrentProgress: integer; MaxProgress: integer);
begin
if (MaxProgress <> -1) then ProgressBar.Max:= MaxProgress;
ProgressBar.Position:= CurrentProgress;
memoStatus.Lines.Add(Text);
Application.ProcessMessages;
end;
end.
Odnosnik: https://andydunkel.net/2015/09/09/lazarus_synapse_progress/
Z serwera HTTP przez parsowanie adresów URL: Sourceforge
Zobacz Pobieranie z SourceForge, aby zapoznać się z przykładem pobierania z sourceforge.net.
Z serwera HTTPS
Jest to podobne do pobierania z serwera HTTP. Dodatkowo musisz aktywować obsługę SSL/TLS i uzyskać plik(i) binarny wymaganej biblioteki. Następnie możesz użyć tej samej funkcji DownloadHTTP do pobrania pliku z adresu URL zaczynającego się od https://.
Przykładowy program klienta SSH/Telnet
Poniżej znajdziesz moduł, który pozwala na korzystanie z funkcjonalności klienta telnet/SSH, która wykorzystuje moduł synapse tlntsend.pas. Przykładowy program pokazuje, jak tego używać. Inny, prostszy sposób ilustruje Leonardo Ramé na [1]. Jego przykład nie może jednak używać telnetu i wysyła tylko jedno polecenie.
Wymagania
Oprócz źródeł Synapse (z których potrzebujesz tylko kilku), jeśli chcesz korzystać z funkcjonalności SSH, będziesz potrzebować biblioteki szyfrowania, której używa Synapse. Chyba, że używasz tylko usługi Telnet, wówczas tego nie potrzebujesz.
Istnieją 2 możliwości:
- Biblioteka Cryptlib. Zaleta: stabilna. Najwyraźniej można używać kluczy prywatnych, ale są one stosowane w pewnym formacie, który nie jest powszechnie obsługiwany.
- Biblioteka LibSSH2. Obsługa w Pascalu jest nadal w fazie rozwoju, ale do uwierzytelnienia możesz użyć pliku z kluczem prywatnym (w formacie OpenSSH).
Cryptlib
- W systemie Windows pobierz binarną wersję biblioteki DLL cryptlib (CL32.DLL) i umieść ją w katalogu źródłowym. Jeśli kompilujesz do innego katalogu lub rozprowadzasz swój program, będziesz musiał również rozesłać bibliotekę DLL.
- W systemach Linux i OSX zainstaluj cryptlib za pomocą menedżera pakietów lub z innych źródeł. Podczas dystrybucji aplikacji oznacz cryptlib jako wymaganie w swoim pakiecie .deb/.rpm/inny.
Będziesz także potrzebował dowiązań (cryptlib.pas), obecnych w źródłach dystrybucji cryptlib.
Wersje pliku binarnego cryptlib i dowiązania muszą być zgodne.
LibSSH2
- W systemie Windows pobierz binarną wersję biblioteki DLL libssh2 (LIBSSH2.DLL) i umieść ją w katalogu źródłowym. Jeśli kompilujesz do innego katalogu lub rozprowadzasz swój program, będziesz musiał również rozesłać tą bibliotekę DLL.
- W systemach Linux i macOS zainstaluj libssh2 za pomocą menedżera pakietów lub z innych środków. Ponadto dystrybuując swoją aplikację:
- Linux: oznacz libssh2 jako wymaganie w swoim pakiecie .deb/.rpm/inny.
- macOS: dołącz libssh2 do katalogu zasobów Pakietu aplikacji.
Będziesz także potrzebował ssl_libssh2.pas (patrz poniżej) i dowiązań: (libssh2.pas, zobacz ten post na forum). Plik binarny libssh2 i dowiązania muszą być zgodne.
Wtyczka Synapse libssh2 SSL
{
ssl_libssh2.pas wersja 0.2
Wtyczka obsługi SSH2 (zarys) dla biblioteki Synapse (http://www.ararat.cz/synapse) do LibSSH2 (http://libssh2.org)
Wymaga: interfejsu libssh2 pascal - http://www.lazarus.freepascal.org/index.php/topic,15935.msg86465.html#msg86465 oraz
libssh2.dll z OpenSSL.
(С) Alexey Suhinin http://x-alexey.narod.ru
}
{$IFDEF FPC}
{$MODE DELPHI}
{$ENDIF}
{$H+}
unit ssl_libssh2;
interface
uses
SysUtils,
blcksock, synsock,
libssh2;
type
{:@abstract(klasa implementująca wtyczkę CryptLib SSL/SSH.)
Instancja tej klasy zostanie utworzona dla każdego @link(TTCPBlockSocket).
Nie musisz tworzyć instancji tej klasy, wszystko robi sam Synapse!}
TSSLLibSSH2 = class(TCustomSSL)
protected
FSession: PLIBSSH2_SESSION;
FChannel: PLIBSSH2_CHANNEL;
function SSHCheck(Value: integer): Boolean;
function DeInit: Boolean;
public
{:Zobacz @inherited}
constructor Create(const Value: TTCPBlockSocket); override;
destructor Destroy; override;
function Connect: boolean; override;
function LibName: String; override;
function Shutdown: boolean; override;
{:Zobacz @inherited}
function BiShutdown: boolean; override;
{:Zobacz @inherited}
function SendBuffer(Buffer: TMemory; Len: Integer): Integer; override;
{:Zobacz @inherited}
function RecvBuffer(Buffer: TMemory; Len: Integer): Integer; override;
{:Zobacz @inherited}
function WaitingData: Integer; override;
{:Zobacz @inherited}
function GetSSLVersion: string; override;
published
end;
implementation
{==============================================================================}
function TSSLLibSSH2.SSHCheck(Value: integer): Boolean;
var
PLastError: PAnsiChar;
ErrMsgLen: Integer;
begin
Result := true;
FLastError := 0;
FLastErrorDesc := '';
if Value<0 then
begin
FLastError := libssh2_session_last_error(FSession, PLastError, ErrMsglen, 0);
FLastErrorDesc := PLastError;
Result := false;
end;
end;
function TSSLLibSSH2.DeInit: Boolean;
begin
if Assigned(FChannel) then
begin
libssh2_channel_free(FChannel);
FChannel := nil;
end;
if Assigned(FSession) then
begin
libssh2_session_disconnect(FSession,'Goodbye');
libssh2_session_free(FSession);
FSession := nil;
end;
FSSLEnabled := False;
Result := true;
end;
constructor TSSLLibSSH2.Create(const Value: TTCPBlockSocket);
begin
inherited Create(Value);
FSession := nil;
FChannel := nil;
end;
destructor TSSLLibSSH2.Destroy;
begin
DeInit;
inherited Destroy;
end;
function TSSLLibSSH2.Connect: boolean;
begin
Result := False;
if SSLEnabled then DeInit;
if (FSocket.Socket <> INVALID_SOCKET) and (FSocket.SSL.SSLType = LT_SSHv2) then
begin
FSession := libssh2_session_init();
if not Assigned(FSession) then
begin
FLastError := -999;
FLastErrorDesc := 'Cannot initialize SSH session';
exit;
end;
if not SSHCheck(libssh2_session_startup(FSession, FSocket.Socket)) then
exit;
if (FSocket.SSL.PrivateKeyFile<>'') then
begin
if (not SSHCheck(libssh2_userauth_publickey_fromfile(FSession, PChar(FSocket.SSL.Username), nil, PChar(FSocket.SSL.PrivateKeyFile), PChar(FSocket.SSL.KeyPassword)))) then
exit;
end
else
if (FSocket.SSL.Username<>'') and (FSocket.SSL.Password<>'') then
begin
if (not SSHCheck(libssh2_userauth_password(FSession, PChar(FSocket.SSL.Username), PChar(FSocket.SSL.Password)))) then
exit;
end;
FChannel := libssh2_channel_open_session(FSession);
if not assigned(FChannel) then
begin
SSHCheck(-1); // get error
if FLastError = 0 then
begin
FLastError := -999; // unknown error
FLastErrorDesc := 'Cannot open session';
end;
exit;
end;
if not SSHCheck(libssh2_channel_request_pty(FChannel, 'vanilla')) then
exit;
if not SSHCheck(libssh2_channel_shell(FChannel)) then
exit;
FSSLEnabled := True;
Result := True;
end;
end;
function TSSLLibSSH2.LibName: String;
begin
Result := 'ssl_libssh2';
end;
function TSSLLibSSH2.Shutdown: boolean;
begin
Result := DeInit;
end;
function TSSLLibSSH2.BiShutdown: boolean;
begin
Result := DeInit;
end;
function TSSLLibSSH2.SendBuffer(Buffer: TMemory; Len: Integer): Integer;
begin
Result:=libssh2_channel_write(FChannel, PChar(Buffer), Len);
SSHCheck(Result);
end;
function TSSLLibSSH2.RecvBuffer(Buffer: TMemory; Len: Integer): Integer;
begin
result:=libssh2_channel_read(FChannel, PChar(Buffer), Len);
SSHCheck(Result);
end;
function TSSLLibSSH2.WaitingData: Integer;
begin
if libssh2_poll_channel_read(FChannel, Result) <> 1 then Result := 0;
end;
function TSSLLibSSH2.GetSSLVersion: string;
begin
Result:=libssh2_version(0);
end;
initialization
if libssh2_init(0)=0 then
SSLImplementation := TSSLLibSSH2;
finalization
libssh2_exit;
end.
Klasa klienta terminala
Poniższy moduł telnetsshclient.pas wykorzystuje moduł Synapse tlntsend.pas i opisuje logowanie, wysyłanie poleceń, odbieranie danych wyjściowych i wylogowywanie.
Jeśli potrzebujesz tylko klienta telnet i możesz żyć bez obsługi SSH, zakomentuj {$DEFINE HAS_SSH_SUPPORT} poniżej, aby kod nie wymagał biblioteki libssh2.
To urządzenie zostało lekko przetestowane na serwerze Linux ssh/telnet. Dodatkowe testy mile widziane.
unit telnetsshclient;
{ Wrapper wokół bibliotek Synapse i biblioteki SSL (obecnie używane są libssh2+libssl)
Pobierz skompilowaną bibliotekę Windows dll, np. z
http://alxdm.dyndns-at-work.com:808/files/windll_libssh2.zip
Pobierz pliki interfejsu FreePascal:
http://www.lazarus.freepascal.org/index.php/topic,15935.msg86465.html#msg86465
Ten moduł umożliwia użytkownikowi wysyłanie poleceń Telnet lub SSH i uzyskiwanie danych wyjściowych
Dzięki Leonardo Rame
http://leonardorame.blogspot.com/2010/01/synapse-based-ssh-client.html
i Ludo Brands.
Napisany przez Reiniera Olislagersa 2011.
Zmodyfikowany dla libssh2 przez Alexeya Suhinina 2012.
Licencja kodu:
* MIT
* LGPLv2 lub nowszy (z wyjątkiem statycznego linkowania FreePascal)
* GPLv2 lub nowszy
zgodnie z twoim wyborem.
Dozwolone jest bezpłatne korzystanie, ale proszę nie pozywać mnie ani nie obwiniać.
Korzysta z innych bibliotek/komponentów, dla których mogą obowiązywać różne licencje, które również mogą wpływać na skompilowaną pracę.
}
{$mode objfpc}{$H+}
{$DEFINE HAS_SSH_SUPPORT} //zakomentuj, jeśli wymagana jest tylko obsługa telnet
{$DEFINE LIBSSH2}
interface
uses
Classes, SysUtils,
tlntsend
{$IFDEF HAS_SSH_SUPPORT}
{ssl - a właściwie teraz ssh - biblioteki wymagane przez tlntsend}
{$IFDEF LIBSSH2}
ssl_libssh2
{$ELSE}
ssl_cryptlib
{$ENDIF}
{$ENDIF HAS_SSH_SUPPORT} ;
type
TProtocolType = (Telnet, SSH); //Różne sposoby łączenia
TServerType = (Unix, Windows); //głównie dotyczy zakończenia linii tekstu
{ TelnetSSHClient }
{ TTelnetSSHClient }
TTelnetSSHClient = class(TTelnetSend)
protected
FConnected: boolean;
FOutputPosition: integer; //Śledzi pozycję w strumieniu wyjściowym
FProtocolType: TProtocolType;
FServerLineEnding: string; //zależy od FServerType
FServerType: TServerType;
FWelcomeMessage, FTelnetLoginPrompt, FTelnetPasswordPrompt: string;
procedure SetPrivateKeyFile(Value: string);
function GetPrivateKeyFile: string;
{ Na podstawie protokołu i typu serwera ustaw oczekiwane zakończenie linii po stronie serwera}
procedure DetermineLineEnding;
{ Ustawia port, jeśli portu nie ustawiono jawnie. Używa protokołu typu: SSH lub telnet}
procedure DeterminePort;
function GetSessionLog: string;
procedure ProtocolTypeChange(Value: TProtocolType);
function ReceiveData: string; //Może być używany do odbierania wiadomości powitalnej itp.
procedure SendData(Data: string);
procedure ServerTypeChange(Value: TServerType);
public
{Wszystkie dane wyjściowe wygenerowane podczas całej sesji do tej pory}
property AllOutput: string read GetSessionLog;
{Ma wartość True, jeśli jest podłączony do serwera}
property Connected: boolean read FConnected;
{Nazwa lub adres IP hosta, z którym chcesz się połączyć}
property HostName: string read FTargetHost write FTargetHost;
{Port na hoście używany do połączenia. Jeśli zostanie pozostawiony jako 0, zostanie określony przez typ protokołu (22 dla SSH, 23 dla Telnet}
property Port: String read FTargetPort write FTargetPort;
{Lokalizacja pliku klucza prywatnego}
property PrivateKeyFile: string read GetPrivateKeyFile write SetPrivateKeyFile;
{Pytanie o login Telnet}
property TelnetLoginPrompt: string read FTelnetLoginPrompt write FTelnetLoginPrompt;
{Pytanie o hasło Telnet}
property TelnetPasswordPrompt: string read FTelnetPasswordPrompt write FTelnetPasswordPrompt;
{Nazwa użytkownika używana podczas łączenia}
property UserName: string read FUserName write FUserName;
{Hasło używane podczas łączenia. Używane jako hasło (passphrase), jeśli używany jest klucz prywatny}
property Password: string read FPassword write FPassword;
{Czy powinniśmy rozmawiać z serwerem przez Telnet czy SSH? Domyślnie jest to SSH}
property ProtocolType: TProtocolType read FProtocolType write ProtocolTypeChange;
{Serwer Windows czy Unix/Linux? Ma to wpływ na końcówki linii. Domyślnie jest to Unix. UWAGA: nieprzetestowane}
property Servertype: TServerType read FServerType write ServerTypeChange;
{Pierwsza wiadomość wyświetlana przy logowaniu}
property WelcomeMessage: string read FWelcomeMessage;
{Połącz/zaloguj się do serwera. Wymaga poprawnych wszystkich opcji uwierzytelniania, protokołu i nazwy hosta/portu.
Zwraca opisowy wynik. Następnie możesz użyć właściwości Connected.}
function Connect: string;
{Jeśli połączony, wyloguj się z serwera}
procedure Disconnect;
{Wyślij polecenie do serwera i odbierz wynik}
function CommandResult(Command: string): string; //Wyślij polecenie i uzyskaj wyniki
constructor Create;
destructor Destroy; override;
end;
implementation
{ TelnetSSHClient }
procedure TTelnetSSHClient.SetPrivateKeyFile(value: string);
begin
Sock.SSL.PrivateKeyFile := value;
end;
function TTelnetSSHClient.GetPrivateKeyFile: string;
begin
Result := Sock.SSL.PrivateKeyFile;
end;
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('Nieznany typ protokołu.');
end;
end;
procedure Ttelnetsshclient.DeterminePort;
begin
if FTargetPort = '' then
//Ustaw domyślny port dla protokołu
begin
case FProtocolType of
Telnet: FTargetPort := '23';
SSH: FTargetPort := '22';
else
raise Exception.Create('Nieznany typ protokołu.');
end;
end;
end;
procedure TTelnetSSHClient.ServerTypeChange(Value: Tservertype);
begin
FServerType := Value;
DetermineLineEnding;
end;
function TTelnetSSHClient.Connect: string;
var
Received: string;
begin
result:='Nieznany błąd podczas łączenia';
FOutputPosition := 1; //Pierwszy znak w strumieniu wyjściowym
FWelcomeMessage := '';
//Tylko dla pewności:
DetermineLineEnding;
DeterminePort;
if FTargetPort='0' then
begin
result:='Port nie może być równy 0.';
exit; //wyskoczyć z funkcji
end;
case FProtocolType of
Telnet:
begin
try
if Login then
begin
FConnected := True;
result:='Połączono z serwerem telnet.';
end
else
if Sock.LastError<>0 then raise Exception.Create(Sock.LastErrorDesc);
except
on E: Exception do
begin
FConnected:=false;
result:='Błąd połączenia z serwerem telnet '+FTargetHost+':'+
FTargetPort+' jako użytkownik ' + FUserName +
'. Szczegóły techniczne: '+E.Message;
end;
end;
end;
SSH:
begin
{$IFNDEF HAS_SSH_SUPPORT}
raise Exception.Create(
'Obsługa SSH nie została skompilowana do biblioteki telnetsshclient.');
{$ENDIF HAS_SSH_SUPPORT}
try
if (PrivateKeyFile <> '') and (FPassword <> '') then
Sock.SSL.KeyPassword:=FPassword;
if SSHLogin then
begin
FConnected := True;
result:='Connected to SSH server.';
end
else
begin
if Sock.LastError<>0 then raise Exception.Create(Sock.LastErrorDesc);
if Sock.SSL.LastError<0 then raise Exception.Create(Sock.SSL.LastErrorDesc);
end;
except
on E: Exception do
begin
FConnected:=false;
result:='Błąd połączenia z serwerem SSH '+FTargetHost+':'+
FTargetPort+' jako użytkownik ' + FUserName +
'. Szczegóły techniczne: '+E.Message;
end;
end;
end;
else
raise Exception.Create('Nieznany typ protokołu.');
end;
if FConnected = True then
begin
FWelcomeMessage := ReceiveData;
if FProtocolType=Telnet then
begin
//Niestety, będziemy musieli sami wyodrębnić login
//Mam nadzieję, że dotyczy to wszystkich typów serwerów.
if (AnsiPos(AnsiLowerCase(FTelnetLoginPrompt),AnsiLowerCase(FWelcomeMessage))>0) then
begin
SendData(UserName);
end;
Received:=ReceiveData;
if (AnsiPos(AnsiLowerCase(FTelnetPasswordPrompt),AnsiLowerCase(Received))>0) then
begin
SendData(Password);
end;
//Otrzymuj dodatkową wiadomość powitalną/wiadomość dnia
FWelcomeMessage:=FWelcomeMessage+LineEnding+ReceiveData;
end;
end;
end;
procedure TTelnetSSHClient.Disconnect;
begin
Logout;
FConnected := False;
end;
function TTelnetSSHClient.ReceiveData: string;
begin
Result := '';
while Sock.CanRead(1000) or (Sock.WaitingData > 0) do
begin
Sock.RecvPacket(1000);
Result := Result + Copy(SessionLog, FOutputPosition,
Length(SessionLog));
FOutputPosition := Length(SessionLog) + 1;
end;
end;
procedure Ttelnetsshclient.SendData(Data: String);
begin
Data := Data + FServerLineEnding; //Może to być linux, może być Windows
Send(Data);
end;
function TTelnetSSHClient.GetSessionLog: string;
begin
// Do tej pory uzyskuje pełne dane wyjściowe
Result := SessionLog;
end;
procedure TTelnetSSHClient.ProtocolTypeChange(Value: Tprotocoltype);
begin
FProtocolType := Value;
//W razie potrzeby automatycznie określ port i zakończenie linii
DeterminePort;
DetermineLineEnding;
end;
function TTelnetSSHClient.CommandResult(Command: string): string;
begin
Result := '';
if FConnected then
begin
SendData(Command);
Result := ReceiveData; //dostaje za dużo
end
else
begin
//podnieś wyjątek
Result := '';
raise Exception.Create('Polecenie można uruchomić tylko po podłączeniu');
end;
end;
constructor TTelnetSSHClient.Create;
begin
inherited;
FConnected := False;
FProtocolType := SSH; //Może to też być telnet
FServerType := Unix; //Prawdopodobnie bezpieczna wartość domyślna.
FTelnetLoginPrompt := 'login:';
FTelnetPasswordPrompt := 'password:';
DetermineLineEnding;
DeterminePort;
end;
destructor TTelnetSSHClient.Destroy;
begin
if FConnected then
Disconnect;
inherited Destroy;
end;
end.
Przykładowy kod klienta
Aby użyć właśnie stworzonej przez nas klasy TTelnetSSHClient, możesz wypróbować przykładową aplikację, sshtest.lpr. Zauważ, że kod musi być skompilowany przez Lazarusa, ponieważ potrzebuje komponentów LCL do pracy z Synapse:
program sshtest;
{Program testowy dla telnetsshclient
Napisany przez Reiniera Olislagersa 2011.
Zmodyfikowany dla libssh2 przez Alexeya Suhinina 2012.
Licencja kodu:
* MIT
* LGPLv2 lub nowszy (z wyjątkiem statycznego linkowania FreePascal)
* GPLv2 lub nowszy
zgodnie z twoim wyborem.
Dozwolone jest bezpłatne korzystanie, ale proszę nie pozywać mnie ani nie obwiniać.
Korzysta z innych bibliotek/komponentów, dla których mogą obowiązywać różne licencje, które również mogą wpływać na skompilowaną pracę.
Uruchamianie: sshtest <serverIPorhostname> [PrivateKeyFile]
}
{$mode objfpc}{$H+}
{$APPTYPE CONSOLE}
uses
telnetsshclient;
var
comm: TTelnetSSHClient;
Command: string;
begin
writeln('Start.');
comm:=TTelnetSSHClient.Create;
comm.HostName:= ParamStr(1); //Pierwszy argument w wierszu poleceń
if comm.HostName='' then
begin
writeln('Proszę podać nazwę hosta w wierszu poleceń.');
halt(1);
end;
comm.PrivateKeyFile := ParamStr(2);
comm.TargetPort:='0'; //automatyczne określanie na podstawie typu protokołu
comm.UserName:='root'; //zmień dla swojej sytuacji
comm.Password:='password'; //zmień dla swojej sytuacji
comm.ProtocolType:=SSH; //Telnet lub SSH
writeln(comm.Connect); //Pokaż wynik połączenia
if comm.Connected then
begin
writeln('Serwer: ' + comm.HostName + ':'+comm.TargetPort+', użytkownik: '+comm.UserName);
writeln('Wiadomość powitalna:');
writeln(comm.WelcomeMessage);
Command:='ls -al';
writeln('*** Wysyłanie ' + Command);
writeln('*** Początek wyniku****');
writeln(comm.CommandResult(Command));
writeln('*** Koniec wyniku****');
writeln('');
writeln('');
Command:='df -h';
writeln('*** Wysyłanie ' + Command);
writeln('*** Początek wyniku****');
writeln(comm.CommandResult(Command));
writeln('*** Koniec wyniku****');
writeln('');
writeln('');
writeln('Wszystkie wyjścia:');
writeln('*** Początek wyniku****');
writeln(comm.AllOutput);
writeln('*** Koniec wyniku****');
comm.Disconnect;
end
else
begin
writeln('Połączenie z ' +
comm.HostName + ':' +
comm.TargetPort + ' nie udało się.');
end;
comm.Free;
end.
Integracja z OAuth v1/Twitter/Plurk
Biblioteka OAuth v1 napisana w FPC, która używa Synapse (i jest gotowa do obsługi innych bibliotek sieciowych, takich jak lNet) jest dostępna w fpctwit. FPCtwit zawiera również przykładowe programy klienckie FPC jak Twitter i Plurk oraz klienta Lazarus Twitter.
Inne artykuły internetowe i sieciowe
- Portal na temat tworzenia stron internetowych
- Sieć
- Biblioteki sieciowe - porównanie różnych bibliotek sieciowych
- Brook Framework - Idealny framework Free Pascal dla Twoich aplikacji internetowych. To czysty Pascal. Nie musisz porzucać preferowanego języka programowania.
- Gniazda - Komponenty gniazd TCP/IP
- fcl-net - Biblioteka sieciowa dostarczana z FPC
- lNet - Lekkie komponenty sieciowe
- Synapse - Port szeregowy i synchroniczna biblioteka TCP/IP
- Poradnik XML - XML jest często używany w komunikacji sieciowej
- FPC i moduły Apache
- fcl-web - Znana również jako fpWeb, jest to biblioteka do tworzenia aplikacji internetowych, które można wdrażać jako moduły cgi, fastcgi lub apache.
- Bezpieczne programowanie
- Narzędzia internetowe - Wrapper wokół komponentów http Synapse/wininet/Android upraszczający korzystanie z https i przekierowań oraz silnik XPath/XQuery/CSS Selector/JSONiq do przetwarzania pobranych stron
Zobacz też
- Pobieranie z SourceForge Przykład, który używa Synapse do pobierania z serwera HTTP, który stosuje przekierowanie.
- Wizualny Synapse wrappery komponentów dla wielu elementów komunikacji szeregowej i sieciowej biblioteki Synapse (TvsComPort, TvsWebClient, TvsSniffer, TvsHTTPServer, TvsFTPServer, TvsAuthentication, TvsVisualDNS, TvsVisualHTTP, TvsVisualDUP, TvsVisualTCP, TvsVisualICMP, TvsSocksProxyInfo, TvsIPHelper, TvsSendMail i TvsSynPing).
- Komponent TCP/IP oparty na Synapse + mała aplikacja demonstracyjna
Linki zewnętrzne
- Oficjalna strona
- Oficjalna dokumentacja
- Użytkownik malcome stworzył rozwidlenie, aby szybciej poprawić synapse; ta gałąź znajduje się pod adresem
Witryna z kodem Google dla Synapse4Lazarus(aktualnie niedostępna)