diff --git a/Duplicati/Library/Backend/SSHv2/Duplicati.Library.Backend.SSHv2.csproj b/Duplicati/Library/Backend/SSHv2/Duplicati.Library.Backend.SSHv2.csproj index c6edf84bd..a69949098 100644 --- a/Duplicati/Library/Backend/SSHv2/Duplicati.Library.Backend.SSHv2.csproj +++ b/Duplicati/Library/Backend/SSHv2/Duplicati.Library.Backend.SSHv2.csproj @@ -11,6 +11,7 @@ + diff --git a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs index 9f5078555..b9ab84717 100644 --- a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs +++ b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs @@ -25,8 +25,9 @@ using Duplicati.Library.Interface; using Duplicati.Library.SourceProvider; using Duplicati.Library.Utility; using Duplicati.Library.Utility.Options; -using Renci.SshNet; -using Renci.SshNet.Common; +using Renci.SshNet; +using Renci.SshNet.Agent; +using Renci.SshNet.Common; using System.Globalization; using System.Runtime.CompilerServices; @@ -41,7 +42,8 @@ namespace Duplicati.Library.Backend private const string SSH_FINGERPRINT_ACCEPT_ANY_OPTION = "ssh-accept-any-fingerprints"; public const string KEYFILE_URI = "sshkey://"; private const string SSH_TIMEOUT_OPTION = "ssh-operation-timeout"; - private const string SSH_KEEPALIVE_OPTION = "ssh-keepalive"; + private const string SSH_KEEPALIVE_OPTION = "ssh-keepalive"; + private const string SSH_DISABLE_AGENT_OPTION = "ssh-disable-agent"; readonly Dictionary m_options; @@ -55,21 +57,23 @@ namespace Duplicati.Library.Backend private readonly TimeSpan m_keepaliveinterval; private readonly int m_port = 22; - private readonly bool m_useRelativePath; - private readonly TimeoutOptionsHelper.Timeouts m_timeouts; + private readonly bool m_useRelativePath; + private readonly bool m_useAgent; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; private string? m_initialDirectory; private SftpClient? m_con; - public SSHv2() - { - m_server = null!; - m_path = null!; - m_username = null!; - m_options = null!; - m_timeouts = null!; - m_con = null!; - } + public SSHv2() + { + m_server = null!; + m_path = null!; + m_username = null!; + m_options = null!; + m_timeouts = null!; + m_con = null!; + m_useAgent = true; + } public SSHv2(string url, Dictionary options) { @@ -85,8 +89,9 @@ namespace Duplicati.Library.Backend m_password = auth.Password; m_fingerprint = options.GetValueOrDefault(SSH_FINGERPRINT_OPTION); m_fingerprintallowall = Utility.Utility.ParseBoolOption(options, SSH_FINGERPRINT_ACCEPT_ANY_OPTION); - m_useRelativePath = Utility.Utility.ParseBoolOption(options, SSH_RELATIVE_PATH_OPTION); - m_timeouts = TimeoutOptionsHelper.Parse(options); + m_useRelativePath = Utility.Utility.ParseBoolOption(options, SSH_RELATIVE_PATH_OPTION); + m_useAgent = !Utility.Utility.ParseBoolOption(options, SSH_DISABLE_AGENT_OPTION); + m_timeouts = TimeoutOptionsHelper.Parse(options); m_path = uri.Path; @@ -191,12 +196,15 @@ namespace Duplicati.Library.Backend new CommandLineArgument(SSH_TIMEOUT_OPTION, CommandLineArgument.ArgumentType.Timespan, Strings.SSHv2Backend.DescriptionSshtimeoutShort, Strings.SSHv2Backend.DescriptionSshtimeoutLong, "0", null,null, Strings.SSHv2Backend.TimeoutDeprecated(SSH_TIMEOUT_OPTION, TimeoutOptionsHelper.ShortTimeoutOption, TimeoutOptionsHelper.ListTimeoutOption, TimeoutOptionsHelper.ReadWriteTimeoutOption)), - new CommandLineArgument(SSH_KEEPALIVE_OPTION, CommandLineArgument.ArgumentType.Timespan, - Strings.SSHv2Backend.DescriptionSshkeepaliveShort, - Strings.SSHv2Backend.DescriptionSshkeepaliveLong, "0"), - new CommandLineArgument(SSH_RELATIVE_PATH_OPTION, CommandLineArgument.ArgumentType.Boolean, - Strings.SSHv2Backend.DescriptionRelativePathShort, - Strings.SSHv2Backend.DescriptionRelativePathLong), + new CommandLineArgument(SSH_KEEPALIVE_OPTION, CommandLineArgument.ArgumentType.Timespan, + Strings.SSHv2Backend.DescriptionSshkeepaliveShort, + Strings.SSHv2Backend.DescriptionSshkeepaliveLong, "0"), + new CommandLineArgument(SSH_DISABLE_AGENT_OPTION, CommandLineArgument.ArgumentType.Boolean, + Strings.SSHv2Backend.DescriptionDisableAgentShort, + Strings.SSHv2Backend.DescriptionDisableAgentLong), + new CommandLineArgument(SSH_RELATIVE_PATH_OPTION, CommandLineArgument.ArgumentType.Boolean, + Strings.SSHv2Backend.DescriptionRelativePathShort, + Strings.SSHv2Backend.DescriptionRelativePathLong), .. TimeoutOptionsHelper.GetOptions(), ]; @@ -293,15 +301,31 @@ namespace Duplicati.Library.Backend return m_con; } - SftpClient con; - - m_options.TryGetValue(SSH_KEYFILE_OPTION, out var keyFile); - m_options.TryGetValue(SSH_KEYFILE_INLINE, out var keyInline); - - if (!string.IsNullOrWhiteSpace(keyFile) || !string.IsNullOrWhiteSpace(keyInline)) - con = new SftpClient(m_server, m_port, m_username, ValidateKeyFile(keyFile, keyInline, m_password)); - else - con = new SftpClient(m_server, m_port, m_username, m_password ?? string.Empty); + SftpClient con; + + m_options.TryGetValue(SSH_KEYFILE_OPTION, out var keyFile); + m_options.TryGetValue(SSH_KEYFILE_INLINE, out var keyInline); + + if (!string.IsNullOrWhiteSpace(keyFile) || !string.IsNullOrWhiteSpace(keyInline)) + { + con = new SftpClient(m_server, m_port, m_username, ValidateKeyFile(keyFile, keyInline, m_password)); + } + else if (!string.IsNullOrWhiteSpace(m_password)) + { + con = new SftpClient(m_server, m_port, m_username, m_password); + } + else if (m_useAgent) + { + var connectionInfo = new ConnectionInfo(m_server, m_port, m_username, new AuthenticationMethod[] + { + new AgentAuthenticationMethod(m_username) + }); + con = new SftpClient(connectionInfo); + } + else + { + con = new SftpClient(m_server, m_port, m_username, string.Empty); + } con.HostKeyReceived += (sender, e) => { diff --git a/Duplicati/Library/Backend/SSHv2/Strings.cs b/Duplicati/Library/Backend/SSHv2/Strings.cs index addd0b93a..b478ae61b 100644 --- a/Duplicati/Library/Backend/SSHv2/Strings.cs +++ b/Duplicati/Library/Backend/SSHv2/Strings.cs @@ -57,9 +57,11 @@ namespace Duplicati.Library.Backend.Strings public static string DescriptionSshkeyShort => LC.L(@"Use a SSH private key to authenticate"); public static string DescriptionSshtimeoutLong => LC.L(@"Use this option to manage the internal timeout for SSH operations. If the value is set to zero, the operations will not time out."); public static string DescriptionSshtimeoutShort => LC.L(@"Set the operation timeout value"); - public static string DescriptionSshkeepaliveLong => LC.L(@"Use this option to enable the keep-alive interval for the SSH connection. If the connection is idle, aggressive firewalls might close the connection. Using keep-alive will keep the connection open in this scenario. If this value is set to zero, the keep-alive is disabled."); - public static string DescriptionSshkeepaliveShort => LC.L(@"Set a keepalive value"); - public static string DescriptionRelativePathShort => LC.L(@"Treat source path as relative to the initial path"); + public static string DescriptionSshkeepaliveLong => LC.L(@"Use this option to enable the keep-alive interval for the SSH connection. If the connection is idle, aggressive firewalls might close the connection. Using keep-alive will keep the connection open in this scenario. If this value is set to zero, the keep-alive is disabled."); + public static string DescriptionSshkeepaliveShort => LC.L(@"Set a keepalive value"); + public static string DescriptionDisableAgentShort => LC.L(@"Disable ssh-agent authentication"); + public static string DescriptionDisableAgentLong => LC.L(@"Prevent using ssh-agent for authentication. When not set and no password or keyfile is supplied, the agent will be used."); + public static string DescriptionRelativePathShort => LC.L(@"Treat source path as relative to the initial path"); public static string DescriptionRelativePathLong => LC.L(@"Use this option to treat the source path as relative to the initial path. This is useful when the full path of the system is not known."); public static string FolderNotFoundManagedError(string foldername, string message) { return LC.L(@"Unable to set folder to {0}, error message: {1}", foldername, message); } public static string FingerprintNotMatchManagedError(string fingerprint) { return LC.L(@"Validation of server fingerprint failed. Server returned fingerprint ""{0}"". Cause of this message is either not correct configuration or Man-in-the-middle attack!", fingerprint); }