using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; namespace Renci.SshNet { public partial class Session { private const byte Null = 0x00; private const byte CarriageReturn = 0x0d; private const byte LineFeed = 0x0a; private static readonly TimeSpan Infinite = new TimeSpan(0, 0, 0, 0, -1); private readonly AutoResetEvent _connectEvent = new AutoResetEvent(false); private readonly AutoResetEvent _sendEvent = new AutoResetEvent(false); private readonly AutoResetEvent _receiveEvent = new AutoResetEvent(false); /// /// Gets a value indicating whether the socket is connected. /// /// true if the socket is connected; otherwise, false partial void IsSocketConnected(ref bool isConnected) { isConnected = (_socket != null && _socket.Connected); } /// /// Establishes a socket connection to the specified host and port. /// /// The host name of the server to connect to. /// The port to connect to. /// The connection failed to establish within the configured . /// An error occurred trying to establish the connection. partial void SocketConnect(string host, int port) { var timeout = ConnectionInfo.Timeout; var ipAddress = host.GetIPAddress(); var ep = new IPEndPoint(ipAddress, port); _socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); var args = CreateSocketAsyncEventArgs(_connectEvent); if (_socket.ConnectAsync(args)) { if (!_connectEvent.WaitOne(timeout)) throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture, "Connection failed to establish within {0:F0} milliseconds.", timeout.TotalMilliseconds)); } if (args.SocketError != SocketError.Success) throw new SocketException((int) args.SocketError); } /// /// Closes the socket. /// /// /// This method will wait up to 10 seconds to send any remaining data. /// partial void SocketDisconnect() { _socket.Close(10); } /// /// Performs a blocking read on the socket until a line is read. /// /// The line read from the socket, or null when the remote server has shutdown and all data has been received. /// A that represents the time to wait until a line is read. /// The read has timed-out. /// An error occurred when trying to access the socket. partial void SocketReadLine(ref string response, TimeSpan timeout) { var encoding = new ASCIIEncoding(); var buffer = new List(); var data = new byte[1]; // 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 do { var args = CreateSocketAsyncEventArgs(_receiveEvent, data, 0, data.Length); if (_socket.ReceiveAsync(args)) { if (!_receiveEvent.WaitOne(timeout)) throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds)); } if (args.SocketError != SocketError.Success) throw new SocketException((int) args.SocketError); if (args.BytesTransferred == 0) // the remote server shut down the socket break; buffer.Add(data[0]); } while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == LineFeed || buffer[buffer.Count - 1] == Null))); if (buffer.Count == 0) response = null; else if (buffer.Count == 1 && buffer[buffer.Count - 1] == 0x00) // return an empty version string if the buffer consists of only a 0x00 character response = string.Empty; else if (buffer.Count > 1 && buffer[buffer.Count - 2] == CarriageReturn) // strip trailing CRLF response = encoding.GetString(buffer.ToArray(), 0, buffer.Count - 2); else if (buffer.Count > 1 && buffer[buffer.Count - 1] == LineFeed) // strip trailing LF response = encoding.GetString(buffer.ToArray(), 0, buffer.Count - 1); else response = encoding.GetString(buffer.ToArray(), 0, buffer.Count); } /// /// Performs a blocking read on the socket until bytes are received. /// /// The number of bytes to read. /// The buffer to read to. /// The socket is closed. /// The read has timed-out. /// The read failed. partial void SocketRead(int length, ref byte[] buffer) { var timeout = Infinite; var totalBytesReceived = 0; // how many bytes are already received do { var args = CreateSocketAsyncEventArgs(_receiveEvent, buffer, totalBytesReceived, length - totalBytesReceived); if (_socket.ReceiveAsync(args)) { if (!_receiveEvent.WaitOne(timeout)) // currently we wait indefinitely, so this exception will never be thrown // but let's leave this here anyway as we may revisit this later throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds)); } switch (args.SocketError) { case SocketError.WouldBlock: case SocketError.IOPending: case SocketError.NoBufferSpaceAvailable: // socket buffer is probably full, wait and try again Thread.Sleep(30); break; case SocketError.Success: var bytesReceived = args.BytesTransferred; if (bytesReceived > 0) { totalBytesReceived += bytesReceived; continue; } if (_isDisconnecting) throw new SshConnectionException( "An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost); throw new SshConnectionException("An established connection was aborted by the server.", DisconnectReason.ConnectionLost); default: throw new SocketException((int) args.SocketError); } } while (totalBytesReceived < length); } /// /// Writes the specified data to the server. /// /// The data to write to the server. /// The write has timed-out. /// The write failed. partial void SocketWrite(byte[] data) { var timeout = ConnectionInfo.Timeout; var totalBytesSent = 0; // how many bytes are already sent var totalBytesToSend = data.Length; do { var args = CreateSocketAsyncEventArgs(_sendEvent, data, 0, totalBytesToSend - totalBytesSent); if (_socket.SendAsync(args)) { if (!_sendEvent.WaitOne(timeout)) throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture, "Socket write operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds)); } switch (args.SocketError) { case SocketError.WouldBlock: case SocketError.IOPending: case SocketError.NoBufferSpaceAvailable: // socket buffer is probably full, wait and try again Thread.Sleep(30); break; case SocketError.Success: totalBytesSent += args.BytesTransferred; break; default: throw new SocketException((int) args.SocketError); } } while (totalBytesSent < totalBytesToSend); } partial void ExecuteThread(Action action) { ThreadPool.QueueUserWorkItem(o => action()); } partial void InternalRegisterMessage(string messageName) { lock (_messagesMetadata) { foreach (var item in from m in _messagesMetadata where m.Name == messageName select m) { item.Enabled = true; item.Activated = true; } } } partial void InternalUnRegisterMessage(string messageName) { lock (_messagesMetadata) { foreach (var item in from m in _messagesMetadata where m.Name == messageName select m) { item.Enabled = false; item.Activated = false; } } } private SocketAsyncEventArgs CreateSocketAsyncEventArgs(EventWaitHandle waitHandle) { var args = new SocketAsyncEventArgs(); args.UserToken = _socket; args.RemoteEndPoint = _socket.RemoteEndPoint; args.Completed += (sender, eventArgs) => waitHandle.Set(); return args; } private SocketAsyncEventArgs CreateSocketAsyncEventArgs(EventWaitHandle waitHandle, byte[] data, int offset, int count) { var args = CreateSocketAsyncEventArgs(waitHandle); args.SetBuffer(data, offset, count); return args; } } }