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); }