| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 | 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.Text;using System.Text.RegularExpressions;namespace Renci.SshNet.Connection{    /// <summary>    /// Handles the SSH protocol version exchange.    /// </summary>    internal class ProtocolVersionExchange : IProtocolVersionExchange    {        private const byte Null = 0x00;#if FEATURE_REGEX_COMPILE        private static readonly Regex ServerVersionRe = new Regex("^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+?)([ ](?<comments>.+))?$", RegexOptions.Compiled);#else        private static readonly Regex ServerVersionRe = new Regex("^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+?)([ ](?<comments>.+))?$");#endif        /// <summary>        /// Performs the SSH protocol version exchange.        /// </summary>        /// <param name="clientVersion">The identification string of the SSH client.</param>        /// <param name="socket">A <see cref="Socket"/> connected to the server.</param>        /// <param name="timeout">The maximum time to wait for the server to respond.</param>        /// <returns>        /// The SSH identification of the server.        /// </returns>        public SshIdentification Start(string clientVersion, Socket socket, TimeSpan timeout)        {            // Immediately send the identification string since the spec states both sides MUST send an identification string            // when the connection has been established            SocketAbstraction.Send(socket, Encoding.UTF8.GetBytes(clientVersion + "\x0D\x0A"));            var bytesReceived = new List<byte>();            // Get server version from the server,            // ignore text lines which are sent before if any            while (true)            {                var line = SocketReadLine(socket, timeout, bytesReceived);                if (line == null)                {                    if (bytesReceived.Count == 0)                    {                        throw new SshConnectionException("Server response does not contain SSH protocol identification. Connection to remote server was closed before any data was received.", DisconnectReason.ConnectionLost);                    }                    throw new SshConnectionException(string.Format("Server response does not contain SSH protocol identification:{0}{1}", Environment.NewLine, PacketDump.Create(bytesReceived, 2)),                                                     DisconnectReason.ProtocolError);                }                var identificationMatch = ServerVersionRe.Match(line);                if (identificationMatch.Success)                {                    return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"),                                                 GetGroupValue(identificationMatch, "softwareversion"),                                                 GetGroupValue(identificationMatch, "comments"));                }            }        }        private static string GetGroupValue(Match match, string groupName)        {            var commentsGroup = match.Groups[groupName];            if (commentsGroup.Success)            {                return commentsGroup.Value;            }            return null;        }        /// <summary>        /// Performs a blocking read on the socket until a line is read.        /// </summary>        /// <param name="socket">The <see cref="Socket"/> to read from.</param>        /// <param name="timeout">A <see cref="TimeSpan"/> that represents the time to wait until a line is read.</param>        /// <param name="buffer">A <see cref="List{Byte}"/> to which read bytes will be added.</param>        /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>        /// <exception cref="SocketException">An error occurred when trying to access the socket.</exception>        /// <returns>        /// The line read from the socket, or <c>null</c> when the remote server has shutdown and all data has been received.        /// </returns>        private static string SocketReadLine(Socket socket, TimeSpan timeout, List<byte> buffer)        {            var data = new byte[1];            var startPosition = buffer.Count;            // Read data one byte at a time to find end of line and leave any unhandled information in the buffer            // to be processed by subsequent invocations.            while (true)            {                var bytesRead = SocketAbstraction.Read(socket, data, 0, data.Length, timeout);                if (bytesRead == 0)                {                    // The remote server shut down the socket.                    break;                }                var byteRead = data[0];                buffer.Add(byteRead);                // The null character MUST NOT be sent                if (byteRead == Null)                {                    throw new SshConnectionException(string.Format(CultureInfo.InvariantCulture,                                                                   "The identification string contains a null character at position 0x{0:X8}:{1}{2}",                                                                   buffer.Count,                                                                   Environment.NewLine,                                                                   PacketDump.Create(buffer.ToArray(), 2)));                }                if (byteRead == Session.LineFeed && buffer.Count > startPosition + 1 && buffer[buffer.Count - 2] == Session.CarriageReturn)                {                    // Return current line without CRLF                    return Encoding.UTF8.GetString(buffer.ToArray(), startPosition, buffer.Count - (startPosition + 2));                }            }            return null;        }    }}
 |