Decompiled source of LethalSSH v0.1.1
baer1.LethalSSH.dll
Decompiled 2 months agousing System; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Renci.SshNet; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("baer1.LethalSSH")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1+27f95d17cffb6ce84f8b76af23974495ea207269")] [assembly: AssemblyProduct("LethalSsh")] [assembly: AssemblyTitle("baer1.LethalSSH")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LethalSsh { [BepInPlugin("baer1.LethalSSH", "LethalSsh", "0.1.1")] public class LethalSsh : BaseUnityPlugin { [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] public class ParsePlayerSentencePatch { private static bool Prefix(ref Terminal __instance, ref TerminalNode __result) { //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Expected O, but got Unknown //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Expected O, but got Unknown string text = __instance.screenText.text.Substring(__instance.screenText.text.Length - __instance.textAdded); Logger.LogDebug((object)(">> ParsePlayerSentence " + text)); string[] array = text.Split(" "); if (Instance.Active && Instance.sshShell != null && ((Stream)(object)Instance.sshShell).CanWrite) { Instance.sshShell.Write(text + "\n"); return false; } if (array[0] == "ssh") { __result = ScriptableObject.CreateInstance<TerminalNode>(); __result.clearPreviousText = true; switch (array.Length) { case 4: { string[] array2 = array[1].Split("@"); if (array2.Length != 2) { __result.displayText = "Invalid arguments"; break; } int result = 22; int.TryParse(array[2], out result); Instance.sshClient = new SshClient(array2[1], result, array2[0], array[3]); ((BaseClient)Instance.sshClient).Connect(); Instance.sshShell = Instance.sshClient.CreateShellStream("linux", 20u, 20u, 800u, 600u, 1024); __result.displayText = ""; break; } case 3: { string[] array2 = array[1].Split("@"); if (array2.Length != 2) { __result.displayText = "Invalid arguments"; break; } Instance.sshClient = new SshClient(array2[1], array2[0], array[2]); ((BaseClient)Instance.sshClient).Connect(); Instance.sshShell = Instance.sshClient.CreateShellStream("linux", 50u, 50u, 800u, 600u, 1024); __result.displayText = ""; break; } default: __result.displayText = "Invalid arguments\n"; break; } Instance._sgrCloseTags.Clear(); Instance._sgrDim = false; return false; } return true; } } [HarmonyPatch(typeof(Terminal), "TextChanged")] public class TextChangedPatch { private static bool Prefix(ref Terminal __instance, string newText) { Logger.LogDebug((object)(">> TextChanged " + newText.Split("\n").Last())); if (!Instance.Active) { return true; } if (__instance.modifyingText) { __instance.modifyingText = false; return false; } Terminal obj = __instance; obj.textAdded += newText.Length - __instance.currentText.Length; if (__instance.textAdded < 0) { __instance.screenText.text = __instance.currentText; __instance.textAdded = 0; } else { __instance.currentText = newText; } __instance.forceScrollbarCoroutine = ((MonoBehaviour)__instance).StartCoroutine(__instance.forceScrollbarDown()); return false; } } [HarmonyPatch(typeof(Terminal), "QuitTerminal")] public class QuitTerminalPatch { private static void Prefix() { Logger.LogDebug((object)">> QuitTerminal"); Instance.ResetSSH(); } } [HarmonyPatch(typeof(GameNetworkManager), "StartDisconnect")] public class StartDisconnectPatch { private static void Prefix() { Logger.LogDebug((object)">> StartDisconnect"); Instance.ResetSSH(); } } [HarmonyPatch(typeof(Terminal), "Update")] public class TerminalUpdatePatch { private static void Prefix(ref Terminal __instance) { if (!Instance.Active || Instance.sshShell == null || !((Stream)(object)Instance.sshShell).CanRead) { return; } string text = Instance.sshShell.Read(); if (text.Length == 0) { return; } string text2 = Regex.Split(GeneralExtensions.Join<string>((__instance.currentText + text).Split("\n").TakeLast(50), (Func<string, string>)null, "\n"), "\\x1B\\[[0-3]?[jJ]").Last(); __instance.currentText = Regex.Replace(enableANSI.Value ? Regex.Replace(text2, "\\x1B\\[([0-9;]*)[mM]", delegate(Match m) { StringBuilder sb = new StringBuilder(); string[] array = m.Groups[1].Value.Split(";"); Func<string, string, bool> next = null; string text3 = null; string[] array2 = array; foreach (string text4 in array2) { Logger.LogDebug((object)(">> Parsing ANSI code " + text4)); if (next != null) { Logger.LogDebug((object)("Found next function, previous: " + text3)); if (!next(text3, text4)) { text3 = text4; Logger.LogDebug((object)("previous overwritten " + text3)); } else { Logger.LogDebug((object)("previous not overwritten: " + text3)); } } else { switch (text4) { case "1": Instance._sgrCloseTags.Insert(0, "</b>"); sb.Append("<b>"); break; case "2": Instance._sgrCloseTags.Insert(0, "<alpha=#FF>"); Instance._sgrDim = true; sb.Append("<alpha=#33>"); break; case "3": Instance._sgrCloseTags.Insert(0, "</i>"); sb.Append("<i>"); break; case "4": Instance._sgrCloseTags.Insert(0, "</u>"); sb.Append("<u>"); break; case "9": Instance._sgrCloseTags.Insert(0, "</s>"); sb.Append("<s>"); break; case "30": case "31": case "32": case "33": case "34": case "35": case "36": case "37": case "90": case "91": case "92": case "93": case "94": case "95": case "96": case "97": Instance._sgrCloseTags.Insert(0, "</color>"); sb.Append("<color=#" + GetColorByCode(text4) + (Instance._sgrDim ? "33" : "") + ">"); break; case "40": case "41": case "42": case "43": case "44": case "45": case "46": case "47": case "100": case "101": case "102": case "103": case "104": case "105": case "106": case "107": Instance._sgrCloseTags.Insert(0, "</mark>"); sb.Append("<mark=#" + GetColorByCode(text4) + "55>"); break; case "38": case "48": next = delegate(string _, string mode) { if (!(mode == "2")) { if (mode == "5") { next = delegate(string previous, string color) { if (previous == "38") { Instance._sgrCloseTags.Insert(0, "</color>"); sb.Append("<color=#" + Get256ColorByCode(color) + (Instance._sgrDim ? "33" : "") + ">"); } else { Instance._sgrCloseTags.Insert(0, "</mark>"); sb.Append("<mark=#" + Get256ColorByCode(color) + "55>"); } next = null; return false; }; } } else { StringBuilder rgb = new StringBuilder(); next = delegate(string _, string r) { rgb.Append(((byte)int.Parse(r)).ToString("X2")); next = delegate(string _, string g) { rgb.Append(((byte)int.Parse(g)).ToString("X2")); next = delegate(string previous, string b) { rgb.Append(((byte)int.Parse(b)).ToString("X2")); if (previous == "38") { Instance._sgrCloseTags.Insert(0, "</color>"); sb.Append("<color=#" + rgb.ToString() + (Instance._sgrDim ? "33" : "") + ">"); } else { Instance._sgrCloseTags.Insert(0, "</mark>"); sb.Append("<mark=#" + rgb.ToString() + "55>"); } next = null; return false; }; return true; }; return true; }; } return true; }; break; default: { string result = Instance._sgrCloseTags.ToString(); Instance._sgrCloseTags.Clear(); Instance._sgrDim = false; return result; } } text3 = text4; } } Logger.LogDebug((object)("<< Parsed \u001b[" + m.Groups[1].Value + "m: " + sb.ToString())); return sb.ToString(); }) : text2, "\\x1B\\[[?;=0-9]*[a-zA-Z~]", ""); if (!__instance.currentText.StartsWith("\n\n\n")) { __instance.currentText = "\n\n\n" + __instance.currentText.TrimStart('\n'); } __instance.screenText.text = __instance.currentText; __instance.forceScrollbarCoroutine = ((MonoBehaviour)__instance).StartCoroutine(__instance.forceScrollbarDown()); } } internal SshClient? sshClient; internal ShellStream? sshShell; internal StringBuilder _sgrCloseTags = new StringBuilder(); internal bool _sgrDim = false; public static LethalSsh Instance { get; private set; } internal static ManualLogSource Logger { get; private set; } internal static Harmony? Harmony { get; set; } public bool Active { get { SshClient val = sshClient; return val != null && ((BaseClient)val).IsConnected; } } internal static ConfigEntry<bool> enableANSI { get; private set; } private void Awake() { Logger = ((BaseUnityPlugin)this).Logger; Instance = this; enableANSI = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "enableFormatting", true, "Enable or disable text colorization and formatting."); Patch(); Logger.LogInfo((object)"baer1.LethalSSH v0.1.1 has loaded!"); } public static string GetColorByCode(string code) { if (code.StartsWith("4")) { code = $"3{code.Last()}"; } else if (code.StartsWith("10")) { code = $"9{code.Last()}"; } if (1 == 0) { } string result = code switch { "30" => "000000", "31" => "AA0000", "32" => "00AA00", "33" => "AA5500", "34" => "0000AA", "35" => "AA00AA", "36" => "00AAAA", "37" => "AAAAAA", "90" => "555555", "91" => "FF5555", "92" => "55FF55", "93" => "FFFF55", "94" => "5555FF", "95" => "FF55FF", "96" => "55FFFF", _ => "FFFFFF", }; if (1 == 0) { } return result; } public static string Get256ColorByCode(string code) { byte b; try { b = (byte)int.Parse(code); } catch { return "FFFFFF"; } if (b <= 7) { return GetColorByCode($"3{b}"); } if (b <= 15) { return GetColorByCode($"9{b - 8}"); } if (b >= 232) { return string.Concat(Enumerable.Repeat((Enumerable.Range(0, 24).ToArray()[b - 232] * 10 + 8).ToString("X2"), 3)); } byte[] array = new byte[3] { (byte)Math.Floor(((float)(int)b - 16f) / 36f), (byte)Math.Floor(((float)(int)b - 16f) % 36f / 6f), (byte)Math.Floor(((float)(int)b - 16f) % 6f) }; StringBuilder stringBuilder = new StringBuilder(); byte[] array2 = array; foreach (byte b2 in array2) { stringBuilder.Append((b2 == 0) ? "00" : (b2 * 40 + 55).ToString("X2")); } return stringBuilder.ToString(); } internal static void Patch() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown if (Harmony == null) { Harmony = new Harmony("baer1.LethalSSH"); } Logger.LogDebug((object)"Patching..."); Harmony.PatchAll(); Logger.LogDebug((object)"Finished patching!"); } internal static void Unpatch() { Logger.LogDebug((object)"Unpatching..."); Harmony? harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); } Logger.LogDebug((object)"Finished unpatching!"); } internal void ResetSSH() { if (Instance.Active) { ((Stream)(object)Instance.sshShell).Close(); ((BaseClient)Instance.sshClient).Disconnect(); Instance.sshClient = null; Instance._sgrCloseTags.Clear(); Instance._sgrDim = false; } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "baer1.LethalSSH"; public const string PLUGIN_NAME = "LethalSsh"; public const string PLUGIN_VERSION = "0.1.1"; } }
Renci.SshNet.dll
Decompiled 2 months ago
The result has been truncated due to the large size, download it to view full contents!
#define TRACE using System; using System.Buffers.Binary; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Formats.Asn1; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Xml; using Microsoft.CodeAnalysis; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.EdEC; using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Agreement; using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Macs; using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Prng; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; using Org.BouncyCastle.Math.EC.Rfc8032; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Zlib; using Renci.SshNet.Abstractions; using Renci.SshNet.Channels; using Renci.SshNet.Common; using Renci.SshNet.Compression; using Renci.SshNet.Connection; using Renci.SshNet.Messages; using Renci.SshNet.Messages.Authentication; using Renci.SshNet.Messages.Connection; using Renci.SshNet.Messages.Transport; using Renci.SshNet.NetConf; using Renci.SshNet.Security; using Renci.SshNet.Security.Cryptography; using Renci.SshNet.Security.Cryptography.Ciphers; using Renci.SshNet.Security.Cryptography.Ciphers.Modes; using Renci.SshNet.Security.Cryptography.Ciphers.Paddings; using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Requests; using Renci.SshNet.Sftp.Responses; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyFileVersion("2024.2.0.1")] [assembly: AssemblyInformationalVersion("2024.2.0.1+74d4364c32")] [assembly: CLSCompliant(false)] [assembly: Guid("ad816c5e-6f13-4589-9f3e-59523f8b77a4")] [assembly: InternalsVisibleTo("Renci.SshNet.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f9194e1eb66b7e2575aaee115ee1d27bc100920e7150e43992d6f668f9737de8b9c7ae892b62b8a36dd1d57929ff1541665d101dc476d6e02390846efae7e5186eec409710fdb596e3f83740afef0d4443055937649bc5a773175b61c57615dac0f0fd10f52b52fedf76c17474cc567b3f7a79de95dde842509fb39aaf69c6c2")] [assembly: InternalsVisibleTo("Renci.SshNet.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f9194e1eb66b7e2575aaee115ee1d27bc100920e7150e43992d6f668f9737de8b9c7ae892b62b8a36dd1d57929ff1541665d101dc476d6e02390846efae7e5186eec409710fdb596e3f83740afef0d4443055937649bc5a773175b61c57615dac0f0fd10f52b52fedf76c17474cc567b3f7a79de95dde842509fb39aaf69c6c2")] [assembly: InternalsVisibleTo("Renci.SshNet.Benchmarks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f9194e1eb66b7e2575aaee115ee1d27bc100920e7150e43992d6f668f9737de8b9c7ae892b62b8a36dd1d57929ff1541665d101dc476d6e02390846efae7e5186eec409710fdb596e3f83740afef0d4443055937649bc5a773175b61c57615dac0f0fd10f52b52fedf76c17474cc567b3f7a79de95dde842509fb39aaf69c6c2")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Renci")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © Renci 2010-2024")] [assembly: AssemblyDescription("SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism.")] [assembly: AssemblyProduct("SSH.NET")] [assembly: AssemblyTitle("SSH.NET")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/sshnet/SSH.NET.git")] [assembly: AssemblyVersion("2024.2.0.1")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } [GeneratedCode("Nerdbank.GitVersioning.Tasks", "3.7.77.5761")] [ExcludeFromCodeCoverage] internal static class ThisAssembly { internal const string AssemblyConfiguration = "Release"; internal const string AssemblyFileVersion = "2024.2.0.1"; internal const string AssemblyInformationalVersion = "2024.2.0.1+74d4364c32"; internal const string AssemblyName = "Renci.SshNet"; internal const string AssemblyTitle = "SSH.NET"; internal const string AssemblyVersion = "2024.2.0.1"; internal static readonly DateTime GitCommitDate = new DateTime(638667472230000000L, DateTimeKind.Utc); internal const string GitCommitId = "74d4364c32c523f2a7d9a3ddbfc3a304ee9fb023"; internal const bool IsPrerelease = false; internal const bool IsPublicRelease = true; internal const string NuGetPackageVersion = "2024.2.0"; internal const string PublicKey = "0024000004800000940000000602000000240000525341310004000001000100f9194e1eb66b7e2575aaee115ee1d27bc100920e7150e43992d6f668f9737de8b9c7ae892b62b8a36dd1d57929ff1541665d101dc476d6e02390846efae7e5186eec409710fdb596e3f83740afef0d4443055937649bc5a773175b61c57615dac0f0fd10f52b52fedf76c17474cc567b3f7a79de95dde842509fb39aaf69c6c2"; internal const string PublicKeyToken = "1cee9f8bde3db106"; internal const string RootNamespace = "Renci.SshNet"; } namespace System.Runtime.Versioning { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiresPreviewFeaturesAttribute : Attribute { public string? Message { get; } public string? Url { get; set; } public RequiresPreviewFeaturesAttribute() { } public RequiresPreviewFeaturesAttribute(string? message) { Message = message; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CallerArgumentExpressionAttribute : Attribute { public string ParameterName { get; } public CallerArgumentExpressionAttribute(string parameterName) { ParameterName = parameterName; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CollectionBuilderAttribute : Attribute { public Type BuilderType { get; } public string MethodName { get; } public CollectionBuilderAttribute(Type builderType, string methodName) { BuilderType = builderType; MethodName = methodName; } } [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CompilerFeatureRequiredAttribute : Attribute { public const string RefStructs = "RefStructs"; public const string RequiredMembers = "RequiredMembers"; public string FeatureName { get; } public bool IsOptional { get; set; } public CompilerFeatureRequiredAttribute(string featureName) { FeatureName = featureName; } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute { public string[] Arguments { get; } public InterpolatedStringHandlerArgumentAttribute(string argument) { Arguments = new string[1] { argument }; } public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) { Arguments = arguments; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerAttribute : Attribute { } [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal static class IsExternalInit { } [AttributeUsage(AttributeTargets.Method, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ModuleInitializerAttribute : Attribute { } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiredMemberAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal sealed class RequiresLocationAttribute : Attribute { } [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SkipLocalsInitAttribute : Attribute { } } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ExperimentalAttribute : Attribute { public string DiagnosticId { get; } public string? UrlFormat { get; set; } public ExperimentalAttribute(string diagnosticId) { DiagnosticId = diagnosticId; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullAttribute : Attribute { public string[] Members { get; } public MemberNotNullAttribute(string member) { Members = new string[1] { member }; } public MemberNotNullAttribute(params string[] members) { Members = members; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullWhenAttribute : Attribute { public bool ReturnValue { get; } public string[] Members { get; } public MemberNotNullWhenAttribute(bool returnValue, string member) { ReturnValue = returnValue; Members = new string[1] { member }; } public MemberNotNullWhenAttribute(bool returnValue, params string[] members) { ReturnValue = returnValue; Members = members; } } [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SetsRequiredMembersAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class StringSyntaxAttribute : Attribute { public const string CompositeFormat = "CompositeFormat"; public const string DateOnlyFormat = "DateOnlyFormat"; public const string DateTimeFormat = "DateTimeFormat"; public const string EnumFormat = "EnumFormat"; public const string GuidFormat = "GuidFormat"; public const string Json = "Json"; public const string NumericFormat = "NumericFormat"; public const string Regex = "Regex"; public const string TimeOnlyFormat = "TimeOnlyFormat"; public const string TimeSpanFormat = "TimeSpanFormat"; public const string Uri = "Uri"; public const string Xml = "Xml"; public string Syntax { get; } public object?[] Arguments { get; } public StringSyntaxAttribute(string syntax) { Syntax = syntax; Arguments = new object[0]; } public StringSyntaxAttribute(string syntax, params object?[] arguments) { Syntax = syntax; Arguments = arguments; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class UnscopedRefAttribute : Attribute { } } namespace System.Threading.Tasks { internal static class TaskToAsyncResult { private sealed class TaskAsyncResult : IAsyncResult { internal readonly Task _task; private readonly AsyncCallback? _callback; public object? AsyncState { get; } public bool CompletedSynchronously { get; } public bool IsCompleted => _task.IsCompleted; public WaitHandle AsyncWaitHandle => ((IAsyncResult)_task).AsyncWaitHandle; internal TaskAsyncResult(Task task, object? state, AsyncCallback? callback) { _task = task; AsyncState = state; if (task.IsCompleted) { CompletedSynchronously = true; callback?.Invoke(this); } else if (callback != null) { _callback = callback; _task.ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().OnCompleted(delegate { _callback(this); }); } } } public static IAsyncResult Begin(Task task, AsyncCallback? callback, object? state) { if (task == null) { throw new ArgumentNullException("task"); } return new TaskAsyncResult(task, state, callback); } public static void End(IAsyncResult asyncResult) { Unwrap(asyncResult).GetAwaiter().GetResult(); } public static TResult End<TResult>(IAsyncResult asyncResult) { return Unwrap<TResult>(asyncResult).GetAwaiter().GetResult(); } public static Task Unwrap(IAsyncResult asyncResult) { if (asyncResult == null) { throw new ArgumentNullException("asyncResult"); } return (asyncResult as TaskAsyncResult)?._task ?? throw new ArgumentException(null, "asyncResult"); } public static Task<TResult> Unwrap<TResult>(IAsyncResult asyncResult) { if (asyncResult == null) { throw new ArgumentNullException("asyncResult"); } return ((asyncResult as TaskAsyncResult)?._task as Task<TResult>) ?? throw new ArgumentException(null, "asyncResult"); } } } namespace Renci.SshNet { public abstract class AuthenticationMethod : IAuthenticationMethod { public abstract string Name { get; } public string Username { get; private set; } public string[] AllowedAuthentications { get; protected set; } protected AuthenticationMethod(string username) { ThrowHelper.ThrowIfNullOrWhiteSpace(username, "username"); Username = username; } public abstract AuthenticationResult Authenticate(Session session); AuthenticationResult IAuthenticationMethod.Authenticate(ISession session) { return Authenticate((Session)session); } } public enum AuthenticationResult { Success, PartialSuccess, Failure } public abstract class BaseClient : IBaseClient, IDisposable { private readonly bool _ownsConnectionInfo; private readonly IServiceFactory _serviceFactory; private readonly object _keepAliveLock = new object(); private TimeSpan _keepAliveInterval; private Timer? _keepAliveTimer; private ConnectionInfo _connectionInfo; private bool _isDisposed; internal ISession? Session { get; private set; } internal IServiceFactory ServiceFactory => _serviceFactory; public ConnectionInfo ConnectionInfo { get { CheckDisposed(); return _connectionInfo; } private set { _connectionInfo = value; } } public virtual bool IsConnected { get { CheckDisposed(); return IsSessionConnected(); } } public TimeSpan KeepAliveInterval { get { CheckDisposed(); return _keepAliveInterval; } set { CheckDisposed(); value.EnsureValidTimeout("KeepAliveInterval"); if (!(value == _keepAliveInterval)) { if (value == Timeout.InfiniteTimeSpan) { StopKeepAliveTimer(); } else if (_keepAliveTimer != null) { _keepAliveTimer.Change(value, value); } else if (IsSessionConnected()) { _keepAliveTimer = CreateKeepAliveTimer(value, value); } _keepAliveInterval = value; } } } public event EventHandler<ExceptionEventArgs>? ErrorOccurred; public event EventHandler<HostKeyEventArgs>? HostKeyReceived; public event EventHandler<SshIdentificationEventArgs>? ServerIdentificationReceived; protected BaseClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo) : this(connectionInfo, ownsConnectionInfo, new ServiceFactory()) { } private protected BaseClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory) { ThrowHelper.ThrowIfNull(connectionInfo, "connectionInfo"); ThrowHelper.ThrowIfNull(serviceFactory, "serviceFactory"); _connectionInfo = connectionInfo; _ownsConnectionInfo = ownsConnectionInfo; _serviceFactory = serviceFactory; _keepAliveInterval = Timeout.InfiniteTimeSpan; } public void Connect() { CheckDisposed(); if (IsConnected) { throw new InvalidOperationException("The client is already connected."); } OnConnecting(); ISession session = Session; if (session == null || !session.IsConnected) { if (session != null) { DisposeSession(session); } Session = CreateAndConnectSession(); } try { OnConnected(); } catch { DisposeSession(); throw; } StartKeepAliveTimer(); } public async Task ConnectAsync(CancellationToken cancellationToken) { CheckDisposed(); cancellationToken.ThrowIfCancellationRequested(); if (IsConnected) { throw new InvalidOperationException("The client is already connected."); } OnConnecting(); ISession session = Session; if (session == null || !session.IsConnected) { if (session != null) { DisposeSession(session); } using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(ConnectionInfo.Timeout); using CancellationTokenSource linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token); try { Session = await CreateAndConnectSessionAsync(linkedCancellationTokenSource.Token).ConfigureAwait(continueOnCapturedContext: false); } catch (OperationCanceledException innerException) when (timeoutCancellationTokenSource.IsCancellationRequested) { throw new SshOperationTimeoutException("Connection has timed out.", innerException); } } try { OnConnected(); } catch { DisposeSession(); throw; } StartKeepAliveTimer(); } public void Disconnect() { CheckDisposed(); OnDisconnecting(); StopKeepAliveTimer(); DisposeSession(); OnDisconnected(); } [Obsolete("Use KeepAliveInterval to send a keep-alive message at regular intervals.")] public void SendKeepAlive() { CheckDisposed(); SendKeepAliveMessage(); } protected virtual void OnConnecting() { } protected virtual void OnConnected() { } protected virtual void OnDisconnecting() { Session?.OnDisconnecting(); } protected virtual void OnDisconnected() { } private void Session_ErrorOccured(object? sender, ExceptionEventArgs e) { this.ErrorOccurred?.Invoke(this, e); } private void Session_HostKeyReceived(object? sender, HostKeyEventArgs e) { this.HostKeyReceived?.Invoke(this, e); } private void Session_ServerIdentificationReceived(object? sender, SshIdentificationEventArgs e) { this.ServerIdentificationReceived?.Invoke(this, e); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_isDisposed && disposing) { Disconnect(); if (_ownsConnectionInfo && _connectionInfo is IDisposable disposable) { disposable.Dispose(); } _isDisposed = true; } } protected void CheckDisposed() { ThrowHelper.ThrowObjectDisposedIf(_isDisposed, this); } private void StopKeepAliveTimer() { if (_keepAliveTimer != null) { _keepAliveTimer.Dispose(); _keepAliveTimer = null; } } private void SendKeepAliveMessage() { ISession session = Session; if (session == null || !Monitor.TryEnter(_keepAliveLock)) { return; } try { session.TrySendMessage(new IgnoreMessage()); } finally { Monitor.Exit(_keepAliveLock); } } private void StartKeepAliveTimer() { if (!(_keepAliveInterval == Timeout.InfiniteTimeSpan) && _keepAliveTimer == null) { _keepAliveTimer = CreateKeepAliveTimer(_keepAliveInterval, _keepAliveInterval); } } private Timer CreateKeepAliveTimer(TimeSpan dueTime, TimeSpan period) { return new Timer(delegate { SendKeepAliveMessage(); }, Session, dueTime, period); } private ISession CreateAndConnectSession() { ISession session = _serviceFactory.CreateSession(ConnectionInfo, _serviceFactory.CreateSocketFactory()); session.ServerIdentificationReceived += Session_ServerIdentificationReceived; session.HostKeyReceived += Session_HostKeyReceived; session.ErrorOccured += Session_ErrorOccured; try { session.Connect(); return session; } catch { DisposeSession(session); throw; } } private async Task<ISession> CreateAndConnectSessionAsync(CancellationToken cancellationToken) { ISession session = _serviceFactory.CreateSession(ConnectionInfo, _serviceFactory.CreateSocketFactory()); session.ServerIdentificationReceived += Session_ServerIdentificationReceived; session.HostKeyReceived += Session_HostKeyReceived; session.ErrorOccured += Session_ErrorOccured; try { await session.ConnectAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); return session; } catch { DisposeSession(session); throw; } } private void DisposeSession(ISession session) { session.ErrorOccured -= Session_ErrorOccured; session.HostKeyReceived -= Session_HostKeyReceived; session.ServerIdentificationReceived -= Session_ServerIdentificationReceived; session.Dispose(); } private void DisposeSession() { ISession session = Session; if (session != null) { Session = null; DisposeSession(session); } } private bool IsSessionConnected() { return Session?.IsConnected ?? false; } } public class CipherInfo { public int KeySize { get; private set; } public bool IsAead { get; private set; } public Func<byte[], byte[], Cipher> Cipher { get; private set; } public CipherInfo(int keySize, Func<byte[], byte[], Cipher> cipher, bool isAead = false) { CipherInfo cipherInfo = this; KeySize = keySize; Cipher = (byte[] key, byte[] iv) => cipher(key.Take(cipherInfo.KeySize / 8), iv); IsAead = isAead; } } internal sealed class ClientAuthentication : IClientAuthentication { private sealed class AuthenticationState { private readonly IList<IAuthenticationMethod> _supportedAuthenticationMethods; private readonly Dictionary<IAuthenticationMethod, int> _authenticationMethodPartialSuccessRegister; private readonly List<IAuthenticationMethod> _failedAuthenticationMethods; public AuthenticationState(IList<IAuthenticationMethod> supportedAuthenticationMethods) { _supportedAuthenticationMethods = supportedAuthenticationMethods; _failedAuthenticationMethods = new List<IAuthenticationMethod>(); _authenticationMethodPartialSuccessRegister = new Dictionary<IAuthenticationMethod, int>(); } public void RecordFailure(IAuthenticationMethod authenticationMethod) { _failedAuthenticationMethods.Add(authenticationMethod); } public void RecordPartialSuccess(IAuthenticationMethod authenticationMethod) { if (_authenticationMethodPartialSuccessRegister.TryGetValue(authenticationMethod, out var value)) { _authenticationMethodPartialSuccessRegister[authenticationMethod] = value + 1; } else { _authenticationMethodPartialSuccessRegister.Add(authenticationMethod, 1); } } public int GetPartialSuccessCount(IAuthenticationMethod authenticationMethod) { if (_authenticationMethodPartialSuccessRegister.TryGetValue(authenticationMethod, out var value)) { return value; } return 0; } public List<IAuthenticationMethod> GetSupportedAuthenticationMethods(string[] allowedAuthenticationMethods) { List<IAuthenticationMethod> list = new List<IAuthenticationMethod>(); foreach (IAuthenticationMethod supportedAuthenticationMethod in _supportedAuthenticationMethods) { string name = supportedAuthenticationMethod.Name; for (int i = 0; i < allowedAuthenticationMethods.Length; i++) { if (allowedAuthenticationMethods[i] == name) { list.Add(supportedAuthenticationMethod); break; } } } return list; } public IEnumerable<IAuthenticationMethod> GetActiveAuthenticationMethods(List<IAuthenticationMethod> matchingAuthenticationMethods) { List<IAuthenticationMethod> skippedAuthenticationMethods = new List<IAuthenticationMethod>(); for (int i = 0; i < matchingAuthenticationMethods.Count; i++) { IAuthenticationMethod authenticationMethod = matchingAuthenticationMethods[i]; if (!_failedAuthenticationMethods.Contains(authenticationMethod)) { if (_authenticationMethodPartialSuccessRegister.ContainsKey(authenticationMethod)) { skippedAuthenticationMethods.Add(authenticationMethod); } else { yield return authenticationMethod; } } } foreach (IAuthenticationMethod item in skippedAuthenticationMethods) { yield return item; } } } private readonly int _partialSuccessLimit; internal int PartialSuccessLimit => _partialSuccessLimit; public ClientAuthentication(int partialSuccessLimit) { if (partialSuccessLimit < 1) { throw new ArgumentOutOfRangeException("partialSuccessLimit", "Cannot be less than one."); } _partialSuccessLimit = partialSuccessLimit; } public void Authenticate(IConnectionInfoInternal connectionInfo, ISession session) { ThrowHelper.ThrowIfNull(connectionInfo, "connectionInfo"); ThrowHelper.ThrowIfNull(session, "session"); session.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"); session.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"); session.RegisterMessage("SSH_MSG_USERAUTH_BANNER"); session.UserAuthenticationBannerReceived += connectionInfo.UserAuthenticationBannerReceived; try { SshAuthenticationException authenticationException = null; IAuthenticationMethod authenticationMethod = connectionInfo.CreateNoneAuthenticationMethod(); if (authenticationMethod.Authenticate(session) != 0 && !TryAuthenticate(session, new AuthenticationState(connectionInfo.AuthenticationMethods), authenticationMethod.AllowedAuthentications, ref authenticationException)) { throw authenticationException; } } finally { session.UserAuthenticationBannerReceived -= connectionInfo.UserAuthenticationBannerReceived; session.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"); session.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"); session.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"); } } private bool TryAuthenticate(ISession session, AuthenticationState authenticationState, string[] allowedAuthenticationMethods, ref SshAuthenticationException authenticationException) { if (allowedAuthenticationMethods.Length == 0) { authenticationException = new SshAuthenticationException("No authentication methods defined on SSH server."); return false; } List<IAuthenticationMethod> supportedAuthenticationMethods = authenticationState.GetSupportedAuthenticationMethods(allowedAuthenticationMethods); if (supportedAuthenticationMethods.Count == 0) { authenticationException = new SshAuthenticationException(string.Format(CultureInfo.InvariantCulture, "No suitable authentication method found to complete authentication ({0}).", string.Join(',', allowedAuthenticationMethods))); return false; } foreach (IAuthenticationMethod activeAuthenticationMethod in authenticationState.GetActiveAuthenticationMethods(supportedAuthenticationMethods)) { if (authenticationState.GetPartialSuccessCount(activeAuthenticationMethod) >= _partialSuccessLimit) { authenticationException = new SshAuthenticationException($"Reached authentication attempt limit for method ({activeAuthenticationMethod.Name})."); continue; } AuthenticationResult authenticationResult = activeAuthenticationMethod.Authenticate(session); switch (authenticationResult) { case AuthenticationResult.PartialSuccess: authenticationState.RecordPartialSuccess(activeAuthenticationMethod); if (TryAuthenticate(session, authenticationState, activeAuthenticationMethod.AllowedAuthentications, ref authenticationException)) { authenticationResult = AuthenticationResult.Success; } break; case AuthenticationResult.Failure: authenticationState.RecordFailure(activeAuthenticationMethod); authenticationException = new SshAuthenticationException($"Permission denied ({activeAuthenticationMethod.Name})."); break; case AuthenticationResult.Success: authenticationException = null; break; } if (authenticationResult != 0) { continue; } return true; } return false; } } public class ConnectionInfo : IConnectionInfoInternal, IConnectionInfo { internal const int DefaultPort = 22; private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30.0); private static readonly TimeSpan DefaultChannelCloseTimeout = TimeSpan.FromSeconds(1.0); private TimeSpan _timeout; private TimeSpan _channelCloseTimeout; public IDictionary<string, Func<IKeyExchange>> KeyExchangeAlgorithms { get; private set; } public IDictionary<string, CipherInfo> Encryptions { get; private set; } public IDictionary<string, HashInfo> HmacAlgorithms { get; private set; } public IDictionary<string, Func<byte[], KeyHostAlgorithm>> HostKeyAlgorithms { get; private set; } public IList<AuthenticationMethod> AuthenticationMethods { get; private set; } public IDictionary<string, Func<Compressor>> CompressionAlgorithms { get; private set; } public IDictionary<string, RequestInfo> ChannelRequests { get; private set; } public bool IsAuthenticated { get; private set; } public string Host { get; private set; } public int Port { get; private set; } public string Username { get; private set; } public ProxyTypes ProxyType { get; private set; } public string ProxyHost { get; private set; } public int ProxyPort { get; private set; } public string ProxyUsername { get; private set; } public string ProxyPassword { get; private set; } public TimeSpan Timeout { get { return _timeout; } set { value.EnsureValidTimeout("Timeout"); _timeout = value; } } public TimeSpan ChannelCloseTimeout { get { return _channelCloseTimeout; } set { value.EnsureValidTimeout("ChannelCloseTimeout"); _channelCloseTimeout = value; } } public Encoding Encoding { get; set; } public int RetryAttempts { get; set; } public int MaxSessions { get; set; } public string CurrentKeyExchangeAlgorithm { get; internal set; } public string CurrentServerEncryption { get; internal set; } public string CurrentClientEncryption { get; internal set; } public string CurrentServerHmacAlgorithm { get; internal set; } public string CurrentClientHmacAlgorithm { get; internal set; } public string CurrentHostKeyAlgorithm { get; internal set; } public string CurrentServerCompressionAlgorithm { get; internal set; } public string ServerVersion { get; internal set; } public string ClientVersion { get; internal set; } public string CurrentClientCompressionAlgorithm { get; internal set; } IList<IAuthenticationMethod> IConnectionInfoInternal.AuthenticationMethods => AuthenticationMethods.Cast<IAuthenticationMethod>().ToList(); public event EventHandler<AuthenticationBannerEventArgs> AuthenticationBanner; public ConnectionInfo(string host, string username, params AuthenticationMethod[] authenticationMethods) : this(host, 22, username, ProxyTypes.None, null, 0, null, null, authenticationMethods) { } public ConnectionInfo(string host, int port, string username, params AuthenticationMethod[] authenticationMethods) : this(host, port, username, ProxyTypes.None, null, 0, null, null, authenticationMethods) { } public ConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params AuthenticationMethod[] authenticationMethods) { ThrowHelper.ThrowIfNull(host, "host"); port.ValidatePort("port"); ThrowHelper.ThrowIfNullOrWhiteSpace(username, "username"); if (proxyType != 0) { ThrowHelper.ThrowIfNull(proxyHost, "proxyHost"); proxyPort.ValidatePort("proxyPort"); } ThrowHelper.ThrowIfNull(authenticationMethods, "authenticationMethods"); if (authenticationMethods.Length == 0) { throw new ArgumentException("At least one authentication method should be specified.", "authenticationMethods"); } Timeout = DefaultTimeout; ChannelCloseTimeout = DefaultChannelCloseTimeout; RetryAttempts = 10; MaxSessions = 10; Encoding = Encoding.UTF8; KeyExchangeAlgorithms = new Dictionary<string, Func<IKeyExchange>> { { "curve25519-sha256", () => new KeyExchangeECCurve25519() }, { "curve25519-sha256@libssh.org", () => new KeyExchangeECCurve25519() }, { "ecdh-sha2-nistp256", () => new KeyExchangeECDH256() }, { "ecdh-sha2-nistp384", () => new KeyExchangeECDH384() }, { "ecdh-sha2-nistp521", () => new KeyExchangeECDH521() }, { "diffie-hellman-group-exchange-sha256", () => new KeyExchangeDiffieHellmanGroupExchangeSha256() }, { "diffie-hellman-group-exchange-sha1", () => new KeyExchangeDiffieHellmanGroupExchangeSha1() }, { "diffie-hellman-group16-sha512", () => new KeyExchangeDiffieHellmanGroup16Sha512() }, { "diffie-hellman-group14-sha256", () => new KeyExchangeDiffieHellmanGroup14Sha256() }, { "diffie-hellman-group14-sha1", () => new KeyExchangeDiffieHellmanGroup14Sha1() }, { "diffie-hellman-group1-sha1", () => new KeyExchangeDiffieHellmanGroup1Sha1() } }; Encryptions = new Dictionary<string, CipherInfo> { { "aes128-ctr", new CipherInfo(128, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CTR)) }, { "aes192-ctr", new CipherInfo(192, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CTR)) }, { "aes256-ctr", new CipherInfo(256, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CTR)) }, { "aes128-gcm@openssh.com", new CipherInfo(128, (byte[] key, byte[] iv) => new AesGcmCipher(key, iv, 4), isAead: true) }, { "aes256-gcm@openssh.com", new CipherInfo(256, (byte[] key, byte[] iv) => new AesGcmCipher(key, iv, 4), isAead: true) }, { "chacha20-poly1305@openssh.com", new CipherInfo(512, (byte[] key, byte[] iv) => new ChaCha20Poly1305Cipher(key, 4), isAead: true) }, { "aes128-cbc", new CipherInfo(128, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CBC)) }, { "aes192-cbc", new CipherInfo(192, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CBC)) }, { "aes256-cbc", new CipherInfo(256, (byte[] key, byte[] iv) => new AesCipher(key, iv, AesCipherMode.CBC)) }, { "3des-cbc", new CipherInfo(192, (byte[] key, byte[] iv) => new TripleDesCipher(key, new CbcCipherMode(iv), null)) } }; HmacAlgorithms = new Dictionary<string, HashInfo> { { "hmac-sha2-256", new HashInfo(256, (byte[] key) => new HMACSHA256(key)) }, { "hmac-sha2-512", new HashInfo(512, (byte[] key) => new HMACSHA512(key)) }, { "hmac-sha1", new HashInfo(160, (byte[] key) => new HMACSHA1(key)) }, { "hmac-sha2-256-etm@openssh.com", new HashInfo(256, (byte[] key) => new HMACSHA256(key), isEncryptThenMAC: true) }, { "hmac-sha2-512-etm@openssh.com", new HashInfo(512, (byte[] key) => new HMACSHA512(key), isEncryptThenMAC: true) }, { "hmac-sha1-etm@openssh.com", new HashInfo(160, (byte[] key) => new HMACSHA1(key), isEncryptThenMAC: true) } }; Dictionary<string, Func<byte[], KeyHostAlgorithm>> hostAlgs = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>(); hostAlgs.Add("ssh-ed25519-cert-v01@openssh.com", delegate(byte[] data) { Certificate certificate8 = new Certificate(data); return new CertificateHostAlgorithm("ssh-ed25519-cert-v01@openssh.com", certificate8, hostAlgs); }); hostAlgs.Add("ecdsa-sha2-nistp256-cert-v01@openssh.com", delegate(byte[] data) { Certificate certificate7 = new Certificate(data); return new CertificateHostAlgorithm("ecdsa-sha2-nistp256-cert-v01@openssh.com", certificate7, hostAlgs); }); hostAlgs.Add("ecdsa-sha2-nistp384-cert-v01@openssh.com", delegate(byte[] data) { Certificate certificate6 = new Certificate(data); return new CertificateHostAlgorithm("ecdsa-sha2-nistp384-cert-v01@openssh.com", certificate6, hostAlgs); }); hostAlgs.Add("ecdsa-sha2-nistp521-cert-v01@openssh.com", delegate(byte[] data) { Certificate certificate5 = new Certificate(data); return new CertificateHostAlgorithm("ecdsa-sha2-nistp521-cert-v01@openssh.com", certificate5, hostAlgs); }); hostAlgs.Add("rsa-sha2-512-cert-v01@openssh.com", delegate(byte[] data) { Certificate certificate4 = new Certificate(data); return new CertificateHostAlgorithm("rsa-sha2-512-cert-v01@openssh.com", certificate4, new RsaDigitalSignature((RsaKey)certificate4.Key, HashAlgorithmName.SHA512), hostAlgs); }); hostAlgs.Add("rsa-sha2-256-cert-v01@openssh.com", delegate(byte[] data) { Certificate certificate3 = new Certificate(data); return new CertificateHostAlgorithm("rsa-sha2-256-cert-v01@openssh.com", certificate3, new RsaDigitalSignature((RsaKey)certificate3.Key, HashAlgorithmName.SHA256), hostAlgs); }); hostAlgs.Add("ssh-rsa-cert-v01@openssh.com", delegate(byte[] data) { Certificate certificate2 = new Certificate(data); return new CertificateHostAlgorithm("ssh-rsa-cert-v01@openssh.com", certificate2, hostAlgs); }); hostAlgs.Add("ssh-dss-cert-v01@openssh.com", delegate(byte[] data) { Certificate certificate = new Certificate(data); return new CertificateHostAlgorithm("ssh-dss-cert-v01@openssh.com", certificate, hostAlgs); }); hostAlgs.Add("ssh-ed25519", (byte[] data) => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(new SshKeyData(data)))); hostAlgs.Add("ecdsa-sha2-nistp256", (byte[] data) => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(new SshKeyData(data)))); hostAlgs.Add("ecdsa-sha2-nistp384", (byte[] data) => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(new SshKeyData(data)))); hostAlgs.Add("ecdsa-sha2-nistp521", (byte[] data) => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(new SshKeyData(data)))); hostAlgs.Add("rsa-sha2-512", delegate(byte[] data) { RsaKey rsaKey2 = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-512", rsaKey2, new RsaDigitalSignature(rsaKey2, HashAlgorithmName.SHA512)); }); hostAlgs.Add("rsa-sha2-256", delegate(byte[] data) { RsaKey rsaKey = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-256", rsaKey, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256)); }); hostAlgs.Add("ssh-rsa", (byte[] data) => new KeyHostAlgorithm("ssh-rsa", new RsaKey(new SshKeyData(data)))); hostAlgs.Add("ssh-dss", (byte[] data) => new KeyHostAlgorithm("ssh-dss", new DsaKey(new SshKeyData(data)))); HostKeyAlgorithms = hostAlgs; CompressionAlgorithms = new Dictionary<string, Func<Compressor>> { { "none", null }, { "zlib@openssh.com", () => new ZlibOpenSsh() } }; ChannelRequests = new Dictionary<string, RequestInfo> { { "env", new EnvironmentVariableRequestInfo() }, { "exec", new ExecRequestInfo() }, { "exit-signal", new ExitSignalRequestInfo() }, { "exit-status", new ExitStatusRequestInfo() }, { "pty-req", new PseudoTerminalRequestInfo() }, { "shell", new ShellRequestInfo() }, { "signal", new SignalRequestInfo() }, { "subsystem", new SubsystemRequestInfo() }, { "window-change", new WindowChangeRequestInfo() }, { "x11-req", new X11ForwardingRequestInfo() }, { "xon-xoff", new XonXoffRequestInfo() }, { "eow@openssh.com", new EndOfWriteRequestInfo() }, { "keepalive@openssh.com", new KeepAliveRequestInfo() } }; Host = host; Port = port; Username = username; ProxyType = proxyType; ProxyHost = proxyHost; ProxyPort = proxyPort; ProxyUsername = proxyUsername; ProxyPassword = proxyPassword; AuthenticationMethods = authenticationMethods; } internal void Authenticate(ISession session, IServiceFactory serviceFactory) { ThrowHelper.ThrowIfNull(serviceFactory, "serviceFactory"); IsAuthenticated = false; serviceFactory.CreateClientAuthentication().Authenticate(this, session); IsAuthenticated = true; } void IConnectionInfoInternal.UserAuthenticationBannerReceived(object sender, MessageEventArgs<BannerMessage> e) { this.AuthenticationBanner?.Invoke(this, new AuthenticationBannerEventArgs(Username, e.Message.Message, e.Message.Language)); } IAuthenticationMethod IConnectionInfoInternal.CreateNoneAuthenticationMethod() { return new NoneAuthenticationMethod(Username); } } public class ExpectAction { public Regex Expect { get; private set; } public Action<string> Action { get; private set; } public ExpectAction(Regex expect, Action<string> action) { ThrowHelper.ThrowIfNull(expect, "expect"); ThrowHelper.ThrowIfNull(action, "action"); Expect = expect; Action = action; } public ExpectAction(string expect, Action<string> action) { ThrowHelper.ThrowIfNull(expect, "expect"); ThrowHelper.ThrowIfNull(action, "action"); Expect = new Regex(Regex.Escape(expect)); Action = action; } } public abstract class ForwardedPort : IForwardedPort, IDisposable { internal ISession Session { get; set; } public abstract bool IsStarted { get; } public event EventHandler Closing; public event EventHandler<ExceptionEventArgs> Exception; public event EventHandler<PortForwardEventArgs> RequestReceived; public virtual void Start() { CheckDisposed(); if (IsStarted) { throw new InvalidOperationException("Forwarded port is already started."); } if (Session == null) { throw new InvalidOperationException("Forwarded port is not added to a client."); } if (!Session.IsConnected) { throw new SshConnectionException("Client not connected."); } Session.ErrorOccured += Session_ErrorOccured; StartPort(); } public virtual void Stop() { if (IsStarted) { StopPort(Session.ConnectionInfo.Timeout); } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected abstract void StartPort(); protected virtual void StopPort(TimeSpan timeout) { timeout.EnsureValidTimeout("timeout"); RaiseClosing(); ISession session = Session; if (session != null) { session.ErrorOccured -= Session_ErrorOccured; } } protected virtual void Dispose(bool disposing) { if (disposing) { ISession session = Session; if (session != null) { StopPort(session.ConnectionInfo.Timeout); Session = null; } } } protected abstract void CheckDisposed(); protected void RaiseExceptionEvent(Exception exception) { this.Exception?.Invoke(this, new ExceptionEventArgs(exception)); } protected void RaiseRequestReceived(string host, uint port) { this.RequestReceived?.Invoke(this, new PortForwardEventArgs(host, port)); } private void RaiseClosing() { this.Closing?.Invoke(this, EventArgs.Empty); } private void Session_ErrorOccured(object sender, ExceptionEventArgs e) { RaiseExceptionEvent(e.Exception); } } public class ForwardedPortDynamic : ForwardedPort { private ForwardedPortStatus _status; private bool _isDisposed; private Socket _listener; private CountdownEvent _pendingChannelCountdown; public string BoundHost { get; } public uint BoundPort { get; } public override bool IsStarted => _status == ForwardedPortStatus.Started; public ForwardedPortDynamic(uint port) : this(string.Empty, port) { } public ForwardedPortDynamic(string host, uint port) { BoundHost = host; BoundPort = port; _status = ForwardedPortStatus.Stopped; } protected override void StartPort() { if (!ForwardedPortStatus.ToStarting(ref _status)) { return; } try { InternalStart(); } catch (Exception) { _status = ForwardedPortStatus.Stopped; throw; } } protected override void StopPort(TimeSpan timeout) { timeout.EnsureValidTimeout("timeout"); if (ForwardedPortStatus.ToStopping(ref _status)) { base.StopPort(timeout); StopListener(); InternalStop(timeout); _status = ForwardedPortStatus.Stopped; } } protected override void CheckDisposed() { ThrowHelper.ThrowObjectDisposedIf(_isDisposed, this); } protected override void Dispose(bool disposing) { if (!_isDisposed) { base.Dispose(disposing); InternalDispose(disposing); _isDisposed = true; } } private void InternalStart() { InitializePendingChannelCountdown(); IPAddress address = IPAddress.Any; if (!string.IsNullOrEmpty(BoundHost)) { address = Dns.GetHostAddresses(BoundHost)[0]; } IPEndPoint iPEndPoint = new IPEndPoint(address, (int)BoundPort); _listener = new Socket(iPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; _listener.Bind(iPEndPoint); _listener.Listen(5); base.Session.ErrorOccured += Session_ErrorOccured; base.Session.Disconnected += Session_Disconnected; _status = ForwardedPortStatus.Started; StartAccept(null); } private void StartAccept(SocketAsyncEventArgs e) { if (e == null) { e = new SocketAsyncEventArgs(); e.Completed += AcceptCompleted; } else { e.AcceptSocket = null; } if (!IsStarted) { return; } try { if (!_listener.AcceptAsync(e)) { AcceptCompleted(null, e); } } catch (ObjectDisposedException) { if (_status == ForwardedPortStatus.Stopping || _status == ForwardedPortStatus.Stopped) { return; } throw; } } private void AcceptCompleted(object sender, SocketAsyncEventArgs e) { SocketError socketError = e.SocketError; if ((socketError != SocketError.OperationAborted && socketError != SocketError.NotSocket) || 1 == 0) { Socket acceptSocket = e.AcceptSocket; if (e.SocketError != 0) { StartAccept(e); CloseClientSocket(acceptSocket); } else { StartAccept(e); ProcessAccept(acceptSocket); } } } private void ProcessAccept(Socket clientSocket) { if (!IsStarted) { CloseClientSocket(clientSocket); return; } CountdownEvent pendingChannelCountdown = _pendingChannelCountdown; pendingChannelCountdown.AddCount(); try { using IChannelDirectTcpip channelDirectTcpip = base.Session.CreateChannelDirectTcpip(); channelDirectTcpip.Exception += Channel_Exception; if (!HandleSocks(channelDirectTcpip, clientSocket, base.Session.ConnectionInfo.Timeout)) { CloseClientSocket(clientSocket); } else { channelDirectTcpip.Bind(); } } catch (Exception exception) { RaiseExceptionEvent(exception); CloseClientSocket(clientSocket); } finally { try { pendingChannelCountdown.Signal(); } catch (ObjectDisposedException) { } } } private void InitializePendingChannelCountdown() { Interlocked.Exchange(ref _pendingChannelCountdown, new CountdownEvent(1))?.Dispose(); } private bool HandleSocks(IChannelDirectTcpip channel, Socket clientSocket, TimeSpan timeout) { base.Closing += closeClientSocket; try { int num = SocketAbstraction.ReadByte(clientSocket, timeout); return num switch { -1 => false, 4 => HandleSocks4(clientSocket, channel, timeout), 5 => HandleSocks5(clientSocket, channel, timeout), _ => throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "SOCKS version {0} is not supported.", num)), }; } catch (SocketException ex) { if (ex.SocketErrorCode != SocketError.ConnectionAborted) { RaiseExceptionEvent(ex); } return false; } finally { base.Closing -= closeClientSocket; } void closeClientSocket(object sender, EventArgs args) { CloseClientSocket(clientSocket); } } private static void CloseClientSocket(Socket clientSocket) { if (clientSocket.Connected) { try { clientSocket.Shutdown(SocketShutdown.Send); } catch (Exception) { } } clientSocket.Dispose(); } private void StopListener() { _listener?.Dispose(); ISession session = base.Session; if (session != null) { session.ErrorOccured -= Session_ErrorOccured; session.Disconnected -= Session_Disconnected; } } private void InternalStop(TimeSpan timeout) { _pendingChannelCountdown.Signal(); _pendingChannelCountdown.Wait(timeout); } private void InternalDispose(bool disposing) { if (disposing) { Socket listener = _listener; if (listener != null) { _listener = null; listener.Dispose(); } CountdownEvent pendingChannelCountdown = _pendingChannelCountdown; if (pendingChannelCountdown != null) { _pendingChannelCountdown = null; pendingChannelCountdown.Dispose(); } } } private void Session_Disconnected(object sender, EventArgs e) { ISession session = base.Session; if (session != null) { StopPort(session.ConnectionInfo.Timeout); } } private void Session_ErrorOccured(object sender, ExceptionEventArgs e) { ISession session = base.Session; if (session != null) { StopPort(session.ConnectionInfo.Timeout); } } private void Channel_Exception(object sender, ExceptionEventArgs e) { RaiseExceptionEvent(e.Exception); } private bool HandleSocks4(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout) { if (SocketAbstraction.ReadByte(socket, timeout) == -1) { return false; } byte[] array = new byte[2]; if (SocketAbstraction.Read(socket, array, 0, array.Length, timeout) == 0) { return false; } ushort port = BinaryPrimitives.ReadUInt16BigEndian(array); byte[] array2 = new byte[4]; if (SocketAbstraction.Read(socket, array2, 0, array2.Length, timeout) == 0) { return false; } IPAddress iPAddress = new IPAddress(array2); if (ReadString(socket, timeout) == null) { return false; } string text = iPAddress.ToString(); RaiseRequestReceived(text, port); channel.Open(text, port, this, socket); SocketAbstraction.SendByte(socket, 0); if (channel.IsOpen) { SocketAbstraction.SendByte(socket, 90); SocketAbstraction.Send(socket, array, 0, array.Length); SocketAbstraction.Send(socket, array2, 0, array2.Length); return true; } SocketAbstraction.SendByte(socket, 91); return false; } private bool HandleSocks5(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout) { int num = SocketAbstraction.ReadByte(socket, timeout); if (num == -1) { return false; } byte[] array = new byte[num]; if (SocketAbstraction.Read(socket, array, 0, array.Length, timeout) == 0) { return false; } if (array.Min() == 0) { SocketAbstraction.Send(socket, new byte[2] { 5, 0 }, 0, 2); } else { SocketAbstraction.Send(socket, new byte[2] { 5, 255 }, 0, 2); } switch (SocketAbstraction.ReadByte(socket, timeout)) { case -1: return false; default: throw new ProxyException("SOCKS5: Version 5 is expected."); case 5: if (SocketAbstraction.ReadByte(socket, timeout) == -1) { return false; } switch (SocketAbstraction.ReadByte(socket, timeout)) { case -1: return false; default: throw new ProxyException("SOCKS5: 0 is expected for reserved byte."); case 0: { int num2 = SocketAbstraction.ReadByte(socket, timeout); if (num2 == -1) { return false; } string socks5Host = GetSocks5Host(num2, socket, timeout); if (socks5Host == null) { return false; } byte[] array2 = new byte[2]; if (SocketAbstraction.Read(socket, array2, 0, array2.Length, timeout) == 0) { return false; } ushort port = BinaryPrimitives.ReadUInt16BigEndian(array2); RaiseRequestReceived(socks5Host, port); channel.Open(socks5Host, port, this, socket); byte[] array3 = CreateSocks5Reply(channel.IsOpen); SocketAbstraction.Send(socket, array3, 0, array3.Length); return true; } } } } private static string GetSocks5Host(int addressType, Socket socket, TimeSpan timeout) { switch (addressType) { case 1: { byte[] array3 = new byte[4]; if (SocketAbstraction.Read(socket, array3, 0, 4, timeout) == 0) { return null; } return new IPAddress(array3).ToString(); } case 3: { int num = SocketAbstraction.ReadByte(socket, timeout); if (num == -1) { return null; } byte[] array2 = new byte[num]; if (SocketAbstraction.Read(socket, array2, 0, array2.Length, timeout) == 0) { return null; } return SshData.Ascii.GetString(array2, 0, array2.Length); } case 4: { byte[] array = new byte[16]; if (SocketAbstraction.Read(socket, array, 0, 16, timeout) == 0) { return null; } return new IPAddress(array).ToString(); } default: throw new ProxyException(string.Format(CultureInfo.InvariantCulture, "SOCKS5: Address type '{0}' is not supported.", addressType)); } } private static byte[] CreateSocks5Reply(bool channelOpen) { byte[] array = new byte[10] { 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (channelOpen) { array[1] = 0; } else { array[1] = 1; } array[2] = 0; array[3] = 1; return array; } private static string ReadString(Socket socket, TimeSpan timeout) { StringBuilder stringBuilder = new StringBuilder(); byte[] array = new byte[1]; while (true) { if (SocketAbstraction.Read(socket, array, 0, 1, timeout) == 0) { return null; } byte b = array[0]; if (b == 0) { break; } stringBuilder.Append((char)b); } return stringBuilder.ToString(); } ~ForwardedPortDynamic() { Dispose(disposing: false); } } public class ForwardedPortLocal : ForwardedPort { private ForwardedPortStatus _status; private bool _isDisposed; private Socket _listener; private CountdownEvent _pendingChannelCountdown; public string BoundHost { get; private set; } public uint BoundPort { get; private set; } public string Host { get; private set; } public uint Port { get; private set; } public override bool IsStarted => _status == ForwardedPortStatus.Started; public ForwardedPortLocal(uint boundPort, string host, uint port) : this(string.Empty, boundPort, host, port) { } public ForwardedPortLocal(string boundHost, string host, uint port) : this(boundHost, 0u, host, port) { } public ForwardedPortLocal(string boundHost, uint boundPort, string host, uint port) { ThrowHelper.ThrowIfNull(boundHost, "boundHost"); ThrowHelper.ThrowIfNull(host, "host"); boundPort.ValidatePort("boundPort"); port.ValidatePort("port"); BoundHost = boundHost; BoundPort = boundPort; Host = host; Port = port; _status = ForwardedPortStatus.Stopped; } protected override void StartPort() { if (!ForwardedPortStatus.ToStarting(ref _status)) { return; } try { InternalStart(); } catch (Exception) { _status = ForwardedPortStatus.Stopped; throw; } } protected override void StopPort(TimeSpan timeout) { timeout.EnsureValidTimeout("timeout"); if (ForwardedPortStatus.ToStopping(ref _status)) { base.StopPort(timeout); StopListener(); InternalStop(timeout); _status = ForwardedPortStatus.Stopped; } } protected override void CheckDisposed() { ThrowHelper.ThrowObjectDisposedIf(_isDisposed, this); } protected override void Dispose(bool disposing) { if (!_isDisposed) { base.Dispose(disposing); InternalDispose(disposing); _isDisposed = true; } } ~ForwardedPortLocal() { Dispose(disposing: false); } private void InternalStart() { IPEndPoint iPEndPoint = new IPEndPoint(Dns.GetHostAddresses(BoundHost)[0], (int)BoundPort); _listener = new Socket(iPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; _listener.Bind(iPEndPoint); _listener.Listen(5); BoundPort = (uint)((IPEndPoint)_listener.LocalEndPoint).Port; base.Session.ErrorOccured += Session_ErrorOccured; base.Session.Disconnected += Session_Disconnected; InitializePendingChannelCountdown(); _status = ForwardedPortStatus.Started; StartAccept(null); } private void StartAccept(SocketAsyncEventArgs e) { if (e == null) { e = new SocketAsyncEventArgs(); e.Completed += AcceptCompleted; } else { e.AcceptSocket = null; } if (!IsStarted) { return; } try { if (!_listener.AcceptAsync(e)) { AcceptCompleted(null, e); } } catch (ObjectDisposedException) { if (_status == ForwardedPortStatus.Stopping || _status == ForwardedPortStatus.Stopped) { return; } throw; } } private void AcceptCompleted(object sender, SocketAsyncEventArgs e) { SocketError socketError = e.SocketError; if ((socketError != SocketError.OperationAborted && socketError != SocketError.NotSocket) || 1 == 0) { Socket acceptSocket = e.AcceptSocket; if (e.SocketError != 0) { StartAccept(e); CloseClientSocket(acceptSocket); } else { StartAccept(e); ProcessAccept(acceptSocket); } } } private void ProcessAccept(Socket clientSocket) { if (!IsStarted) { CloseClientSocket(clientSocket); return; } CountdownEvent pendingChannelCountdown = _pendingChannelCountdown; pendingChannelCountdown.AddCount(); try { IPEndPoint iPEndPoint = (IPEndPoint)clientSocket.RemoteEndPoint; RaiseRequestReceived(iPEndPoint.Address.ToString(), (uint)iPEndPoint.Port); using IChannelDirectTcpip channelDirectTcpip = base.Session.CreateChannelDirectTcpip(); channelDirectTcpip.Exception += Channel_Exception; channelDirectTcpip.Open(Host, Port, this, clientSocket); channelDirectTcpip.Bind(); } catch (Exception exception) { RaiseExceptionEvent(exception); CloseClientSocket(clientSocket); } finally { try { pendingChannelCountdown.Signal(); } catch (ObjectDisposedException) { } } } private void InitializePendingChannelCountdown() { Interlocked.Exchange(ref _pendingChannelCountdown, new CountdownEvent(1))?.Dispose(); } private static void CloseClientSocket(Socket clientSocket) { if (clientSocket.Connected) { try { clientSocket.Shutdown(SocketShutdown.Send); } catch (Exception) { } } clientSocket.Dispose(); } private void StopListener() { _listener?.Dispose(); ISession session = base.Session; if (session != null) { session.ErrorOccured -= Session_ErrorOccured; session.Disconnected -= Session_Disconnected; } } private void InternalStop(TimeSpan timeout) { _pendingChannelCountdown.Signal(); _pendingChannelCountdown.Wait(timeout); } private void InternalDispose(bool disposing) { if (disposing) { Socket listener = _listener; if (listener != null) { _listener = null; listener.Dispose(); } CountdownEvent pendingChannelCountdown = _pendingChannelCountdown; if (pendingChannelCountdown != null) { _pendingChannelCountdown = null; pendingChannelCountdown.Dispose(); } } } private void Session_Disconnected(object sender, EventArgs e) { ISession session = base.Session; if (session != null) { StopPort(session.ConnectionInfo.Timeout); } } private void Session_ErrorOccured(object sender, ExceptionEventArgs e) { ISession session = base.Session; if (session != null) { StopPort(session.ConnectionInfo.Timeout); } } private void Channel_Exception(object sender, ExceptionEventArgs e) { RaiseExceptionEvent(e.Exception); } } public class ForwardedPortRemote : ForwardedPort { private ForwardedPortStatus _status; private bool _requestStatus; private EventWaitHandle _globalRequestResponse = new AutoResetEvent(initialState: false); private CountdownEvent _pendingChannelCountdown; private bool _isDisposed; public override bool IsStarted => _status == ForwardedPortStatus.Started; public IPAddress BoundHostAddress { get; private set; } public string BoundHost => BoundHostAddress.ToString(); public uint BoundPort { get; private set; } public IPAddress HostAddress { get; private set; } public string Host => HostAddress.ToString(); public uint Port { get; private set; } public ForwardedPortRemote(IPAddress boundHostAddress, uint boundPort, IPAddress hostAddress, uint port) { ThrowHelper.ThrowIfNull(boundHostAddress, "boundHostAddress"); ThrowHelper.ThrowIfNull(hostAddress, "hostAddress"); boundPort.ValidatePort("boundPort"); port.ValidatePort("port"); BoundHostAddress = boundHostAddress; BoundPort = boundPort; HostAddress = hostAddress; Port = port; _status = ForwardedPortStatus.Stopped; } public ForwardedPortRemote(uint boundPort, string host, uint port) : this(string.Empty, boundPort, host, port) { } public ForwardedPortRemote(string boundHost, uint boundPort, string host, uint port) : this(Dns.GetHostAddresses(boundHost)[0], boundPort, Dns.GetHostAddresses(host)[0], port) { } protected override void StartPort() { if (!ForwardedPortStatus.ToStarting(ref _status)) { return; } InitializePendingChannelCountdown(); try { base.Session.RegisterMessage("SSH_MSG_REQUEST_FAILURE"); base.Session.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"); base.Session.RegisterMessage("SSH_MSG_CHANNEL_OPEN"); base.Session.RequestSuccessReceived += Session_RequestSuccess; base.Session.RequestFailureReceived += Session_RequestFailure; base.Session.ChannelOpenReceived += Session_ChannelOpening; base.Session.SendMessage(new TcpIpForwardGlobalRequestMessage(BoundHost, BoundPort)); base.Session.WaitOnHandle(_globalRequestResponse); if (!_requestStatus) { throw new SshException(string.Format(CultureInfo.CurrentCulture, "Port forwarding for '{0}' port '{1}' failed to start.", Host, Port)); } } catch (Exception) { _status = ForwardedPortStatus.Stopped; base.Session.RequestSuccessReceived -= Session_RequestSuccess; base.Session.RequestFailureReceived -= Session_RequestFailure; base.Session.ChannelOpenReceived -= Session_ChannelOpening; throw; } _status = ForwardedPortStatus.Started; } protected override void StopPort(TimeSpan timeout) { timeout.EnsureValidTimeout("timeout"); if (ForwardedPortStatus.ToStopping(ref _status)) { base.StopPort(timeout); base.Session.SendMessage(new CancelTcpIpForwardGlobalRequestMessage(BoundHost, BoundPort)); WaitHandle.WaitAny(new WaitHandle[2] { _globalRequestResponse, base.Session.MessageListenerCompleted }, timeout); base.Session.RequestSuccessReceived -= Session_RequestSuccess; base.Session.RequestFailureReceived -= Session_RequestFailure; base.Session.ChannelOpenReceived -= Session_ChannelOpening; _pendingChannelCountdown.Signal(); _pendingChannelCountdown.Wait(timeout); _status = ForwardedPortStatus.Stopped; } } protected override void CheckDisposed() { ThrowHelper.ThrowObjectDisposedIf(_isDisposed, this); } private void Session_ChannelOpening(object sender, MessageEventArgs<ChannelOpenMessage> e) { ChannelOpenMessage channelOpenMessage = e.Message; ChannelOpenInfo info2 = channelOpenMessage.Info; ForwardedTcpipChannelInfo info = info2 as ForwardedTcpipChannelInfo; if (info == null || !(info.ConnectedAddress == BoundHost) || info.ConnectedPort != BoundPort) { return; } if (!IsStarted) { base.Session.SendMessage(new ChannelOpenFailureMessage(channelOpenMessage.LocalChannelNumber, string.Empty, 1u)); return; } ThreadAbstraction.ExecuteThread(delegate { CountdownEvent pendingChannelCountdown = _pendingChannelCountdown; pendingChannelCountdown.AddCount(); try { RaiseRequestReceived(info.OriginatorAddress, info.OriginatorPort); using IChannelForwardedTcpip channelForwardedTcpip = base.Session.CreateChannelForwardedTcpip(channelOpenMessage.LocalChannelNumber, channelOpenMessage.InitialWindowSize, channelOpenMessage.MaximumPacketSize); channelForwardedTcpip.Exception += Channel_Exception; channelForwardedTcpip.Bind(new IPEndPoint(HostAddress, (int)Port), this); } catch (Exception exception) { RaiseExceptionEvent(exception); } finally { try { pendingChannelCountdown.Signal(); } catch (ObjectDisposedException) { } } }); } private void InitializePendingChannelCountdown() { Interlocked.Exchange(ref _pendingChannelCountdown, new CountdownEvent(1))?.Dispose(); } private void Channel_Exception(object sender, ExceptionEventArgs exceptionEventArgs) { RaiseExceptionEvent(exceptionEventArgs.Exception); } private void Session_RequestFailure(object sender, EventArgs e) { _requestStatus = false; _globalRequestResponse.Set(); } private void Session_RequestSuccess(object sender, MessageEventArgs<RequestSuccessMessage> e) { _requestStatus = true; if (BoundPort == 0) { BoundPort = (e.Message.BoundPort.HasValue ? e.Message.BoundPort.Value : 0u); } _globalRequestResponse.Set(); } protected override void Dispose(bool disposing) { if (_isDisposed) { return; } base.Dispose(disposing); if (disposing) { ISession session = base.Session; if (session != null) { base.Session = null; session.RequestSuccessReceived -= Session_RequestSuccess; session.RequestFailureReceived -= Session_RequestFailure; session.ChannelOpenReceived -= Session_ChannelOpening; } EventWaitHandle globalRequestResponse = _globalRequestResponse; if (globalRequestResponse != null) { _globalRequestResponse = null; globalRequestResponse.Dispose(); } CountdownEvent pendingChannelCountdown = _pendingChannelCountdown; if (pendingChannelCountdown != null) { _pendingChannelCountdown = null; pendingChannelCountdown.Dispose(); } } _isDisposed = true; } } internal sealed class ForwardedPortStatus { public static readonly ForwardedPortStatus Stopped = new ForwardedPortStatus(1, "Stopped"); public static readonly ForwardedPortStatus Stopping = new ForwardedPortStatus(2, "Stopping"); public static readonly ForwardedPortStatus Started = new ForwardedPortStatus(3, "Started"); public static readonly ForwardedPortStatus Starting = new ForwardedPortStatus(4, "Starting"); private readonly int _value; private readonly string _name; private ForwardedPortStatus(int value, string name) { _value = value; _name = name; } public override bool Equals(object obj) { if (this == obj) { return true; } if (!(obj is ForwardedPortStatus forwardedPortStatus)) { return false; } return forwardedPortStatus._value == _value; } public static bool operator ==(ForwardedPortStatus left, ForwardedPortStatus right) { return left?.Equals(right) ?? ((object)right == null); } public static bool operator !=(ForwardedPortStatus left, ForwardedPortStatus right) { return !(left == right); } public override int GetHashCode() { return _value; } public override string ToString() { return _name; } public static bool ToStopping(ref ForwardedPortStatus status) { ForwardedPortStatus forwardedPortStatus = Interlocked.CompareExchange(ref status, Stopping, Started); if (forwardedPortStatus == Stopping || forwardedPortStatus == Stopped) { return false; } if (status == Stopping) { return true; } forwardedPortStatus = Interlocked.CompareExchange(ref status, Stopping, Starting); if (forwardedPortStatus == Stopping || forwardedPortStatus == Stopped) { return false; } if (status == Stopping) { return true; } throw new InvalidOperationException($"Forwarded port cannot transition from '{forwardedPortStatus}' to '{Stopping}'."); } public static bool ToStarting(ref ForwardedPortStatus status) { ForwardedPortStatus forwardedPortStatus = Interlocked.CompareExchange(ref status, Starting, Stopped); if (forwardedPortStatus == Starting || forwardedPortStatus == Started) { return false; } if (status == Starting) { return true; } throw new InvalidOperationException($"Forwarded port cannot transition from '{forwardedPortStatus}' to '{Starting}'."); } } public class HashInfo { public int KeySize { get; private set; } public bool IsEncryptThenMAC { get; private set; } public Func<byte[], HashAlgorithm> HashAlgorithm { get; private set; } public HashInfo(int keySize, Func<byte[], HashAlgorithm> hash, bool isEncryptThenMAC = false) { HashInfo hashInfo = this; KeySize = keySize; HashAlgorithm = (byte[] key) => hash(key.Take(hashInfo.KeySize / 8)); IsEncryptThenMAC = isEncryptThenMAC; } } internal interface IAuthenticationMethod { string[] AllowedAuthentications { get; } string Name { get; } AuthenticationResult Authenticate(ISession session); } public interface IBaseClient : IDisposable { ConnectionInfo ConnectionInfo { get; } bool IsConnected { get; } TimeSpan KeepAliveInterval { get; set; } event EventHandler<ExceptionEventArgs> ErrorOccurred; event EventHandler<HostKeyEventArgs> HostKeyReceived; event EventHandler<SshIdentificationEventArgs>? ServerIdentificationReceived; void Connect(); Task ConnectAsync(CancellationToken cancellationToken); void Disconnect(); void SendKeepAlive(); } internal interface IClientAuthentication { void Authenticate(IConnectionInfoInternal connectionInfo, ISession session); } internal interface IConnectionInfo { TimeSpan ChannelCloseTimeout { get; } IDictionary<string, RequestInfo> ChannelRequests { get; } Encoding Encoding { get; } string Host { get; } int Port { get; } ProxyTypes ProxyType { get; } string ProxyHost { get; } int ProxyPort { get; } string ProxyUsername { get; } string ProxyPassword { get; } int RetryAttempts { get; } TimeSpan Timeout { get; } event EventHandler<AuthenticationBannerEventArgs> AuthenticationBanner; } internal interface IConnectionInfoInternal : IConnectionInfo { IList<IAuthenticationMethod> AuthenticationMethods { get; } void UserAuthenticationBannerReceived(object sender, MessageEventArgs<BannerMessage> e); IAuthenticationMethod CreateNoneAuthenticationMethod(); } public interface IForwardedPort : IDisposable { event EventHandler Closing; } public interface IPrivateKeySource { IReadOnlyCollection<HostAlgorithm> HostKeyAlgorithms { get; } } public interface IRemotePathTransformation { string Transform(string path); } internal interface IServiceFactory { IClientAuthentication CreateClientAuthentication(); INetConfSession CreateNetConfSession(ISession session, int operationTimeout); ISession CreateSession(ConnectionInfo connectionInfo, ISocketFactory socketFactory); ISftpSession CreateSftpSession(ISession session, int operationTimeout, Encoding encoding, ISftpResponseFactory sftpMessageFactory); PipeStream CreatePipeStream(); IKeyExchange CreateKeyExchange(IDictionary<string, Func<IKeyExchange>> clientAlgorithms, string[] serverAlgorithms); ISftpFileReader CreateSftpFileReader(string fileName, ISftpSession sftpSession, uint bufferSize); ISftpResponseFactory CreateSftpResponseFactory(); ShellStream CreateShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues, int bufferSize); ShellStream CreateShellStreamNoTerminal(ISession session, int bufferSize); IRemotePathTransformation CreateRemotePathDoubleQuoteTransformation(); IConnector CreateConnector(IConnectionInfo connectionInfo, ISocketFactory socketFactory); IProtocolVersionExchange CreateProtocolVersionExchange(); ISocketFactory CreateSocketFactory(); } internal interface ISession : IDisposable { IConnectionInfo ConnectionInfo { get; } bool IsConnected { get; } SemaphoreSlim SessionSemaphore { get; } WaitHandle MessageListenerCompleted { get; } event EventHandler<MessageEventArgs<ChannelCloseMessage>> ChannelCloseReceived; event EventHandler<MessageEventArgs<ChannelDataMessage>> ChannelDataReceived; event EventHandler<MessageEventArgs<ChannelEofMessage>> ChannelEofReceived; event EventHandler<MessageEventArgs<ChannelExtendedDataMessage>> ChannelExtendedDataReceived; event EventHandler<MessageEventArgs<ChannelFailureMessage>> ChannelFailureReceived; event EventHandler<MessageEventArgs<ChannelOpenConfirmationMessage>> ChannelOpenConfirmationReceived; event EventHandler<MessageEventArgs<ChannelOpenFailureMessage>> ChannelOpenFailureReceived; event EventHandler<MessageEventArgs<ChannelOpenMessage>> ChannelOpenReceived; event EventHandler<MessageEventArgs<ChannelRequestMessage>> ChannelRequestReceived; event EventHandler<MessageEventArgs<ChannelSuccessMessage>> ChannelSuccessReceived; event EventHandler<MessageEventArgs<ChannelWindowAdjustMessage>> ChannelWindowAdjustReceived; event EventHandler<EventArgs> Disconnected; event EventHandler<ExceptionEventArgs> ErrorOccured; event EventHandler<SshIdentificationEventArgs> ServerIdentificationReceived; event EventHandler<HostKeyEventArgs> HostKeyReceived; event EventHandler<MessageEventArgs<RequestSuccessMessage>> RequestSuccessReceived; event EventHandler<MessageEventArgs<RequestFailureMessage>> RequestFailureReceived; event EventHandler<MessageEventArgs<BannerMessage>> UserAuthenticationBannerReceived; void Connect(); Task ConnectAsync(CancellationToken cancellationToken); IChannelSession CreateChannelSession(); IChannelDirectTcpip CreateChannelDirectTcpip(); IChannelForwardedTcpip CreateChannelForwardedTcpip(uint remoteChannelNumber, uint remoteWindowSize, uint remoteChannelDataPacketSize); void Disconnect(); void OnDisconnecting(); void RegisterMessage(string messageName); void SendMessage(Message message); bool TrySendMessage(Message message); void UnRegisterMessage(string messageName); void WaitOnHandle(WaitHandle waitHandle); void WaitOnHandle(WaitHandle waitHandle, TimeSpan timeout); WaitResult TryWait(WaitHandle waitHandle, TimeSpan timeout, out Exception exception); WaitResult TryWait(WaitHandle waitHandle, TimeSpan timeout); } public interface ISftpClient : IBaseClient, IDisposable { uint BufferSize { get; set; } TimeSpan OperationTimeout { get; set; } int ProtocolVersion { get; } string WorkingDirectory { get; } void AppendAllLines(string path, IEnumerable<string> contents); void AppendAllLines(string path, IEnumerable<string> contents, Encoding encoding); void AppendAllText(string path, string contents); void AppendAllText(string path, string contents, Encoding encoding); StreamWriter AppendText(string path); StreamWriter AppendText(string path, Encoding encoding); IAsyncResult BeginDownloadFile(string path, Stream output); IAsyncResult BeginDownloadFile(string path, Stream output, AsyncCallback? asyncCallback); IAsyncResult BeginDownloadFile(string path, Stream output, AsyncCallback? asyncCallback, object? state, Action<ulong>? downloadCallback = null); IAsyncResult BeginListDirectory(string path, AsyncCallback? asyncCallback, object? state, Action<int>? listCallback = null); IAsyncResult BeginSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, AsyncCallback? asyncCallback, object? state); IAsyncResult BeginUploadFile(Stream input, string path); IAsyncResult BeginUploadFile(Stream input, string path, AsyncCallback? asyncCallback); IAsyncResult BeginUploadFile(Stream input, string path, AsyncCallback? asyncCallback, object? state, Action<ulong>? uploadCallback = null); IAsyncResult BeginUploadFile(Stream input, string path, bool canOverride, AsyncCallback? asyncCallback, object? state, Action<ulong>? uploadCallback = null); void ChangeDirectory(string path); Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default(CancellationToken)); void ChangePermissions(string path, short mode); SftpFileStream Create(string path); SftpFileStream Create(string path, int bufferSize); void CreateDirectory(string path); Task CreateDirectoryAsync(string path, CancellationToken cancellationToken = default(CancellationToken)); StreamWriter CreateText(string path); StreamWriter CreateText(string path, Encoding encoding); void Delete(string path); Task DeleteAsync(string path, CancellationToken cancellationToken = default(CancellationToken)); void DeleteDirectory(string path); Task DeleteDirectoryAsync(string path, CancellationToken cancellationToken = default(CancellationToken)); void DeleteFile(string path); Task DeleteFileAsync(string path, CancellationToken cancellationToken); void DownloadFile(string path, Stream output, Action<ulong>? downloadCallback = null); void EndDownloadFile(IAsyncResult asyncResult); IEnumerable<ISftpFile> EndListDirectory(IAsyncResult asyncResult); IEnumerable<FileInfo> EndSynchronizeDirectories(IAsyncResult asyncResult); void EndUploadFile(IAsyncResult asyncResult); bool Exists(string path); ISftpFile Get(string path); SftpFileAttributes GetAttributes(string path); DateTime GetLastAccessTime(string path); DateTime GetLastAccessTimeUtc(string path); DateTime GetLastWriteTime(string path); DateTime GetLastWriteTimeUtc(string path); SftpFileSystemInformation GetStatus(string path); Task<SftpFileSystemInformation> GetStatusAsync(string path, CancellationToken cancellationToken); IEnumerable<ISftpFile> ListDirectory(string path, Action<int>? listCallback = null); IAsyncEnumerable<ISftpFile> ListDirectoryAsync(string path, CancellationToken cancellationToken); SftpFileStream Open(string path, FileMode mode); SftpFileStream Open(string path, FileMode mode, FileAccess access); Task<SftpFileStream> OpenAsync(string path, FileMode mode, FileAccess access, CancellationToken cancellationToken); SftpFileStream OpenRead(string path); StreamReader OpenText(string path); SftpFileStream OpenWrite(string path); byte[] ReadAllBytes(string path); string[] ReadAllLines(string path); string[] ReadAllLines(string path, Encoding encoding); string ReadAllText(string path); string ReadAllText(string path, Encoding encoding); IEnumerable<string> ReadLines(string path); IEnumerable<string> ReadLines(string path, Encoding encoding); void RenameFile(string oldPath, string newPath); void RenameFile(string oldPath, string newPath, bool isPosix); Task RenameFileAsync(string oldPath, string newPath, CancellationToken cancellationToken); void SetLastAccessTime(string path, DateTime lastAccessTime); void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc); void SetLastWriteTime(string path, DateTime lastWriteTime); void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc); void SetAttributes(string path, SftpFileAttributes fileAttributes); void SymbolicLink(string path, string linkPath); IEnumerable<FileInfo> SynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern); void UploadFile(Stream input, string path, Action<ulong>? uploadCallback = null); void UploadFile(Stream input, string path, bool canOverride, Action<ulong>? uploadCallback = null); void WriteAllBytes(string path, byte[] bytes); void WriteAllLines(string path, IEnumerable<string> contents); void WriteAllLines(string path, IEnumerable<string> contents, Encoding encoding); void WriteAllLines(string path, string[] contents); void WriteAllLines(string path, string[] contents, Encoding encoding); void WriteAllText(string path, string contents); void WriteAllText(string path, string contents, Encoding encoding); } public interface ISshClient : IBaseClient, IDisposable { IEnumerable<ForwardedPort> ForwardedPorts { get; } void AddForwardedPort(ForwardedPort port); void RemoveForwardedPort(ForwardedPort port); SshCommand CreateCommand(string commandText); SshCommand CreateCommand(string commandText, Encoding encoding); SshCommand RunCommand(string commandText); Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint>? terminalModes, int bufferSize); Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModes); Shell CreateShell(Stream input, Stream output, Stream extendedOutput); Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint>? terminalModes, int bufferSize); Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModes); Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput); Shell CreateShellNoTerminal(Stream input, Stream output, Stream extendedOutput, int bufferSize = -1); ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize); ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary<TerminalModes, uint>? terminalModeValues); ShellStream CreateShellStreamNoTerminal(int bufferSize = -1); } internal interface ISubsystemSession : IDisposable { int OperationTimeout { get; set; } bool IsOpen { get; } void Connect(); void Disconnect(); void WaitOnHandle(WaitHandle waitHandle, int millisecondsTimeout); bool WaitOne(WaitHandle waitHandle, int millisecondsTimeout); int WaitAny(WaitHandle waitHandleA, WaitHandle waitHandleB, int millisecondsTimeout); int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout); WaitHandle[] CreateWaitHandleArray(params WaitHandle[] waitHandles); WaitHandle[] CreateWaitHandleArray(WaitHandle waitHandle1, WaitHandle waitHandle2); } public class KeyboardInteractiveAuthenticationMethod : AuthenticationMethod, IDisposable { private readonly RequestMessageKeyboardInteractive _requestMessage; private AuthenticationResult _authenticationResult = AuthenticationResult.Failure; private Session _session; private EventWaitHandle _authenticationCompleted = new AutoResetEvent(initialState: false); private Exception _exception; private bool _isDisposed; public override string Name => _requestMessage.MethodName; public event EventHandler<AuthenticationPromptEventArgs> AuthenticationPrompt; public KeyboardInteractiveAuthenticationMethod(string username) : base(username) { _requestMessage = new RequestMessageKeyboardInteractive(ServiceName.Connection, username); } public override AuthenticationResult Authenticate(Session session) { _session = session; session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived; session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived; session.UserAuthenticationInformationRequestReceived += Session_UserAuthenticationInformationRequestReceived; session.RegisterMessage("SSH_MSG_USERAUTH_INFO_REQUEST"); try { session.SendMessage(_requestMessage); session.WaitOnHandle(_authenticationCompleted); } finally { session.UnRegisterMessage("SSH_MSG_USERAUTH_INFO_REQUEST"); session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived; session.UserAuthenticationFailureReceived -= Session_UserAuthenticationFailureReceived; session.UserAuthenticationInformationRequestReceived -= Session_UserAuthenticationInformationRequestReceived; } if (_exception != null) { ExceptionDispatchInfo.Capture(_exception).Throw(); } return _authenticationResult; } private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs<SuccessMessage> e) { _authenticationResult = AuthenticationResult.Success; _authenticationCompleted.Set(); } private void Session_UserAuthenticationFailureReceived(object sender, MessageEventArgs<FailureMessage> e) { if (e.Message.PartialSuccess) { _authenticationResult = AuthenticationResult.PartialSuccess; } else { _authenticationResult = AuthenticationResult.Failure; } base.AllowedAuthentications = e.Message.AllowedAuthentications; _authenticationCompleted.Set(); } private void Session_UserAuthenticationInformationRequestReceived(object sender, MessageEventArgs<InformationRequestMessage> e) { InformationRequestMessage message = e.Message; AuthenticationPromptEventArgs eventArgs = new AuthenticationPromptEventArgs(base.Username, message.Instruction, message.Language, message.Prompts); ThreadAbstraction.ExecuteThread(delegate { try { this.AuthenticationPrompt?.Invoke(this, eventArgs); InformationResponseMessage informationResponseMessage = new InformationResponseMessage(); foreach (AuthenticationPrompt item in eventArgs.Prompts.OrderBy((AuthenticationPrompt r) => r.Id)) { if (item.Response == null) { throw new SshAuthenticationException("AuthenticationPrompt.Response is null for prompt \"" + item.Request + "\". You can set this by subscribing to KeyboardInteractiveAuthenticationMethod.AuthenticationPrompt and inspecting the Prompts property of the event args."); } informationResponseMessage.Responses.Add(item.Response); } _session.SendMessage(informationResponseMessage); } catch (Exception exception) { _exception = exception; _authenticationCompleted.Set(); } }); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_isDisposed && disposing) { EventWaitHandle authenticationCompleted = _authenticationCompleted; if (authenticationCompleted != null) { _authenticationCompleted = null; authenticationCompleted.Dispose(); } _isDisposed = true; } } } public class KeyboardInteractiveConnectionInfo : ConnectionInfo, IDisposable { private bool _isDisposed; public event EventHandler<AuthenticationPromptEventArgs> AuthenticationPrompt; public KeyboardInteractiveConnectionInfo(string host, string username) : this(host, 22, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty) { } public KeyboardInteractiveConnectionInfo(string host, int port, string username) : this(host, port, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty) { } public KeyboardInteractiveConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort) : this(host, port, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty) { } public KeyboardInteractiveConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername) : this(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty) { } public KeyboardInteractiveConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort) : this(host, 22, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty) { } public KeyboardInteractiveConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername) : this(host, 22, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty) { } public KeyboardInteractiveConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword) : this(host, 22, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword) { } public KeyboardInteractiveConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword) : base(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, new KeyboardInteractiveAuthenticationMethod(username)) { foreach (AuthenticationMethod authenticationMethod in base.AuthenticationMethods) { if (authenticationMethod is KeyboardInteractiveAuthenticationMethod keyboardInteractiveAuthenticationMethod) { keyboardInteractiveAuthenticationMethod.AuthenticationPrompt += AuthenticationMethod_AuthenticationPrompt; } } } private void AuthenticationMethod_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) { this.AuthenticationPrompt?.Invoke(sender, e); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_isDisposed || !disposing) { return; } if (base.AuthenticationMethods != null) { foreach (AuthenticationMethod authenticationMethod in base.AuthenticationMethods) { if (authenticationMethod is IDisposable disposable) { disposable.Dispose(); } } } _isDisposed = true; } } public class MessageEventArgs<T> : EventArgs { public T Message { get; private set; } public MessageEventArgs(T message) { ThrowHelper.ThrowIfNull(message, "message"); Message = message; } } public class NetConfClient : BaseClient { private int _operationTimeout; private INetConfSession? _netConfSession; public TimeSpan OperationTimeout { get { return TimeSpan.FromMilliseconds(_operationTimeout); } set { _operationTimeout = value.AsTimeout("OperationTimeout"); } } internal INetConfSession? NetConfSession => _netConfSession; public XmlDocument ServerCapabilities { get { if (_netConfSession == null) { throw new SshConnectionException("Client not connected."); } return _netConfSession.ServerCapabilities; } } public XmlDocument ClientCapabilities { get { if (_netConfSession == null) { throw new SshConnectionException("Client not connected."); } return _netConfSession.ClientCapabilities; } } public bool AutomaticMessageIdHandling { get; set; } public NetConfClient(ConnectionInfo connectionInfo) : this(connectionInfo, ownsConnectionInfo: false) { } public NetConfClient(string host, int port, string username, string password) : this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true) { } public NetConfClient(string host, string username, string password) : this(host, 22, username, password) { } public NetConfClient(string host, int port, string username, params IPrivateKeySource[] keyFiles) : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true) { } public NetConfClient(string host, string username, params IPrivateKeySource[] keyFiles) : this(host, 22, username, keyFiles) { } private NetConfClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo) : this(connectionInfo, ownsConnectionInfo, new ServiceFactory()) { } internal NetConfClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory) : base(connectionInfo, ownsConnectionInfo, serviceFactory) { _operationTimeout = -1; AutomaticMessageIdHandling = true; } public XmlDocument SendReceiveRpc(XmlDocument rpc) { if (_netConfSession == null) { throw new SshConnectionException("Client not connected."); } return _netConfSession.SendReceiveRpc(rpc, AutomaticMessageIdHandling); } public XmlDocument SendReceiveRpc(string xml) { XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(xml); return SendReceiveRpc(xmlDocument); } public XmlDocument SendCloseRpc() { if (_netConfSession == null) { throw new SshConnectionException("Client not connected."); } XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rpc message-id=\"6666\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><close-session/></rpc>"); return _netConfSession.SendReceiveRpc(xmlDocument, AutomaticMessageIdHandling); } protected override void OnConnected() { base.OnConnected(); _netConfSession = CreateAndConnectNetConfSession(); } protected override void OnDisconnecting() { base.OnDisconnecting(); _netConfSession?.Disconnect(); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing && _netConfSession != null) { _netConfSession.Dispose(); _netConfSession = null; } } private INetConfSession CreateAndConnectNetConfSession() { INetConfSession netConfSession = base.ServiceFactory.CreateNetConfSession(base.Session, _operationTimeout); try { netConfSession.Connect(); return netConfSession; } catch { netConfSession.Dispose(); throw; } } } public class NoneAuthenticationMethod : AuthenticationMethod, IDisposable { private AuthenticationResult _authenticationResult = AuthenticationResult.Failure; private EventWaitHandle _authenticationCompleted = new AutoResetEvent(initialState: false); private bool _isDisposed; public override string Name => "none"; public NoneAuthenticationMethod(string username) : base(username) { } public override AuthenticationResult Authenticate(Session session) { ThrowHelper.ThrowIfNull(session, "session"); session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived; session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived; try { session.SendMessage(new RequestMessageNone(ServiceName.Connection, base.Username)); session.WaitOnHandle(_authenticationCompleted); } finally { session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived; session.UserAuthenticationFailureReceived -= Session_UserAuthenticationFailureReceived; } return _authenticationResult; } private void Session_UserAuthenticationSuccessReceived(object sender, MessageEventArgs<SuccessMessage> e) { _authenticationResult = AuthenticationResult.Success; _authenticationCompleted.Set(); } private void Session_UserAuthenticationFailureReceived(object sender, MessageEventArgs<FailureMessage> e) { if (e.Message.PartialSuccess) { _authenticationResult = AuthenticationResult.PartialSuccess; } else { _authenticationResult = AuthenticationResult.Failure; } base.AllowedAuthentications = e.Message.AllowedAuthentications; _authenticationCompleted.Set(); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_isDisposed && disposing) { EventWaitHandle authenticationCompleted = _authenticationCompleted; if (authenticationCompleted != null) { authenticationCompleted.Dispose(); _authenticationCompleted = null; } _isDisposed = true; } } } public class PasswordAuthenticationMethod : AuthenticationMethod, IDisposable { private readonly RequestMessagePassword _requestMessage; private readonly byte[] _password; private AuthenticationResult _authenticationResult = AuthenticationResult.Failure; private Session _session; private EventWaitHandle _authenticationCompleted = new AutoResetEvent(initialState: false); private Exception _exception; private bool _isDisposed; public override string Name => _requestMessage.MethodName; internal byte[] Password => _password; public event EventHandler<AuthenticationPasswordChangeEventArgs> PasswordExpired; public PasswordAuthenticationMethod(string username, string password) : this(username, Encoding.UTF8.GetBytes(password)) { } public PasswordAuthenticationMethod(string username, byte[] password) : base(username) { ThrowHelper.ThrowIfNull(password, "password"); _password = password; _requestMessage = new RequestMessagePassword(ServiceName.Connection, base.Username, _password); } public override AuthenticationResult Authenticate(Session session) { ThrowHelper.ThrowIfNull(session, "session"); _session = session; session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessReceived; session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived; session.UserAuthenticationPasswordChangeRequiredReceived += Session_UserAuthenticationPasswordChangeRequiredReceived; try { session.RegisterMessage("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ"); session.SendMessage(_requestMessage); session.WaitOnHandle(_authenticationCompleted); } finally { session.UnRegisterMessage("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ"); session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived; session.UserAuthenticationFailur