<PackageReference Include="SSH.NET" Version="2024.1.0" />

ProtocolVersionExchange

Handles the SSH protocol version exchange.
using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; using System; using System.Collections.Generic; using System.Globalization; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Renci.SshNet.Connection { internal sealed class ProtocolVersionExchange : IProtocolVersionExchange { private const byte Null = 0; private const string ServerVersionPattern = "^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+?)([ ](?<comments>.+))?$"; private static readonly Regex ServerVersionRegex = new Regex("^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+?)([ ](?<comments>.+))?$", RegexOptions.ExplicitCapture | RegexOptions.Compiled); public SshIdentification Start(string clientVersion, Socket socket, TimeSpan timeout) { SocketAbstraction.Send(socket, Encoding.UTF8.GetBytes(clientVersion + "\r\n")); List<byte> list = new List<byte>(); Match match; do { string text = SocketReadLine(socket, timeout, list); if (text == null) { if (list.Count == 0) throw CreateConnectionLostException(); throw CreateServerResponseDoesNotContainIdentification(list); } match = ServerVersionRegex.Match(text); } while (!match.Success); return new SshIdentification(GetGroupValue(match, "protoversion"), GetGroupValue(match, "softwareversion"), GetGroupValue(match, "comments")); } [AsyncStateMachine(typeof(<StartAsync>d__4))] public Task<SshIdentification> StartAsync(string clientVersion, Socket socket, CancellationToken cancellationToken) { <StartAsync>d__4 stateMachine = default(<StartAsync>d__4); stateMachine.<>t__builder = AsyncTaskMethodBuilder<SshIdentification>.Create(); stateMachine.clientVersion = clientVersion; stateMachine.socket = socket; stateMachine.cancellationToken = cancellationToken; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; } private static string GetGroupValue(Match match, string groupName) { Group group = match.Groups[groupName]; if (group.Success) return group.Value; return null; } private static string SocketReadLine(Socket socket, TimeSpan timeout, List<byte> buffer) { byte[] array = new byte[1]; int count = buffer.Count; while (SocketAbstraction.Read(socket, array, 0, array.Length, timeout) != 0) { byte b = array[0]; buffer.Add(b); switch (b) { case 0: throw CreateServerResponseContainsNullCharacterException(buffer); case 10: if (buffer.Count > count + 1 && buffer[buffer.Count - 2] == 13) return Encoding.UTF8.GetString(buffer.ToArray(), count, buffer.Count - (count + 2)); return Encoding.UTF8.GetString(buffer.ToArray(), count, buffer.Count - (count + 1)); } } return null; } [AsyncStateMachine(typeof(<SocketReadLineAsync>d__7))] private static Task<string> SocketReadLineAsync(Socket socket, List<byte> buffer, CancellationToken cancellationToken) { <SocketReadLineAsync>d__7 stateMachine = default(<SocketReadLineAsync>d__7); stateMachine.<>t__builder = AsyncTaskMethodBuilder<string>.Create(); stateMachine.socket = socket; stateMachine.buffer = buffer; stateMachine.cancellationToken = cancellationToken; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; } private static SshConnectionException CreateConnectionLostException() { return new SshConnectionException(string.Format(CultureInfo.InvariantCulture, "The server response does not contain an SSH identification string.{0}The connection to the remote server was closed before any data was received.{0}{0}More information on the Protocol Version Exchange is available here:{0}https://tools.ietf.org/html/rfc4253#section-4.2", Environment.NewLine), DisconnectReason.ConnectionLost); } private static SshConnectionException CreateServerResponseContainsNullCharacterException(List<byte> buffer) { throw new SshConnectionException(string.Format(CultureInfo.InvariantCulture, "The server response contains a null character at position 0x{0:X8}:{1}{1}{2}{1}{1}A server must not send a null character before the Protocol Version Exchange is complete.{1}{1}More information is available here:{1}https://tools.ietf.org/html/rfc4253#section-4.2", buffer.Count, Environment.NewLine, PacketDump.Create(buffer.ToArray(), 2))); } private static SshConnectionException CreateServerResponseDoesNotContainIdentification(List<byte> bytesReceived) { throw new SshConnectionException(string.Format(CultureInfo.InvariantCulture, "The server response does not contain an SSH identification string:{0}{0}{1}{0}{0}More information on the Protocol Version Exchange is available here:{0}https://tools.ietf.org/html/rfc4253#section-4.2", Environment.NewLine, PacketDump.Create(bytesReceived, 2)), DisconnectReason.ProtocolError); } } }