Synapse/pl

From Lazarus wiki
Revision as of 00:13, 7 July 2022 by Slawek (talk | contribs) (→‎Zobacz też: link pl)
Jump to navigationJump to search

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.

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;

// Niezbędne do działania. Z wartością domyślną {$mode objfpc}{$H+} to nie działa.
{$mode Delphi}

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 Delphi}

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.

Light bulb  Uwaga: Wygląda na to, że cryptlib nie nadaje się do łączenia z maszynami linuksowymi, chociaż w systemie AIX działa. Zamiast tego użyj SSH2.

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

Light bulb  Uwaga: Wtyczka nie jest ukończona.
{
  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

Zobacz też

Linki zewnętrzne