2019-08-27 16:30:25 +02:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using BTCPayServer.SSH;
|
|
|
|
|
using Renci.SshNet;
|
|
|
|
|
|
|
|
|
|
namespace BTCPayServer
|
|
|
|
|
{
|
|
|
|
|
public static class SSHClientExtensions
|
|
|
|
|
{
|
2019-10-21 09:34:26 +02:00
|
|
|
|
public static async Task<SshClient> ConnectAsync(this SSHSettings sshSettings, CancellationToken cancellationToken = default)
|
2019-08-27 16:30:25 +02:00
|
|
|
|
{
|
|
|
|
|
if (sshSettings == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(sshSettings));
|
|
|
|
|
TaskCompletionSource<SshClient> tcs = new TaskCompletionSource<SshClient>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
|
new Thread(() =>
|
|
|
|
|
{
|
2019-10-21 09:34:26 +02:00
|
|
|
|
SshClient sshClient = null;
|
2019-08-27 16:30:25 +02:00
|
|
|
|
try
|
|
|
|
|
{
|
2019-10-21 09:34:26 +02:00
|
|
|
|
sshClient = new SshClient(sshSettings.CreateConnectionInfo());
|
|
|
|
|
sshClient.HostKeyReceived += (object sender, Renci.SshNet.Common.HostKeyEventArgs e) =>
|
|
|
|
|
{
|
|
|
|
|
if (sshSettings.TrustedFingerprints.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
e.CanTrust = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
e.CanTrust = sshSettings.IsTrustedFingerprint(e.FingerPrint, e.HostKey);
|
|
|
|
|
}
|
|
|
|
|
};
|
2019-08-27 16:30:25 +02:00
|
|
|
|
sshClient.Connect();
|
|
|
|
|
tcs.TrySetResult(sshClient);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
tcs.TrySetException(ex);
|
|
|
|
|
try
|
|
|
|
|
{
|
2019-10-21 09:34:26 +02:00
|
|
|
|
sshClient?.Dispose();
|
2019-08-27 16:30:25 +02:00
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
{ IsBackground = true }.Start();
|
2019-10-21 09:34:26 +02:00
|
|
|
|
|
|
|
|
|
using (cancellationToken.Register(() => { tcs.TrySetCanceled(); }))
|
|
|
|
|
{
|
|
|
|
|
return await tcs.Task;
|
|
|
|
|
}
|
2019-08-27 16:30:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-19 12:17:20 +02:00
|
|
|
|
public static string EscapeSingleQuotes(this string command)
|
|
|
|
|
{
|
|
|
|
|
return command.Replace("'", "'\"'\"'", StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-27 16:30:25 +02:00
|
|
|
|
public static Task<SSHCommandResult> RunBash(this SshClient sshClient, string command, TimeSpan? timeout = null)
|
|
|
|
|
{
|
|
|
|
|
if (sshClient == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(sshClient));
|
|
|
|
|
if (command == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(command));
|
2019-09-19 12:17:20 +02:00
|
|
|
|
command = $"bash -c '{command.EscapeSingleQuotes()}'";
|
2019-08-27 16:30:25 +02:00
|
|
|
|
var sshCommand = sshClient.CreateCommand(command);
|
|
|
|
|
if (timeout is TimeSpan v)
|
|
|
|
|
sshCommand.CommandTimeout = v;
|
|
|
|
|
var tcs = new TaskCompletionSource<SSHCommandResult>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
|
new Thread(() =>
|
|
|
|
|
{
|
2019-11-18 09:15:40 +01:00
|
|
|
|
try
|
2019-08-27 16:30:25 +02:00
|
|
|
|
{
|
2019-11-18 09:15:40 +01:00
|
|
|
|
sshCommand.BeginExecute(ar =>
|
2019-08-27 16:30:25 +02:00
|
|
|
|
{
|
2019-11-18 09:15:40 +01:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sshCommand.EndExecute(ar);
|
|
|
|
|
tcs.TrySetResult(CreateSSHCommandResult(sshCommand));
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
tcs.TrySetException(ex);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
sshCommand.Dispose();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch(Exception ex) { tcs.TrySetException(ex); }
|
2019-08-27 16:30:25 +02:00
|
|
|
|
})
|
|
|
|
|
{ IsBackground = true }.Start();
|
|
|
|
|
return tcs.Task;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static SSHCommandResult CreateSSHCommandResult(SshCommand sshCommand)
|
|
|
|
|
{
|
|
|
|
|
return new SSHCommandResult()
|
|
|
|
|
{
|
|
|
|
|
Output = sshCommand.Result,
|
|
|
|
|
Error = sshCommand.Error,
|
|
|
|
|
ExitStatus = sshCommand.ExitStatus
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-21 11:43:53 +02:00
|
|
|
|
public static async Task DisconnectAsync(this SshClient sshClient, CancellationToken cancellationToken = default)
|
2019-08-27 16:30:25 +02:00
|
|
|
|
{
|
|
|
|
|
if (sshClient == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(sshClient));
|
|
|
|
|
|
|
|
|
|
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
|
new Thread(() =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sshClient.Disconnect();
|
|
|
|
|
tcs.TrySetResult(true);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
tcs.TrySetResult(true); // We don't care about exception
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
{ IsBackground = true }.Start();
|
2019-10-21 11:43:53 +02:00
|
|
|
|
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
|
|
|
|
|
{
|
|
|
|
|
await tcs.Task;
|
|
|
|
|
}
|
2019-08-27 16:30:25 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|