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 static readonly Regex ServerVersionRe = new Regex("^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+?)([ ](?<comments>.+))?$", 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 = ServerVersionRe.Match(text);
} while (!match.Success);
return new SshIdentification(GetGroupValue(match, "protoversion"), GetGroupValue(match, "softwareversion"), GetGroupValue(match, "comments"));
}
[AsyncStateMachine(typeof(<StartAsync>d__3))]
public Task<SshIdentification> StartAsync(string clientVersion, Socket socket, CancellationToken cancellationToken)
{
<StartAsync>d__3 stateMachine = default(<StartAsync>d__3);
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__6))]
private static Task<string> SocketReadLineAsync(Socket socket, List<byte> buffer, CancellationToken cancellationToken)
{
<SocketReadLineAsync>d__6 stateMachine = default(<SocketReadLineAsync>d__6);
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);
}
}
}