Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LethalSSH v0.1.1
baer1.LethalSSH.dll
Decompiled a year 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 a year 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() }, { "[email protected]", () => 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)) }, { "[email protected]", new CipherInfo(128, (byte[] key, byte[] iv) => new AesGcmCipher(key, iv, 4), isAead: true) }, { "[email protected]", new CipherInfo(256, (byte[] key, byte[] iv) => new AesGcmCipher(key, iv, 4), isAead: true) }, { "[email protected]", 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)) }, { "[email protected]", new HashInfo(256, (byte[] key) => new HMACSHA256(key), isEncryptThenMAC: true) }, { "[email protected]", new HashInfo(512, (byte[] key) => new HMACSHA512(key), isEncryptThenMAC: true) }, { "[email protected]", new HashInfo(160, (byte[] key) => new HMACSHA1(key), isEncryptThenMAC: true) } }; Dictionary<string, Func<byte[], KeyHostAlgorithm>> hostAlgs = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>(); hostAlgs.Add("[email protected]", delegate(byte[] data) { Certificate certificate8 = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", certificate8, hostAlgs); }); hostAlgs.Add("[email protected]", delegate(byte[] data) { Certificate certificate7 = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", certificate7, hostAlgs); }); hostAlgs.Add("[email protected]", delegate(byte[] data) { Certificate certificate6 = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", certificate6, hostAlgs); }); hostAlgs.Add("[email protected]", delegate(byte[] data) { Certificate certificate5 = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", certificate5, hostAlgs); }); hostAlgs.Add("[email protected]", delegate(byte[] data) { Certificate certificate4 = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", certificate4, new RsaDigitalSignature((RsaKey)certificate4.Key, HashAlgorithmName.SHA512), hostAlgs); }); hostAlgs.Add("[email protected]", delegate(byte[] data) { Certificate certificate3 = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", certificate3, new RsaDigitalSignature((RsaKey)certificate3.Key, HashAlgorithmName.SHA256), hostAlgs); }); hostAlgs.Add("[email protected]", delegate(byte[] data) { Certificate certificate2 = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", certificate2, hostAlgs); }); hostAlgs.Add("[email protected]", delegate(byte[] data) { Certificate certificate = new Certificate(data); return new CertificateHostAlgorithm("[email protected]", 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 }, { "[email protected]", () => 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() }, { "[email protected]", new EndOfWriteRequestInfo() }, { "[email protected]", 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