2016-04-27 14:48:11 +02:00
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
2019-08-19 19:54:29 -04:00
using Duplicati.Library.Common.IO ;
2019-02-22 21:58:40 -06:00
using Duplicati.Library.Interface ;
2019-02-17 09:52:15 -06:00
using FluentFTP ;
2016-04-27 14:48:11 +02:00
using System ;
using System.Collections.Generic ;
2019-02-17 09:52:15 -06:00
using System.IO ;
2016-04-27 14:48:11 +02:00
using System.Linq ;
using System.Net.Security ;
using System.Security.Authentication ;
2019-02-22 21:58:40 -06:00
using System.Threading ;
using System.Threading.Tasks ;
2016-04-27 14:48:11 +02:00
using CoreUtility = Duplicati . Library . Utility . Utility ;
2019-02-17 09:52:15 -06:00
using Uri = System . Uri ;
2016-04-27 14:48:11 +02:00
namespace Duplicati.Library.Backend.AlternativeFTP
{
// ReSharper disable once RedundantExtendsListEntry
public class AlternativeFtpBackend : IBackend , IStreamingBackend
{
private System . Net . NetworkCredential _userInfo ;
private const string OPTION_ACCEPT_SPECIFIED_CERTIFICATE = "accept-specified-ssl-hash" ; // Global option
private const string OPTION_ACCEPT_ANY_CERTIFICATE = "accept-any-ssl-certificate" ; // Global option
private const FtpDataConnectionType DEFAULT_DATA_CONNECTION_TYPE = FtpDataConnectionType . AutoPassive ;
private const FtpEncryptionMode DEFAULT_ENCRYPTION_MODE = FtpEncryptionMode . None ;
private const SslProtocols DEFAULT_SSL_PROTOCOLS = SslProtocols . Default ;
private const string CONFIG_KEY_AFTP_ENCRYPTION_MODE = "aftp-encryption-mode" ;
private const string CONFIG_KEY_AFTP_DATA_CONNECTION_TYPE = "aftp-data-connection-type" ;
private const string CONFIG_KEY_AFTP_SSL_PROTOCOLS = "aftp-ssl-protocols" ;
2019-10-21 08:29:34 +02:00
private const string CONFIG_KEY_AFTP_UPLOAD_DELAY = "aftp-upload-delay" ;
2016-04-27 14:48:11 +02:00
2017-05-24 01:39:30 +07:00
private const string TEST_FILE_NAME = "duplicati-access-privileges-test.tmp" ;
2019-10-21 08:29:34 +02:00
private const string TEST_FILE_CONTENT = "This file is used by Duplicati to test access permissions and can be safely deleted." ;
2017-05-24 01:39:30 +07:00
2016-04-27 14:48:11 +02:00
// ReSharper disable InconsistentNaming
private static readonly string DEFAULT_DATA_CONNECTION_TYPE_STRING = DEFAULT_DATA_CONNECTION_TYPE . ToString ();
private static readonly string DEFAULT_ENCRYPTION_MODE_STRING = DEFAULT_ENCRYPTION_MODE . ToString ();
private static readonly string DEFAULT_SSL_PROTOCOLS_STRING = DEFAULT_SSL_PROTOCOLS . ToString ();
2019-10-21 08:29:34 +02:00
private static readonly string DEFAULT_UPLOAD_DELAY_STRING = "0s" ;
2016-04-27 14:48:11 +02:00
// ReSharper restore InconsistentNaming
private readonly string _url ;
private readonly bool _listVerify = true ;
private readonly FtpEncryptionMode _encryptionMode ;
private readonly FtpDataConnectionType _dataConnectionType ;
private readonly SslProtocols _sslProtocols ;
2019-10-21 08:29:34 +02:00
private readonly TimeSpan _uploadWaitTime ;
2016-04-27 14:48:11 +02:00
private readonly byte [] _copybuffer = new byte [ CoreUtility . DEFAULT_BUFFER_SIZE ];
private readonly bool _accepAllCertificates ;
private readonly string [] _validHashes ;
/// <summary>
/// The localized name to display for this backend
/// </summary>
public string DisplayName
{
get { return Strings . DisplayName ; }
}
/// <summary>
/// The protocol key, eg. ftp, http or ssh
/// </summary>
public string ProtocolKey
{
get { return "aftp" ; }
}
2016-12-29 15:00:19 +01:00
private FtpClient Client
{ get ; set ; }
2016-04-27 14:48:11 +02:00
public IList < ICommandLineArgument > SupportedCommands
{
get
{
return new List < ICommandLineArgument >( new ICommandLineArgument [] {
2017-05-24 01:39:30 +07:00
new CommandLineArgument ( "auth-password" , CommandLineArgument . ArgumentType . Password , Strings . DescriptionAuthPasswordShort , Strings . DescriptionAuthPasswordLong ),
new CommandLineArgument ( "auth-username" , CommandLineArgument . ArgumentType . String , Strings . DescriptionAuthUsernameShort , Strings . DescriptionAuthUsernameLong ),
new CommandLineArgument ( "disable-upload-verify" , CommandLineArgument . ArgumentType . Boolean , Strings . DescriptionDisableUploadVerifyShort , Strings . DescriptionDisableUploadVerifyLong ),
new CommandLineArgument ( CONFIG_KEY_AFTP_DATA_CONNECTION_TYPE , CommandLineArgument . ArgumentType . Enumeration , Strings . DescriptionFtpDataConnectionTypeShort , Strings . DescriptionFtpDataConnectionTypeLong , DEFAULT_DATA_CONNECTION_TYPE_STRING , null , Enum . GetNames ( typeof ( FtpDataConnectionType ))),
new CommandLineArgument ( CONFIG_KEY_AFTP_ENCRYPTION_MODE , CommandLineArgument . ArgumentType . Enumeration , Strings . DescriptionFtpEncryptionModeShort , Strings . DescriptionFtpEncryptionModeLong , DEFAULT_ENCRYPTION_MODE_STRING , null , Enum . GetNames ( typeof ( FtpEncryptionMode ))),
new CommandLineArgument ( CONFIG_KEY_AFTP_SSL_PROTOCOLS , CommandLineArgument . ArgumentType . Flags , Strings . DescriptionSslProtocolsShort , Strings . DescriptionSslProtocolsLong , DEFAULT_SSL_PROTOCOLS_STRING , null , Enum . GetNames ( typeof ( SslProtocols ))),
2019-10-21 08:29:34 +02:00
new CommandLineArgument ( CONFIG_KEY_AFTP_UPLOAD_DELAY , CommandLineArgument . ArgumentType . Timespan , Strings . DescriptionUploadDelayShort , Strings . DescriptionUploadDelayLong , DEFAULT_UPLOAD_DELAY_STRING ),
2017-05-24 01:39:30 +07:00
});
2016-04-27 14:48:11 +02:00
}
}
/// <summary>
/// Initialize a new instance.
/// </summary>
public AlternativeFtpBackend ()
{
}
/// <summary>
/// Initialize a new instance/
/// </summary>
/// <param name="url">Configured url.</param>
/// <param name="options">Configured options. cannot be null.</param>
public AlternativeFtpBackend ( string url , Dictionary < string , string > options )
{
_accepAllCertificates = CoreUtility . ParseBoolOption ( options , OPTION_ACCEPT_ANY_CERTIFICATE );
string certHash ;
options . TryGetValue ( OPTION_ACCEPT_SPECIFIED_CERTIFICATE , out certHash );
_validHashes = certHash == null ? null : certHash . Split ( new [] { "," , ";" }, StringSplitOptions . RemoveEmptyEntries );
var u = new Utility . Uri ( url );
u . RequireHost ();
if (! string . IsNullOrEmpty ( u . Username ))
{
_userInfo = new System . Net . NetworkCredential ();
_userInfo . UserName = u . Username ;
if (! string . IsNullOrEmpty ( u . Password ))
_userInfo . Password = u . Password ;
else if ( options . ContainsKey ( "auth-password" ))
_userInfo . Password = options [ "auth-password" ];
}
else
{
if ( options . ContainsKey ( "auth-username" ))
{
_userInfo = new System . Net . NetworkCredential ();
_userInfo . UserName = options [ "auth-username" ];
if ( options . ContainsKey ( "auth-password" ))
_userInfo . Password = options [ "auth-password" ];
}
}
//Bugfix, see http://connect.microsoft.com/VisualStudio/feedback/details/695227/networkcredential-default-constructor-leaves-domain-null-leading-to-null-object-reference-exceptions-in-framework-code
if ( _userInfo != null )
_userInfo . Domain = "" ;
_url = u . SetScheme ( "ftp" ). SetQuery ( null ). SetCredentials ( null , null ). ToString ();
2018-11-02 21:34:07 +01:00
_url = Common . IO . Util . AppendDirSeparator ( _url , "/" );
2016-04-27 14:48:11 +02:00
_listVerify = ! CoreUtility . ParseBoolOption ( options , "disable-upload-verify" );
2019-10-21 08:29:34 +02:00
if ( options . TryGetValue ( CONFIG_KEY_AFTP_UPLOAD_DELAY , out var uploadWaitTimeString ) && ! string . IsNullOrWhiteSpace ( uploadWaitTimeString ))
_uploadWaitTime = Duplicati . Library . Utility . Timeparser . ParseTimeSpan ( uploadWaitTimeString );
2016-04-27 14:48:11 +02:00
// Process the aftp-data-connection-type option
string dataConnectionTypeString ;
if (! options . TryGetValue ( CONFIG_KEY_AFTP_DATA_CONNECTION_TYPE , out dataConnectionTypeString ) || string . IsNullOrWhiteSpace ( dataConnectionTypeString ))
{
dataConnectionTypeString = null ;
}
if ( dataConnectionTypeString == null || ! Enum . TryParse ( dataConnectionTypeString , true , out _dataConnectionType ))
{
_dataConnectionType = DEFAULT_DATA_CONNECTION_TYPE ;
}
// Process the aftp-encryption-mode option
string encryptionModeString ;
if (! options . TryGetValue ( CONFIG_KEY_AFTP_ENCRYPTION_MODE , out encryptionModeString ) || string . IsNullOrWhiteSpace ( encryptionModeString ))
{
encryptionModeString = null ;
}
if ( encryptionModeString == null || ! Enum . TryParse ( encryptionModeString , true , out _encryptionMode ))
{
_encryptionMode = DEFAULT_ENCRYPTION_MODE ;
}
// Process the aftp-ssl-protocols option
string sslProtocolsString ;
2016-09-15 11:39:27 +02:00
if (! options . TryGetValue ( CONFIG_KEY_AFTP_SSL_PROTOCOLS , out sslProtocolsString ) || string . IsNullOrWhiteSpace ( sslProtocolsString ))
2016-04-27 14:48:11 +02:00
{
sslProtocolsString = null ;
}
if ( sslProtocolsString == null || ! Enum . TryParse ( sslProtocolsString , true , out _sslProtocols ))
{
_sslProtocols = DEFAULT_SSL_PROTOCOLS ;
}
}
2017-09-25 23:17:45 -06:00
public IEnumerable < IFileEntry > List ()
2016-04-27 14:48:11 +02:00
{
return List ( "" );
}
2017-09-25 23:17:45 -06:00
public IEnumerable < IFileEntry > List ( string filename )
2016-04-27 14:48:11 +02:00
{
return List ( filename , false );
}
2017-09-25 23:17:45 -06:00
private IEnumerable < IFileEntry > List ( string filename , bool stripFile )
2016-04-27 14:48:11 +02:00
{
var list = new List < IFileEntry >();
string remotePath = filename ;
try
{
2016-12-29 21:20:12 +01:00
var ftpClient = CreateClient ();
// Get the remote path
var url = new Uri ( this . _url );
2017-11-26 10:53:14 -08:00
remotePath = "/" + ( url . AbsolutePath . EndsWith ( "/" , StringComparison . Ordinal ) ? url . AbsolutePath . Substring ( 0 , url . AbsolutePath . Length - 1 ) : url . AbsolutePath );
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
if (! string . IsNullOrEmpty ( filename ))
{
if (! stripFile )
2016-04-27 14:48:11 +02:00
{
2016-12-29 21:20:12 +01:00
// Append the filename
remotePath += filename ;
}
else if ( filename . Contains ( "/" ))
{
2017-09-18 23:23:45 -06:00
remotePath += filename . Substring ( 0 , filename . LastIndexOf ( "/" , StringComparison . Ordinal ));
2016-04-27 14:48:11 +02:00
}
2016-12-29 21:20:12 +01:00
// else: stripping the filename in this case ignoring it
}
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
foreach ( FtpListItem item in ftpClient . GetListing ( remotePath , FtpListOption . Modify | FtpListOption . Size | FtpListOption . DerefLinks ))
{
switch ( item . Type )
2016-04-27 14:48:11 +02:00
{
2016-12-29 21:20:12 +01:00
case FtpFileSystemObjectType . Directory :
2016-04-27 14:48:11 +02:00
{
if ( item . Name == "." || item . Name == ".." )
{
continue ;
}
list . Add ( new FileEntry ( item . Name , - 1 , new DateTime (), item . Modified )
{
IsFolder = true ,
});
break ;
}
2016-12-29 21:20:12 +01:00
case FtpFileSystemObjectType . File :
2016-04-27 14:48:11 +02:00
{
list . Add ( new FileEntry ( item . Name , item . Size , new DateTime (), item . Modified ));
break ;
}
2016-12-29 21:20:12 +01:00
case FtpFileSystemObjectType . Link :
2016-04-27 14:48:11 +02:00
{
if ( item . Name == "." || item . Name == ".." )
{
continue ;
}
if ( item . LinkObject != null )
{
switch ( item . LinkObject . Type )
{
case FtpFileSystemObjectType . Directory :
{
2016-12-29 21:20:12 +01:00
if ( item . Name == "." || item . Name == ".." )
{
continue ;
}
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
list . Add ( new FileEntry ( item . Name , - 1 , new DateTime (), item . Modified )
{
IsFolder = true ,
});
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
break ;
}
2016-04-27 14:48:11 +02:00
case FtpFileSystemObjectType . File :
2016-12-29 21:20:12 +01:00
{
list . Add ( new FileEntry ( item . Name , item . Size , new DateTime (), item . Modified ));
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
break ;
}
2016-04-27 14:48:11 +02:00
}
}
break ;
}
2016-12-29 21:20:12 +01:00
2016-04-27 14:48:11 +02:00
}
}
2016-09-15 11:39:27 +02:00
} // Message "Directory not found." string
2016-04-27 14:48:11 +02:00
catch ( FtpCommandException ex )
{
if ( ex . Message == "Directory not found." )
{
throw new FolderMissingException ( Strings . MissingFolderError ( remotePath , ex . Message ), ex );
}
throw ;
}
return list ;
}
2019-03-17 18:20:14 -05:00
public async Task PutAsync ( string remotename , Stream input , CancellationToken cancelToken )
2016-04-27 14:48:11 +02:00
{
string remotePath = remotename ;
2019-03-07 18:16:39 -06:00
long streamLen ;
2016-04-27 14:48:11 +02:00
try
{
2016-12-29 21:20:12 +01:00
var ftpClient = CreateClient ();
try
{
streamLen = input . Length ;
}
2019-03-07 18:16:39 -06:00
catch ( NotSupportedException ) { streamLen = - 1 ; }
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
// Get the remote path
remotePath = "" ;
if (! string . IsNullOrEmpty ( remotename ))
{
// Append the filename
remotePath += remotename ;
}
2016-04-27 14:48:11 +02:00
2019-02-17 09:52:15 -06:00
var success = await ftpClient . UploadAsync ( input , remotePath , FtpExists . Overwrite , createRemoteDir : false , token : cancelToken , progress : null ). ConfigureAwait ( false );
if (! success )
2016-12-29 21:20:12 +01:00
{
2019-02-17 09:52:15 -06:00
throw new UserInformationException ( string . Format ( Strings . ErrorWriteFile , remotename ), "AftpPutFailure" );
2016-04-27 14:48:11 +02:00
}
2019-10-21 08:29:34 +02:00
// Wait for the upload, if required
if ( _uploadWaitTime . Ticks > 0 )
2016-04-27 14:48:11 +02:00
{
2019-10-21 08:29:34 +02:00
Thread . Sleep ( _uploadWaitTime );
}
if ( _listVerify )
{
// check remote file size; matching file size indicates completion
2019-10-22 22:52:14 +02:00
var remoteSize = await ftpClient . GetFileSizeAsync ( remotePath , cancelToken );
2019-10-22 10:54:13 +02:00
if ( streamLen != remoteSize )
2016-04-27 14:48:11 +02:00
{
2019-10-22 10:54:13 +02:00
throw new UserInformationException ( Strings . ListVerifySizeFailure ( remotename , remoteSize , streamLen ), "AftpListVerifySizeFailure" );
2016-04-27 14:48:11 +02:00
}
2019-10-21 08:29:34 +02:00
}
2016-04-27 14:48:11 +02:00
}
catch ( FtpCommandException ex )
{
if ( ex . Message == "Directory not found." )
{
throw new FolderMissingException ( Strings . MissingFolderError ( remotePath , ex . Message ), ex );
}
throw ;
}
}
2019-03-17 18:20:14 -05:00
public Task PutAsync ( string remotename , string localname , CancellationToken cancelToken )
2016-04-27 14:48:11 +02:00
{
2019-02-17 09:52:15 -06:00
using ( FileStream fs = File . Open ( localname , FileMode . Open , FileAccess . Read , FileShare . Read ))
2016-04-27 14:48:11 +02:00
{
2019-03-17 18:20:14 -05:00
return PutAsync ( remotename , fs , cancelToken );
2016-04-27 14:48:11 +02:00
}
}
2019-02-17 09:52:15 -06:00
public void Get ( string remotename , Stream output )
2016-04-27 14:48:11 +02:00
{
2016-12-29 21:20:12 +01:00
var ftpClient = CreateClient ();
// Get the remote path
var remotePath = "" ;
if (! string . IsNullOrEmpty ( remotename ))
2016-04-27 14:48:11 +02:00
{
2016-12-29 21:20:12 +01:00
// Append the filename
remotePath += remotename ;
}
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
using ( var inputStream = ftpClient . OpenRead ( remotePath ))
{
try
2016-04-27 14:48:11 +02:00
{
2016-12-29 21:20:12 +01:00
CoreUtility . CopyStream ( inputStream , output , false , _copybuffer );
2016-04-27 14:48:11 +02:00
}
2016-12-29 21:20:12 +01:00
finally
2016-04-27 14:48:11 +02:00
{
2016-12-29 21:20:12 +01:00
inputStream . Close ();
2016-04-27 14:48:11 +02:00
}
}
2016-12-29 21:20:12 +01:00
2016-04-27 14:48:11 +02:00
}
public void Get ( string remotename , string localname )
{
2019-02-17 09:52:15 -06:00
using ( FileStream fs = File . Open ( localname , FileMode . Create , FileAccess . Write , FileShare . None ))
2016-04-27 14:48:11 +02:00
{
Get ( remotename , fs );
}
}
public void Delete ( string remotename )
{
2016-12-29 21:20:12 +01:00
var ftpClient = CreateClient ();
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
// Get the remote path
var remotePath = "" ;
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
if (! string . IsNullOrEmpty ( remotename ))
{
// Append the filename
remotePath += remotename ;
2016-04-27 14:48:11 +02:00
}
2016-12-29 21:20:12 +01:00
ftpClient . DeleteFile ( remotePath );
2016-04-27 14:48:11 +02:00
}
/// <summary>
/// A localized description of the backend, for display in the usage information
/// </summary>
public string Description
{
get
{
return Strings . Description ;
}
}
2018-02-26 11:37:10 +01:00
public string [] DNSName
2018-02-21 22:38:01 +01:00
{
2018-02-26 11:37:10 +01:00
get { return new string [] { new Uri ( _url ). Host }; }
2018-02-21 22:38:01 +01:00
}
2018-02-18 00:18:44 +01:00
2019-02-17 09:52:15 -06:00
private static Stream StringToStream ( string str )
2017-05-24 01:39:30 +07:00
{
2019-02-17 09:52:15 -06:00
var stream = new MemoryStream ();
var writer = new StreamWriter ( stream ) { AutoFlush = true };
2017-05-24 01:39:30 +07:00
writer . Write ( str );
return stream ;
}
/// <summary>
/// Test FTP access permissions.
/// </summary>
2016-04-27 14:48:11 +02:00
public void Test ()
{
2017-05-24 01:39:30 +07:00
var list = List ();
// Delete test file if exists
if ( list . Any ( entry => entry . Name == TEST_FILE_NAME ))
{
try
{
Delete ( TEST_FILE_NAME );
}
catch ( Exception e )
{
throw new Exception ( string . Format ( Strings . ErrorDeleteFile , e . Message ), e );
}
}
// Test write permissions
using ( var testStream = StringToStream ( TEST_FILE_CONTENT ))
{
try
{
2019-03-17 18:20:14 -05:00
PutAsync ( TEST_FILE_NAME , testStream , CancellationToken . None ). Wait ();
2017-05-24 01:39:30 +07:00
}
catch ( Exception e )
{
throw new Exception ( string . Format ( Strings . ErrorWriteFile , e . Message ), e );
}
}
// Test read permissions
2019-02-17 09:52:15 -06:00
using ( var stream = new MemoryStream ())
2017-05-24 01:39:30 +07:00
{
try
{
Get ( TEST_FILE_NAME , stream );
}
catch ( Exception e )
{
throw new Exception ( string . Format ( Strings . ErrorReadFile , e . Message ), e );
}
}
// Cleanup
try
{
Delete ( TEST_FILE_NAME );
}
catch ( Exception e )
{
throw new Exception ( string . Format ( Strings . ErrorDeleteFile , e . Message ), e );
}
2016-04-27 14:48:11 +02:00
}
public void CreateFolder ()
{
2016-12-29 21:20:12 +01:00
var client = CreateClient ();
2016-04-27 14:48:11 +02:00
2016-12-29 21:20:12 +01:00
var url = new Uri ( _url );
// Get the remote path
2017-11-26 10:53:14 -08:00
var remotePath = url . AbsolutePath . EndsWith ( "/" , StringComparison . Ordinal ) ? url . AbsolutePath . Substring ( 0 , url . AbsolutePath . Length - 1 ) : url . AbsolutePath ;
2016-12-29 21:20:12 +01:00
// Try to create the directory
client . CreateDirectory ( remotePath , true );
2016-04-27 14:48:11 +02:00
}
public void Dispose ()
{
2017-05-24 01:39:30 +07:00
if ( Client != null )
2016-12-29 21:20:12 +01:00
Client . Dispose ();
Client = null ;
2016-04-27 14:48:11 +02:00
_userInfo = null ;
}
private FtpClient CreateClient ()
{
2019-02-17 09:52:15 -06:00
var uri = new Uri ( _url );
2016-12-29 15:00:19 +01:00
2019-01-12 23:32:34 +01:00
if ( this . Client == null ) // Create connection if it doesn't exist yet
{
2016-12-29 15:00:19 +01:00
var ftpClient = new FtpClient
{
Host = uri . Host ,
Port = uri . Port == - 1 ? 21 : uri . Port ,
Credentials = _userInfo ,
EncryptionMode = _encryptionMode ,
DataConnectionType = _dataConnectionType ,
SslProtocols = _sslProtocols ,
2019-10-22 10:54:13 +02:00
// We do not support parallel uploads, and the feature is buggy
EnableThreadSafeDataConnections = false ,
2016-12-29 15:00:19 +01:00
};
ftpClient . ValidateCertificate += HandleValidateCertificate ;
this . Client = ftpClient ;
} // else reuse existing connection
2019-01-12 23:32:34 +01:00
// Change working directory to the remote path
// Do this every time to prevent issues when FtpClient silently reconnects after failure.
var remotePath = uri . AbsolutePath . EndsWith ( "/" , StringComparison . Ordinal ) ? uri . AbsolutePath . Substring ( 0 , uri . AbsolutePath . Length - 1 ) : uri . AbsolutePath ;
this . Client . SetWorkingDirectory ( remotePath );
2016-12-29 15:00:19 +01:00
return this . Client ;
2016-04-27 14:48:11 +02:00
}
private void HandleValidateCertificate ( FtpClient control , FtpSslValidationEventArgs e )
{
if ( e . PolicyErrors == SslPolicyErrors . None || _accepAllCertificates )
{
e . Accept = true ;
return ;
}
try
{
var certHash = ( _validHashes != null && _validHashes . Length > 0 ) ? CoreUtility . ByteArrayAsHexString ( e . Certificate . GetCertHash ()) : null ;
if ( certHash != null )
{
2017-09-18 23:23:45 -06:00
if ( _validHashes . Any ( hash => ! string . IsNullOrEmpty ( hash ) && certHash . Equals ( hash , StringComparison . OrdinalIgnoreCase )))
2016-04-27 14:48:11 +02:00
{
e . Accept = true ;
}
}
}
catch
{
e . Accept = false ;
}
}
}
}