瀏覽代碼

ADD: FTP - SSH-agent authentication (issue #1133)

Alexander Koblov 1 年之前
父節點
當前提交
9e97bc10d5

+ 28 - 17
plugins/wfx/ftp/src/FtpConfDlg.lfm

@@ -13,7 +13,7 @@ object DialogBox: TDialogBox
   ClientWidth = 440
   ClientWidth = 440
   OnShow = DialogBoxShow
   OnShow = DialogBoxShow
   Position = poScreenCenter
   Position = poScreenCenter
-  LCLVersion = '1.8.4.0'
+  LCLVersion = '2.2.7.0'
   object btnCancel: TButton
   object btnCancel: TButton
     AnchorSideTop.Control = PageControl
     AnchorSideTop.Control = PageControl
     AnchorSideTop.Side = asrBottom
     AnchorSideTop.Side = asrBottom
@@ -417,7 +417,7 @@ object DialogBox: TDialogBox
         AnchorSideRight.Control = gbFTP
         AnchorSideRight.Control = gbFTP
         AnchorSideRight.Side = asrBottom
         AnchorSideRight.Side = asrBottom
         Left = 6
         Left = 6
-        Height = 166
+        Height = 204
         Top = 101
         Top = 101
         Width = 400
         Width = 400
         Anchors = [akTop, akLeft, akRight]
         Anchors = [akTop, akLeft, akRight]
@@ -426,30 +426,41 @@ object DialogBox: TDialogBox
         Caption = 'SSH'
         Caption = 'SSH'
         ChildSizing.LeftRightSpacing = 6
         ChildSizing.LeftRightSpacing = 6
         ChildSizing.TopBottomSpacing = 6
         ChildSizing.TopBottomSpacing = 6
-        ClientHeight = 139
-        ClientWidth = 394
+        ClientHeight = 184
+        ClientWidth = 396
         TabOrder = 1
         TabOrder = 1
-        object chkCopySCP: TCheckBox
+        object chkAgentSSH: TCheckBox
           AnchorSideLeft.Control = gbSSH
           AnchorSideLeft.Control = gbSSH
           AnchorSideTop.Control = gbSSH
           AnchorSideTop.Control = gbSSH
           Left = 6
           Left = 6
           Height = 19
           Height = 19
           Top = 6
           Top = 6
-          Width = 238
-          Caption = 'Copy using SCP protocol (faster)'
+          Width = 178
+          Caption = 'Use SSH-agent authentication'
           TabOrder = 0
           TabOrder = 0
         end
         end
+        object chkCopySCP: TCheckBox
+          AnchorSideLeft.Control = gbSSH
+          AnchorSideTop.Control = chkAgentSSH
+          AnchorSideTop.Side = asrBottom
+          Left = 6
+          Height = 19
+          Top = 25
+          Width = 192
+          Caption = 'Copy using SCP protocol (faster)'
+          TabOrder = 1
+        end
         object chkOnlySCP: TCheckBox
         object chkOnlySCP: TCheckBox
           AnchorSideLeft.Control = gbSSH
           AnchorSideLeft.Control = gbSSH
           AnchorSideTop.Control = chkCopySCP
           AnchorSideTop.Control = chkCopySCP
           AnchorSideTop.Side = asrBottom
           AnchorSideTop.Side = asrBottom
           Left = 6
           Left = 6
           Height = 19
           Height = 19
-          Top = 30
-          Width = 241
+          Top = 44
+          Width = 193
           Caption = 'Use SSH+SCP protocol (no SFTP)'
           Caption = 'Use SSH+SCP protocol (no SFTP)'
           ParentFont = False
           ParentFont = False
-          TabOrder = 1
+          TabOrder = 2
         end
         end
         object DividerBevel: TDividerBevel
         object DividerBevel: TDividerBevel
           AnchorSideTop.Control = chkOnlySCP
           AnchorSideTop.Control = chkOnlySCP
@@ -458,7 +469,7 @@ object DialogBox: TDialogBox
           AnchorSideRight.Side = asrBottom
           AnchorSideRight.Side = asrBottom
           Left = 6
           Left = 6
           Height = 15
           Height = 15
-          Top = 31
+          Top = 69
           Width = 384
           Width = 384
           Caption = 'Client certificate for authentication:'
           Caption = 'Client certificate for authentication:'
           Anchors = [akTop, akLeft, akRight]
           Anchors = [akTop, akLeft, akRight]
@@ -471,7 +482,7 @@ object DialogBox: TDialogBox
           AnchorSideTop.Side = asrBottom
           AnchorSideTop.Side = asrBottom
           Left = 6
           Left = 6
           Height = 15
           Height = 15
-          Top = 52
+          Top = 90
           Width = 116
           Width = 116
           BorderSpacing.Top = 6
           BorderSpacing.Top = 6
           Caption = 'Public key file (*.pub):'
           Caption = 'Public key file (*.pub):'
@@ -485,7 +496,7 @@ object DialogBox: TDialogBox
           AnchorSideRight.Side = asrBottom
           AnchorSideRight.Side = asrBottom
           Left = 6
           Left = 6
           Height = 23
           Height = 23
-          Top = 71
+          Top = 109
           Width = 384
           Width = 384
           FilterIndex = 0
           FilterIndex = 0
           HideDirectories = False
           HideDirectories = False
@@ -494,7 +505,7 @@ object DialogBox: TDialogBox
           Anchors = [akTop, akLeft, akRight]
           Anchors = [akTop, akLeft, akRight]
           BorderSpacing.Top = 4
           BorderSpacing.Top = 4
           MaxLength = 0
           MaxLength = 0
-          TabOrder = 2
+          TabOrder = 3
         end
         end
         object lblPrivateKey: TLabel
         object lblPrivateKey: TLabel
           AnchorSideLeft.Control = gbSSH
           AnchorSideLeft.Control = gbSSH
@@ -502,7 +513,7 @@ object DialogBox: TDialogBox
           AnchorSideTop.Side = asrBottom
           AnchorSideTop.Side = asrBottom
           Left = 6
           Left = 6
           Height = 15
           Height = 15
-          Top = 98
+          Top = 136
           Width = 122
           Width = 122
           BorderSpacing.Top = 4
           BorderSpacing.Top = 4
           Caption = 'Private key file (*.pem):'
           Caption = 'Private key file (*.pem):'
@@ -516,7 +527,7 @@ object DialogBox: TDialogBox
           AnchorSideRight.Side = asrBottom
           AnchorSideRight.Side = asrBottom
           Left = 6
           Left = 6
           Height = 23
           Height = 23
-          Top = 117
+          Top = 155
           Width = 384
           Width = 384
           FilterIndex = 0
           FilterIndex = 0
           HideDirectories = False
           HideDirectories = False
@@ -525,7 +536,7 @@ object DialogBox: TDialogBox
           Anchors = [akTop, akLeft, akRight]
           Anchors = [akTop, akLeft, akRight]
           BorderSpacing.Top = 4
           BorderSpacing.Top = 4
           MaxLength = 0
           MaxLength = 0
-          TabOrder = 3
+          TabOrder = 4
         end
         end
       end
       end
     end
     end

+ 5 - 0
plugins/wfx/ftp/src/FtpConfDlg.pas

@@ -77,6 +77,7 @@ begin
     begin
     begin
       SendDlgMsg(pDlg, 'chkCopySCP', DM_SETCHECK, 0, 0);
       SendDlgMsg(pDlg, 'chkCopySCP', DM_SETCHECK, 0, 0);
       SendDlgMsg(pDlg, 'chkOnlySCP', DM_SETCHECK, 0, 0);
       SendDlgMsg(pDlg, 'chkOnlySCP', DM_SETCHECK, 0, 0);
+      SendDlgMsg(pDlg, 'chkAgentSSH', DM_SETCHECK, 0, 0);
     end
     end
     else begin
     else begin
       SendDlgMsg(pDlg, 'chkShowHidden', DM_SETCHECK, 0, 0);
       SendDlgMsg(pDlg, 'chkShowHidden', DM_SETCHECK, 0, 0);
@@ -282,6 +283,8 @@ begin
           SendDlgMsg(pDlg, 'chkAutoTLS', DM_SETCHECK, Data, 0);
           SendDlgMsg(pDlg, 'chkAutoTLS', DM_SETCHECK, Data, 0);
           Data:= PtrInt(gConnection.OpenSSH);
           Data:= PtrInt(gConnection.OpenSSH);
           SendDlgMsg(pDlg, 'chkOpenSSH', DM_SETCHECK, Data, 0);
           SendDlgMsg(pDlg, 'chkOpenSSH', DM_SETCHECK, Data, 0);
+          Data:= PtrInt(gConnection.AgentSSH);
+          SendDlgMsg(pDlg, 'chkAgentSSH', DM_SETCHECK, Data, 0);
           Data:= PtrInt(gConnection.CopySCP);
           Data:= PtrInt(gConnection.CopySCP);
           SendDlgMsg(pDlg, 'chkCopySCP', DM_SETCHECK, Data, 0);
           SendDlgMsg(pDlg, 'chkCopySCP', DM_SETCHECK, Data, 0);
           Data:= PtrInt(gConnection.OnlySCP);
           Data:= PtrInt(gConnection.OnlySCP);
@@ -435,6 +438,8 @@ begin
             gConnection.PassiveMode:= Boolean(Data);
             gConnection.PassiveMode:= Boolean(Data);
             Data:= SendDlgMsg(pDlg, 'chkAutoTLS', DM_GETCHECK, 0, 0);
             Data:= SendDlgMsg(pDlg, 'chkAutoTLS', DM_GETCHECK, 0, 0);
             gConnection.AutoTLS:= Boolean(Data);
             gConnection.AutoTLS:= Boolean(Data);
+            Data:= SendDlgMsg(pDlg, 'chkAgentSSH', DM_GETCHECK, 0, 0);
+            gConnection.AgentSSH:= Boolean(Data);
             Data:= SendDlgMsg(pDlg, 'chkCopySCP', DM_GETCHECK, 0, 0);
             Data:= SendDlgMsg(pDlg, 'chkCopySCP', DM_GETCHECK, 0, 0);
             gConnection.CopySCP:= Boolean(Data);
             gConnection.CopySCP:= Boolean(Data);
             Data:= SendDlgMsg(pDlg, 'chkOnlySCP', DM_GETCHECK, 0, 0);
             Data:= SendDlgMsg(pDlg, 'chkOnlySCP', DM_GETCHECK, 0, 0);

+ 6 - 1
plugins/wfx/ftp/src/ftpfunc.pas

@@ -54,6 +54,7 @@ type
     AutoTLS: Boolean;
     AutoTLS: Boolean;
     FullSSL: Boolean;
     FullSSL: Boolean;
     OpenSSH: Boolean;
     OpenSSH: Boolean;
+    AgentSSH: Boolean;
     UseAllocate: Boolean;
     UseAllocate: Boolean;
     Encoding: AnsiString;
     Encoding: AnsiString;
     Fingerprint: AnsiString;
     Fingerprint: AnsiString;
@@ -177,6 +178,7 @@ begin
     Connection.OpenSSH:= IniFile.ReadBool('FTP', 'Connection' + sIndex + 'OpenSSH', False);
     Connection.OpenSSH:= IniFile.ReadBool('FTP', 'Connection' + sIndex + 'OpenSSH', False);
     Connection.OnlySCP:= IniFile.ReadBool('FTP', 'Connection' + sIndex + 'OnlySCP', False);
     Connection.OnlySCP:= IniFile.ReadBool('FTP', 'Connection' + sIndex + 'OnlySCP', False);
     Connection.CopySCP:= IniFile.ReadBool('FTP', 'Connection' + sIndex + 'CopySCP', False);
     Connection.CopySCP:= IniFile.ReadBool('FTP', 'Connection' + sIndex + 'CopySCP', False);
+    Connection.AgentSSH:= IniFile.ReadBool('FTP', 'Connection' + sIndex + 'AgentSSH', False);
     Connection.UseAllocate:= IniFile.ReadBool('FTP', 'Connection' + sIndex + 'UseAllocate', False);
     Connection.UseAllocate:= IniFile.ReadBool('FTP', 'Connection' + sIndex + 'UseAllocate', False);
     Connection.PublicKey := IniFile.ReadString('FTP', 'Connection' + sIndex + 'PublicKey', EmptyStr);
     Connection.PublicKey := IniFile.ReadString('FTP', 'Connection' + sIndex + 'PublicKey', EmptyStr);
     Connection.PrivateKey := IniFile.ReadString('FTP', 'Connection' + sIndex + 'PrivateKey', EmptyStr);
     Connection.PrivateKey := IniFile.ReadString('FTP', 'Connection' + sIndex + 'PrivateKey', EmptyStr);
@@ -221,6 +223,7 @@ begin
     IniFile.WriteBool('FTP', 'Connection' + sIndex + 'OpenSSH', Connection.OpenSSH);
     IniFile.WriteBool('FTP', 'Connection' + sIndex + 'OpenSSH', Connection.OpenSSH);
     IniFile.WriteBool('FTP', 'Connection' + sIndex + 'OnlySCP', Connection.OnlySCP);
     IniFile.WriteBool('FTP', 'Connection' + sIndex + 'OnlySCP', Connection.OnlySCP);
     IniFile.WriteBool('FTP', 'Connection' + sIndex + 'CopySCP', Connection.CopySCP);
     IniFile.WriteBool('FTP', 'Connection' + sIndex + 'CopySCP', Connection.CopySCP);
+    IniFile.WriteBool('FTP', 'Connection' + sIndex + 'AgentSSH', Connection.AgentSSH);
     IniFile.WriteBool('FTP', 'Connection' + sIndex + 'UseAllocate', Connection.UseAllocate);
     IniFile.WriteBool('FTP', 'Connection' + sIndex + 'UseAllocate', Connection.UseAllocate);
     IniFile.WriteString('FTP', 'Connection' + sIndex + 'PublicKey', Connection.PublicKey);
     IniFile.WriteString('FTP', 'Connection' + sIndex + 'PublicKey', Connection.PublicKey);
     IniFile.WriteString('FTP', 'Connection' + sIndex + 'PrivateKey', Connection.PrivateKey);
     IniFile.WriteString('FTP', 'Connection' + sIndex + 'PrivateKey', Connection.PrivateKey);
@@ -352,6 +355,7 @@ begin
           end;
           end;
           FtpSend.PublicKey:= Connection.PublicKey;
           FtpSend.PublicKey:= Connection.PublicKey;
           FtpSend.PrivateKey:= Connection.PrivateKey;
           FtpSend.PrivateKey:= Connection.PrivateKey;
+          TScpSend(FtpSend).Agent:= Connection.AgentSSH;
           TScpSend(FtpSend).Fingerprint:= Connection.Fingerprint;
           TScpSend(FtpSend).Fingerprint:= Connection.Fingerprint;
         end
         end
         else begin
         else begin
@@ -377,7 +381,7 @@ begin
             ZeroPassword(Connection.Password);
             ZeroPassword(Connection.Password);
         end;
         end;
         // if no saved password then ask it
         // if no saved password then ask it
-        if Connection.OpenSSH and (Connection.PrivateKey <> '') and (Connection.PublicKey <> '') then
+        if Connection.OpenSSH and (Connection.AgentSSH or ((Connection.PrivateKey <> '') and (Connection.PublicKey <> ''))) then
           APassword:= EmptyStr
           APassword:= EmptyStr
         else if Length(Connection.Password) > 0 then
         else if Length(Connection.Password) > 0 then
           APassword:= Connection.Password
           APassword:= Connection.Password
@@ -1175,6 +1179,7 @@ begin
   OpenSSH:= Connection.OpenSSH;
   OpenSSH:= Connection.OpenSSH;
   CopySCP:= Connection.CopySCP;
   CopySCP:= Connection.CopySCP;
   OnlySCP:= Connection.OnlySCP;
   OnlySCP:= Connection.OnlySCP;
+  AgentSSH:= Connection.AgentSSH;
   UserName:= Connection.UserName;
   UserName:= Connection.UserName;
   Password:= Connection.Password;
   Password:= Connection.Password;
   Encoding:= Connection.Encoding;
   Encoding:= Connection.Encoding;

+ 28 - 0
plugins/wfx/ftp/src/sftp/libssh.pas

@@ -145,6 +145,17 @@ const
 type
 type
   //* Session API */
   //* Session API */
   PLIBSSH2_SESSION = type Pointer;
   PLIBSSH2_SESSION = type Pointer;
+  //* Agent API */
+  PLIBSSH2_AGENT = type Pointer;
+  libssh2_agent_publickey = record
+    magic: cuint;
+    node: Pointer;
+    blob: PByte;
+    blob_len: csize_t;
+    comment: PAnsiChar;
+  end;
+  Plibssh2_agent_publickey = ^libssh2_agent_publickey;
+  PPlibssh2_agent_publickey = ^Plibssh2_agent_publickey;
   //* Channel API */
   //* Channel API */
   PLIBSSH2_CHANNEL = type Pointer;
   PLIBSSH2_CHANNEL = type Pointer;
   //* SFTP API */
   //* SFTP API */
@@ -237,6 +248,14 @@ var
   libssh2_userauth_publickey_fromfile_ex: function(session: PLIBSSH2_SESSION;
   libssh2_userauth_publickey_fromfile_ex: function(session: PLIBSSH2_SESSION;
                                                    const username: PAnsiChar; username_len: cuint;
                                                    const username: PAnsiChar; username_len: cuint;
                                                    const publickey, privatekey, passphrase: PAnsiChar): cint; cdecl;
                                                    const publickey, privatekey, passphrase: PAnsiChar): cint; cdecl;
+  //* Agent API */
+  libssh2_agent_init: function(session: PLIBSSH2_SESSION): PLIBSSH2_AGENT; cdecl;
+  libssh2_agent_connect: function(agent: PLIBSSH2_AGENT): cint; cdecl;
+  libssh2_agent_list_identities: function(agent: PLIBSSH2_AGENT): cint; cdecl;
+  libssh2_agent_get_identity: function(agent: PLIBSSH2_AGENT; store: PPlibssh2_agent_publickey; prev: Plibssh2_agent_publickey): cint; cdecl;
+  libssh2_agent_userauth: function(agent: PLIBSSH2_AGENT; const username: PAnsiChar; identity: Plibssh2_agent_publickey): cint; cdecl;
+  libssh2_agent_disconnect: function(agent: PLIBSSH2_AGENT): cint; cdecl;
+  libssh2_agent_free: procedure(agent: PLIBSSH2_AGENT); cdecl;
   //* Channel API */
   //* Channel API */
   libssh2_channel_open_ex: function(session: PLIBSSH2_SESSION; const channel_type: PAnsiChar;
   libssh2_channel_open_ex: function(session: PLIBSSH2_SESSION; const channel_type: PAnsiChar;
                           channel_type_len, window_size, packet_size: cuint;
                           channel_type_len, window_size, packet_size: cuint;
@@ -545,6 +564,15 @@ begin
     libssh2_userauth_keyboard_interactive_ex:= SafeGetProcAddress(libssh2, 'libssh2_userauth_keyboard_interactive_ex');
     libssh2_userauth_keyboard_interactive_ex:= SafeGetProcAddress(libssh2, 'libssh2_userauth_keyboard_interactive_ex');
     libssh2_userauth_publickey_fromfile_ex:= SafeGetProcAddress(libssh2, 'libssh2_userauth_publickey_fromfile_ex');
     libssh2_userauth_publickey_fromfile_ex:= SafeGetProcAddress(libssh2, 'libssh2_userauth_publickey_fromfile_ex');
 
 
+    //* Agent API */
+    libssh2_agent_init:= SafeGetProcAddress(libssh2, 'libssh2_agent_init');
+    libssh2_agent_connect:= SafeGetProcAddress(libssh2, 'libssh2_agent_connect');
+    libssh2_agent_list_identities:= SafeGetProcAddress(libssh2, 'libssh2_agent_list_identities');
+    libssh2_agent_get_identity:= SafeGetProcAddress(libssh2, 'libssh2_agent_get_identity');
+    libssh2_agent_userauth:= SafeGetProcAddress(libssh2, 'libssh2_agent_userauth');
+    libssh2_agent_disconnect:= SafeGetProcAddress(libssh2, 'libssh2_agent_disconnect');
+    libssh2_agent_free:= SafeGetProcAddress(libssh2, 'libssh2_agent_free');
+
     //* Channel API */
     //* Channel API */
     libssh2_channel_open_ex:= SafeGetProcAddress(libssh2, 'libssh2_channel_open_ex');
     libssh2_channel_open_ex:= SafeGetProcAddress(libssh2, 'libssh2_channel_open_ex');
     libssh2_channel_free:= SafeGetProcAddress(libssh2, 'libssh2_channel_free');
     libssh2_channel_free:= SafeGetProcAddress(libssh2, 'libssh2_channel_free');

+ 58 - 3
plugins/wfx/ftp/src/sftp/scpsend.pas

@@ -48,6 +48,7 @@ type
   private
   private
     FAnswer: String;
     FAnswer: String;
   protected
   protected
+    FAgent: Boolean;
     FCurrentDir: String;
     FCurrentDir: String;
     FLastError: Integer;
     FLastError: Integer;
     FSavedPassword: Boolean;
     FSavedPassword: Boolean;
@@ -59,6 +60,7 @@ type
     procedure PrintLastError;
     procedure PrintLastError;
     procedure DetectEncoding;
     procedure DetectEncoding;
     function AuthKey: Integer;
     function AuthKey: Integer;
+    function AuthAgent: Integer;
     function Connect: Boolean; override;
     function Connect: Boolean; override;
   public
   public
     constructor Create(const Encoding: String); override;
     constructor Create(const Encoding: String); override;
@@ -85,6 +87,7 @@ type
     function List(Directory: String; NameList: Boolean): Boolean; override;
     function List(Directory: String; NameList: Boolean): Boolean; override;
     function FsSetTime(const FileName: String; LastAccessTime, LastWriteTime: PWfxFileTime): BOOL; override;
     function FsSetTime(const FileName: String; LastAccessTime, LastWriteTime: PWfxFileTime): BOOL; override;
   public
   public
+    property Agent: Boolean read FAgent write FAgent;
     property Fingerprint: AnsiString read FFingerprint write FFingerprint;
     property Fingerprint: AnsiString read FFingerprint write FFingerprint;
   end;
   end;
 
 
@@ -326,6 +329,49 @@ begin
   Result:= FLastError;
   Result:= FLastError;
 end;
 end;
 
 
+function TScpSend.AuthAgent: Integer;
+var
+  agent: PLIBSSH2_AGENT;
+  identity, prev_identity: Plibssh2_agent_publickey;
+begin
+  agent:= libssh2_agent_init(FSession);
+  if (agent = nil) then Exit(-1);
+  try
+    Result:= libssh2_agent_connect(agent);
+    if (Result = LIBSSH2_ERROR_NONE) then
+    try
+      Result:= libssh2_agent_list_identities(agent);
+      if Result < 0 then Exit;
+      prev_identity:= nil;
+
+      while True do
+      begin
+        Result:= libssh2_agent_get_identity(agent, @identity, prev_identity);
+        if (Result < 0) then Exit;
+        if (Result = 1) then Exit(-1);
+
+        repeat
+          FLastError:= libssh2_agent_userauth(agent, PAnsiChar(FUserName), identity);
+        until (FLastError <> LIBSSH2_ERROR_EAGAIN);
+
+        if (FLastError <> 0) then
+        begin
+          DoStatus(False, Format('Authentication with username %s and public key %s failed', [username, identity^.comment]));
+        end
+        else begin
+          DoStatus(False, Format('Authentication with username %s and public key %s succeeded', [username, identity^.comment]));
+          Break;
+        end;
+        prev_identity:= identity;
+      end;
+    finally
+      libssh2_agent_disconnect(agent);
+    end;
+  finally
+    libssh2_agent_free(agent);
+  end;
+end;
+
 function TScpSend.Connect: Boolean;
 function TScpSend.Connect: Boolean;
 const
 const
   HASH_SIZE: array[1..3] of Byte = (16, 20, 32);
   HASH_SIZE: array[1..3] of Byte = (16, 20, 32);
@@ -433,10 +479,18 @@ begin
       begin
       begin
         DoStatus(False, 'Username authentication');
         DoStatus(False, 'Username authentication');
       end
       end
-      else if (strpos(userauthlist, 'publickey') <> nil) and (FPublicKey <> '') and (FPrivateKey <> '') then
+      else if (strpos(userauthlist, 'publickey') <> nil) and (FAgent or ((FPublicKey <> '') and (FPrivateKey <> ''))) then
       begin
       begin
-        DoStatus(False, 'Public key authentication');
-        if (AuthKey < 0) then
+        if FAgent then
+        begin
+          DoStatus(False, 'SSH-agent authentication');
+          FLastError:= AuthAgent;
+        end
+        else begin
+          DoStatus(False, 'Public key authentication');
+          FLastError:= AuthKey;
+        end;
+        if (FLastError < 0) then
         begin
         begin
           PrintLastError;
           PrintLastError;
           Exit(False);
           Exit(False);
@@ -555,6 +609,7 @@ end;
 procedure TScpSend.CloneTo(AValue: TFTPSendEx);
 procedure TScpSend.CloneTo(AValue: TFTPSendEx);
 begin
 begin
   inherited CloneTo(AValue);
   inherited CloneTo(AValue);
+  TScpSend(AValue).FAgent:= FAgent;
   TScpSend(AValue).FPassphrase:= FPassphrase;
   TScpSend(AValue).FPassphrase:= FPassphrase;
   TScpSend(AValue).FFingerprint:= FFingerprint;
   TScpSend(AValue).FFingerprint:= FFingerprint;
 end;
 end;