/// <summary> /// Binds the channel to the specified endpoint. /// </summary> /// <param name="remoteEndpoint">The endpoint to connect to.</param> /// <param name="forwardedPort">The forwarded port for which the channel is opened.</param> public void Bind(IPEndPoint remoteEndpoint, IForwardedPort forwardedPort) { if (!IsConnected) { throw new SshException("Session is not connected."); } _forwardedPort = forwardedPort; _forwardedPort.Closing += ForwardedPort_Closing; // Try to connect to the socket try { _socket = SocketAbstraction.Connect(remoteEndpoint, ConnectionInfo.Timeout); // send channel open confirmation message SendMessage(new ChannelOpenConfirmationMessage(RemoteChannelNumber, LocalWindowSize, LocalPacketSize, LocalChannelNumber)); } catch (Exception exp) { // send channel open failure message SendMessage(new ChannelOpenFailureMessage(RemoteChannelNumber, exp.ToString(), ChannelOpenFailureMessage.ConnectFailed, "en")); throw; } var buffer = new byte[RemotePacketSize]; SocketAbstraction.ReadContinuous(_socket, buffer, 0, buffer.Length, SendData); }
/// <summary> /// Reads a null terminated string from a socket. /// </summary> /// <param name="socket">The <see cref="Socket"/> to read from.</param> /// <param name="timeout">The timeout to apply to individual reads.</param> /// <returns> /// The <see cref="string"/> read, or <c>null</c> when the socket was closed. /// </returns> private static string ReadString(Socket socket, TimeSpan timeout) { var text = new StringBuilder(); var buffer = new byte[1]; while (true) { if (SocketAbstraction.Read(socket, buffer, 0, 1, timeout) == 0) { // SOCKS client closed connection return(null); } var byteRead = buffer[0]; if (byteRead == 0) { // end of the string break; } var c = (char)byteRead; text.Append(c); } return(text.ToString()); }
private void EstablishSocks4Connection(Socket client) { var userNameBytes = Encoding.ASCII.GetBytes(_userName); var addressBytes = _remoteEndpoint.Address.GetAddressBytes(); var portBytes = BitConverter.GetBytes((ushort)_remoteEndpoint.Port).Reverse().ToArray(); _client.Connect(_endpoint); // send SOCKS version client.Send(new byte[] { 0x04 }, 0, 1, SocketFlags.None); // send command byte client.Send(new byte[] { 0x00 }, 0, 1, SocketFlags.None); // send port client.Send(portBytes, 0, portBytes.Length, SocketFlags.None); // send address client.Send(addressBytes, 0, addressBytes.Length, SocketFlags.None); // send user name client.Send(userNameBytes, 0, userNameBytes.Length, SocketFlags.None); // terminate user name with null client.Send(new byte[] { 0x00 }, 0, 1, SocketFlags.None); var buffer = new byte[8]; var bytesRead = SocketAbstraction.Read(client, buffer, 0, buffer.Length, TimeSpan.FromMilliseconds(500)); Assert.AreEqual(buffer.Length, bytesRead); // wait until SOCKS client is bound to channel Assert.IsTrue(_channelBindStarted.WaitOne(TimeSpan.FromMilliseconds(200))); }
private bool HandleSocks4(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout) { var commandCode = SocketAbstraction.ReadByte(socket, timeout); if (commandCode == 0) { // SOCKS client closed connection return(false); } // TODO: See what need to be done depends on the code var portBuffer = new byte[2]; if (SocketAbstraction.Read(socket, portBuffer, 0, portBuffer.Length, timeout) == 0) { // SOCKS client closed connection return(false); } var port = (uint)(portBuffer[0] * 256 + portBuffer[1]); var ipBuffer = new byte[4]; if (SocketAbstraction.Read(socket, ipBuffer, 0, ipBuffer.Length, timeout) == 0) { // SOCKS client closed connection return(false); } var ipAddress = new IPAddress(ipBuffer); var username = ReadString(socket, timeout); if (username == null) { // SOCKS client closed connection return(false); } var host = ipAddress.ToString(); RaiseRequestReceived(host, port); channel.Open(host, port, this, socket); SocketAbstraction.SendByte(socket, 0x00); if (channel.IsOpen) { SocketAbstraction.SendByte(socket, 0x5a); SocketAbstraction.Send(socket, portBuffer, 0, portBuffer.Length); SocketAbstraction.Send(socket, ipBuffer, 0, ipBuffer.Length); return(true); } // signal that request was rejected or failed SocketAbstraction.SendByte(socket, 0x5b); return(false); }
/// <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="readTimeout">A <see cref="TimeSpan"/> that represents the time to wait until a line is read.</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 readTimeout) { var encoding = SshData.Ascii; var buffer = new List <byte>(); 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 bytesRead = SocketAbstraction.Read(socket, data, 0, data.Length, readTimeout); if (bytesRead == 0) { // the remote server shut down the socket break; } var b = data[0]; buffer.Add(b); if (b == Session.LineFeed && buffer.Count > 1 && buffer[buffer.Count - 2] == Session.CarriageReturn) { // Return line without CRLF return(encoding.GetString(buffer.ToArray(), 0, buffer.Count - 2)); } }while (true); if (buffer.Count == 0) { return(null); } return(encoding.GetString(buffer.ToArray(), 0, buffer.Count)); }
public void ShouldReturnFalseWhenSocketIsNull() { const Socket socket = null; var actual = SocketAbstraction.CanWrite(socket); Assert.IsFalse(actual); }
/// <summary> /// Called when channel data is received. /// </summary> /// <param name="data">The data.</param> protected override void OnData(byte[] data) { base.OnData(data); var socket = _socket; if (socket != null && socket.Connected) { SocketAbstraction.Send(socket, data, 0, data.Length); } }
/// <summary> /// Performs a blocking read on the socket until <paramref name="length"/> bytes are received. /// </summary> /// <param name="socket">The <see cref="Socket"/> to read from.</param> /// <param name="buffer">An array of type <see cref="byte"/> that is the storage location for the received data.</param> /// <param name="offset">The position in <paramref name="buffer"/> parameter to store the received data.</param> /// <param name="length">The number of bytes to read.</param> /// <param name="readTimeout">The maximum time to wait until <paramref name="length"/> bytes have been received.</param> /// <returns> /// The number of bytes read. /// </returns> /// <exception cref="SshConnectionException">The socket is closed.</exception> /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception> /// <exception cref="SocketException">The read failed.</exception> protected static int SocketRead(Socket socket, byte[] buffer, int offset, int length, TimeSpan readTimeout) { var bytesRead = SocketAbstraction.Read(socket, buffer, offset, length, readTimeout); if (bytesRead == 0) { throw new SshConnectionException("An established connection was aborted by the server.", DisconnectReason.ConnectionLost); } return(bytesRead); }
private static string GetSocks5Host(int addressType, Socket socket, TimeSpan timeout) { switch (addressType) { case 0x01: // IPv4 { var addressBuffer = new byte[4]; if (SocketAbstraction.Read(socket, addressBuffer, 0, 4, timeout) == 0) { // SOCKS client closed connection return(null); } var ipv4 = new IPAddress(addressBuffer); return(ipv4.ToString()); } case 0x03: // Domain name { var length = SocketAbstraction.ReadByte(socket, timeout); if (length == -1) { // SOCKS client closed connection return(null); } var addressBuffer = new byte[length]; if (SocketAbstraction.Read(socket, addressBuffer, 0, addressBuffer.Length, timeout) == 0) { // SOCKS client closed connection return(null); } var hostName = SshData.Ascii.GetString(addressBuffer, 0, addressBuffer.Length); return(hostName); } case 0x04: // IPv6 { var addressBuffer = new byte[16]; if (SocketAbstraction.Read(socket, addressBuffer, 0, 16, timeout) == 0) { // SOCKS client closed connection return(null); } var ipv6 = new IPAddress(addressBuffer); return(ipv6.ToString()); } default: throw new ProxyException(string.Format("SOCKS5: Address type '{0}' is not supported.", addressType)); } }
/// <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 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))); } if (byteRead == Session.LineFeed) { if (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))); } else { // Even though RFC4253 clearly indicates that the identification string should be terminated // by a CR LF we also support banners and identification strings that are terminated by a LF // Return current line without LF return(Encoding.UTF8.GetString(buffer.ToArray(), startPosition, buffer.Count - (startPosition + 1))); } } } return(null); }
/// <summary> /// Called when channel data is received. /// </summary> /// <param name="data">The data.</param> protected override void OnData(byte[] data) { base.OnData(data); if (_socket != null) { lock (_socketLock) { if (_socket.IsConnected()) { SocketAbstraction.Send(_socket, data, 0, data.Length); } } } }
private bool HandleSocks(IChannelDirectTcpip channel, Socket clientSocket, TimeSpan timeout) { // create eventhandler which is to be invoked to interrupt a blocking receive // when we're closing the forwarded port EventHandler closeClientSocket = (_, args) => CloseClientSocket(clientSocket); Closing += closeClientSocket; try { var version = SocketAbstraction.ReadByte(clientSocket, timeout); switch (version) { case -1: // SOCKS client closed connection return(false); case 4: return(HandleSocks4(clientSocket, channel, timeout)); case 5: return(HandleSocks5(clientSocket, channel, timeout)); default: throw new NotSupportedException(string.Format("SOCKS version {0} is not supported.", version)); } } catch (SocketException ex) { // ignore exception thrown by interrupting the blocking receive as part of closing // the forwarded port if (ex.SocketErrorCode != SocketError.Interrupted) { RaiseExceptionEvent(ex); } return(false); } finally { // interrupt of blocking receive is now handled by channel (SOCKS4 and SOCKS5) // or no longer necessary Closing -= closeClientSocket; } }
private bool HandleSocks(IChannelDirectTcpip channel, Socket remoteSocket, TimeSpan timeout) { // create eventhandler which is to be invoked to interrupt a blocking receive // when we're closing the forwarded port EventHandler closeClientSocket = (_, args) => CloseSocket(remoteSocket); Closing += closeClientSocket; try { #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Before ReadByte for version | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT var version = SocketAbstraction.ReadByte(remoteSocket, timeout); if (version == -1) { return(false); } #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After ReadByte for version | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT if (version == 4) { return(HandleSocks4(remoteSocket, channel, timeout)); } else if (version == 5) { return(HandleSocks5(remoteSocket, channel, timeout)); } else { throw new NotSupportedException(string.Format("SOCKS version {0} is not supported.", version)); } } finally { // interrupt of blocking receive is now handled by channel (SOCKS4 and SOCKS5) // or no longer necessary Closing -= closeClientSocket; } }
/// <summary> /// Binds channel to remote host. /// </summary> public void Bind() { // Cannot bind if channel is not open if (!IsOpen) { return; } var buffer = new byte[RemotePacketSize]; SocketAbstraction.ReadContinuous(_socket, buffer, 0, buffer.Length, SendData); // even though the client has disconnected, we still want to properly close the // channel // // we'll do this in in Close() - invoked through Dispose(bool) - that way we have // a single place from which we send an SSH_MSG_CHANNEL_EOF message and wait for // the SSH_MSG_CHANNEL_CLOSE message }
/// <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(string.Format("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); } throw new SshConnectionException(string.Format("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); } var identificationMatch = ServerVersionRe.Match(line); if (identificationMatch.Success) { return(new SshIdentification(GetGroupValue(identificationMatch, "protoversion"), GetGroupValue(identificationMatch, "softwareversion"), GetGroupValue(identificationMatch, "comments"))); } } }
/// <summary> /// Binds channel to remote host. /// </summary> public void Bind() { // Cannot bind if channel is not open if (!IsOpen) { return; } var buffer = new byte[RemotePacketSize]; SocketAbstraction.ReadContinuous(_socket, buffer, 0, buffer.Length, SendData); #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.Bind (after) '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT // even though the client has disconnected, we still want to properly close the // channel // // we'll do this in in Close(bool) that way we have a single place from which we // send an SSH_MSG_CHANNEL_EOF message and wait for the SSH_MSG_CHANNEL_CLOSE // message }
/// <summary> /// Establishes a connection to the server via a SOCKS5 proxy. /// </summary> /// <param name="connectionInfo">The connection information.</param> /// <param name="socket">The <see cref="Socket"/>.</param> private void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket) { var connectionRequest = CreateSocks4ConnectionRequest(connectionInfo.Host, (ushort)connectionInfo.Port, connectionInfo.ProxyUsername); SocketAbstraction.Send(socket, connectionRequest); // Read reply version if (SocketReadByte(socket, connectionInfo.Timeout) != 0x00) { throw new ProxyException("SOCKS4: Null is expected."); } // Read response code var code = SocketReadByte(socket, connectionInfo.Timeout); switch (code) { case 0x5a: break; case 0x5b: throw new ProxyException("SOCKS4: Connection rejected."); case 0x5c: throw new ProxyException("SOCKS4: Client is not running identd or not reachable from the server."); case 0x5d: throw new ProxyException("SOCKS4: Client's identd could not confirm the user ID string in the request."); default: throw new ProxyException("SOCKS4: Not valid response."); } var destBuffer = new byte[6]; // destination port and IP address should be ignored SocketRead(socket, destBuffer, 0, destBuffer.Length, connectionInfo.Timeout); }
/// <summary> /// Establishes a socket connection to the specified host and port. /// </summary> /// <param name="host">The host name of the server to connect to.</param> /// <param name="port">The port to connect to.</param> /// <param name="timeout">The maximum time to wait for the connection to be established.</param> /// <exception cref="SshOperationTimeoutException">The connection failed to establish within the configured <see cref="ConnectionInfo.Timeout"/>.</exception> /// <exception cref="SocketException">An error occurred trying to establish the connection.</exception> protected Socket SocketConnect(string host, int port, TimeSpan timeout) { var ipAddress = DnsAbstraction.GetHostAddresses(host)[0]; var ep = new IPEndPoint(ipAddress, port); DiagnosticAbstraction.Log(string.Format("Initiating connection to '{0}:{1}'.", host, port)); var socket = SocketFactory.Create(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); try { SocketAbstraction.Connect(socket, ep, timeout); const int socketBufferSize = 2 * Session.MaximumSshPacketSize; socket.SendBufferSize = socketBufferSize; socket.ReceiveBufferSize = socketBufferSize; return(socket); } catch (Exception) { socket.Dispose(); throw; } }
private bool HandleSocks5(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout) { #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Handling Socks5: " + socket.LocalEndPoint + " | " + socket.RemoteEndPoint + " | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT var authenticationMethodsCount = SocketAbstraction.ReadByte(socket, timeout); if (authenticationMethodsCount == -1) { // SOCKS client closed connection return(false); } #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After ReadByte for authenticationMethodsCount | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT var authenticationMethods = new byte[authenticationMethodsCount]; if (SocketAbstraction.Read(socket, authenticationMethods, 0, authenticationMethods.Length, timeout) == 0) { // SOCKS client closed connection return(false); } #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After Read for authenticationMethods | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT if (authenticationMethods.Min() == 0) { // no user authentication is one of the authentication methods supported // by the SOCKS client SocketAbstraction.Send(socket, new byte[] { 0x05, 0x00 }, 0, 2); #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After Send for authenticationMethods 0 | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT } else { // the SOCKS client requires authentication, which we currently do not support SocketAbstraction.Send(socket, new byte[] { 0x05, 0xFF }, 0, 2); // we continue business as usual but expect the client to close the connection // so one of the subsequent reads should return -1 signaling that the client // has effectively closed the connection #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After Send for authenticationMethods 2 | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT } var version = SocketAbstraction.ReadByte(socket, timeout); if (version == -1) { // SOCKS client closed connection return(false); } if (version != 5) { throw new ProxyException("SOCKS5: Version 5 is expected."); } var commandCode = SocketAbstraction.ReadByte(socket, timeout); if (commandCode == -1) { // SOCKS client closed connection return(false); } var reserved = SocketAbstraction.ReadByte(socket, timeout); if (reserved == -1) { // SOCKS client closed connection return(false); } if (reserved != 0) { throw new ProxyException("SOCKS5: 0 is expected for reserved byte."); } var addressType = SocketAbstraction.ReadByte(socket, timeout); if (addressType == -1) { // SOCKS client closed connection return(false); } IPAddress ipAddress; byte[] addressBuffer; switch (addressType) { case 0x01: { addressBuffer = new byte[4]; if (SocketAbstraction.Read(socket, addressBuffer, 0, 4, timeout) == 0) { // SOCKS client closed connection return(false); } ipAddress = new IPAddress(addressBuffer); } break; case 0x03: { var length = SocketAbstraction.ReadByte(socket, timeout); addressBuffer = new byte[length]; if (SocketAbstraction.Read(socket, addressBuffer, 0, addressBuffer.Length, timeout) == 0) { // SOCKS client closed connection return(false); } ipAddress = IPAddress.Parse(SshData.Ascii.GetString(addressBuffer)); //var hostName = new Common.ASCIIEncoding().GetString(addressBuffer); //ipAddress = Dns.GetHostEntry(hostName).AddressList[0]; } break; case 0x04: { addressBuffer = new byte[16]; if (SocketAbstraction.Read(socket, addressBuffer, 0, 16, timeout) == 0) { // SOCKS client closed connection return(false); } ipAddress = new IPAddress(addressBuffer); } break; default: throw new ProxyException(string.Format("SOCKS5: Address type '{0}' is not supported.", addressType)); } var portBuffer = new byte[2]; if (SocketAbstraction.Read(socket, portBuffer, 0, portBuffer.Length, timeout) == 0) { // SOCKS client closed connection return(false); } var port = (uint)(portBuffer[0] * 256 + portBuffer[1]); var host = ipAddress.ToString(); RaiseRequestReceived(host, port); #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Before channel open | " + DateTime.Now.ToString("hh:mm:ss.fff")); var stopWatch = new Stopwatch(); stopWatch.Start(); #endif // DEBUG_GERT channel.Open(host, port, this, socket); #if DEBUG_GERT stopWatch.Stop(); Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After channel open | " + DateTime.Now.ToString("hh:mm:ss.fff") + " => " + stopWatch.ElapsedMilliseconds); #endif // DEBUG_GERT SocketAbstraction.SendByte(socket, 0x05); if (channel.IsOpen) { SocketAbstraction.SendByte(socket, 0x00); } else { SocketAbstraction.SendByte(socket, 0x01); } // reserved SocketAbstraction.SendByte(socket, 0x00); if (ipAddress.AddressFamily == AddressFamily.InterNetwork) { SocketAbstraction.SendByte(socket, 0x01); } else if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { SocketAbstraction.SendByte(socket, 0x04); } else { throw new NotSupportedException("Not supported address family."); } var addressBytes = ipAddress.GetAddressBytes(); SocketAbstraction.Send(socket, addressBytes, 0, addressBytes.Length); SocketAbstraction.Send(socket, portBuffer, 0, portBuffer.Length); return(true); }
/// <summary> /// Establishes a connection to the server via a SOCKS5 proxy. /// </summary> /// <param name="connectionInfo">The connection information.</param> /// <param name="socket">The <see cref="Socket"/>.</param> private void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket) { var greeting = new byte[] { // SOCKS version number 0x05, // Number of supported authentication methods 0x02, // No authentication 0x00, // Username/Password authentication 0x02 }; SocketAbstraction.Send(socket, greeting); var socksVersion = SocketReadByte(socket); if (socksVersion != 0x05) { throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion)); } var authenticationMethod = SocketReadByte(socket); switch (authenticationMethod) { case 0x00: break; case 0x02: // Create username/password authentication request var authenticationRequest = CreateSocks5UserNameAndPasswordAuthenticationRequest(connectionInfo.ProxyUsername, connectionInfo.ProxyPassword); // Send authentication request SocketAbstraction.Send(socket, authenticationRequest); // Read authentication result var authenticationResult = SocketAbstraction.Read(socket, 2, connectionInfo.Timeout); if (authenticationResult[0] != 0x01) { throw new ProxyException("SOCKS5: Server authentication version is not valid."); } if (authenticationResult[1] != 0x00) { throw new ProxyException("SOCKS5: Username/Password authentication failed."); } break; case 0xFF: throw new ProxyException("SOCKS5: No acceptable authentication methods were offered."); } var connectionRequest = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort)connectionInfo.Port); SocketAbstraction.Send(socket, connectionRequest); // Read Server SOCKS5 version if (SocketReadByte(socket) != 5) { throw new ProxyException("SOCKS5: Version 5 is expected."); } // Read response code var status = SocketReadByte(socket); switch (status) { case 0x00: break; case 0x01: throw new ProxyException("SOCKS5: General failure."); case 0x02: throw new ProxyException("SOCKS5: Connection not allowed by ruleset."); case 0x03: throw new ProxyException("SOCKS5: Network unreachable."); case 0x04: throw new ProxyException("SOCKS5: Host unreachable."); case 0x05: throw new ProxyException("SOCKS5: Connection refused by destination host."); case 0x06: throw new ProxyException("SOCKS5: TTL expired."); case 0x07: throw new ProxyException("SOCKS5: Command not supported or protocol error."); case 0x08: throw new ProxyException("SOCKS5: Address type not supported."); default: throw new ProxyException("SOCKS5: Not valid response."); } // Read reserved byte if (SocketReadByte(socket) != 0) { throw new ProxyException("SOCKS5: 0 byte is expected."); } var addressType = SocketReadByte(socket); switch (addressType) { case 0x01: var ipv4 = new byte[4]; SocketRead(socket, ipv4, 0, 4); break; case 0x04: var ipv6 = new byte[16]; SocketRead(socket, ipv6, 0, 16); break; default: throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType)); } var port = new byte[2]; // Read 2 bytes to be ignored SocketRead(socket, port, 0, 2); }
internal static bool CanWrite(this Socket socket) { return(SocketAbstraction.CanWrite(socket)); }
private bool HandleSocks5(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout) { var authenticationMethodsCount = SocketAbstraction.ReadByte(socket, timeout); if (authenticationMethodsCount == -1) { // SOCKS client closed connection return(false); } var authenticationMethods = new byte[authenticationMethodsCount]; if (SocketAbstraction.Read(socket, authenticationMethods, 0, authenticationMethods.Length, timeout) == 0) { // SOCKS client closed connection return(false); } if (authenticationMethods.Min() == 0) { // no user authentication is one of the authentication methods supported // by the SOCKS client SocketAbstraction.Send(socket, new byte[] { 0x05, 0x00 }, 0, 2); } else { // the SOCKS client requires authentication, which we currently do not support SocketAbstraction.Send(socket, new byte[] { 0x05, 0xFF }, 0, 2); // we continue business as usual but expect the client to close the connection // so one of the subsequent reads should return -1 signaling that the client // has effectively closed the connection } var version = SocketAbstraction.ReadByte(socket, timeout); if (version == -1) { // SOCKS client closed connection return(false); } if (version != 5) { throw new ProxyException("SOCKS5: Version 5 is expected."); } var commandCode = SocketAbstraction.ReadByte(socket, timeout); if (commandCode == -1) { // SOCKS client closed connection return(false); } var reserved = SocketAbstraction.ReadByte(socket, timeout); if (reserved == -1) { // SOCKS client closed connection return(false); } if (reserved != 0) { throw new ProxyException("SOCKS5: 0 is expected for reserved byte."); } var addressType = SocketAbstraction.ReadByte(socket, timeout); if (addressType == -1) { // SOCKS client closed connection return(false); } var host = GetSocks5Host(addressType, socket, timeout); if (host == null) { // SOCKS client closed connection return(false); } var portBuffer = new byte[2]; if (SocketAbstraction.Read(socket, portBuffer, 0, portBuffer.Length, timeout) == 0) { // SOCKS client closed connection return(false); } var port = Pack.BigEndianToUInt16(portBuffer); RaiseRequestReceived(host, port); channel.Open(host, port, this, socket); var socksReply = CreateSocks5Reply(channel.IsOpen); SocketAbstraction.Send(socket, socksReply, 0, socksReply.Length); return(true); }
private bool HandleSocks5(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout) { var authenticationMethodsCount = SocketAbstraction.ReadByte(socket, timeout); if (authenticationMethodsCount == -1) { // SOCKS client closed connection return(false); } var authenticationMethods = new byte[authenticationMethodsCount]; if (SocketAbstraction.Read(socket, authenticationMethods, 0, authenticationMethods.Length, timeout) == 0) { // SOCKS client closed connection return(false); } if (authenticationMethods.Min() == 0) { // no user authentication is one of the authentication methods supported // by the SOCKS client SocketAbstraction.Send(socket, new byte[] { 0x05, 0x00 }, 0, 2); } else { // the SOCKS client requires authentication, which we currently do not support SocketAbstraction.Send(socket, new byte[] { 0x05, 0xFF }, 0, 2); // we continue business as usual but expect the client to close the connection // so one of the subsequent reads should return -1 signaling that the client // has effectively closed the connection } var version = SocketAbstraction.ReadByte(socket, timeout); if (version == -1) { // SOCKS client closed connection return(false); } if (version != 5) { throw new ProxyException("SOCKS5: Version 5 is expected."); } var commandCode = SocketAbstraction.ReadByte(socket, timeout); if (commandCode == -1) { // SOCKS client closed connection return(false); } var reserved = SocketAbstraction.ReadByte(socket, timeout); if (reserved == -1) { // SOCKS client closed connection return(false); } if (reserved != 0) { throw new ProxyException("SOCKS5: 0 is expected for reserved byte."); } var addressType = SocketAbstraction.ReadByte(socket, timeout); if (addressType == -1) { // SOCKS client closed connection return(false); } IPAddress ipAddress; byte[] addressBuffer; switch (addressType) { case 0x01: { addressBuffer = new byte[4]; if (SocketAbstraction.Read(socket, addressBuffer, 0, 4, timeout) == 0) { // SOCKS client closed connection return(false); } ipAddress = new IPAddress(addressBuffer); } break; case 0x03: { var length = SocketAbstraction.ReadByte(socket, timeout); if (length == -1) { // SOCKS client closed connection return(false); } addressBuffer = new byte[length]; if (SocketAbstraction.Read(socket, addressBuffer, 0, addressBuffer.Length, timeout) == 0) { // SOCKS client closed connection return(false); } ipAddress = IPAddress.Parse(SshData.Ascii.GetString(addressBuffer, 0, addressBuffer.Length)); //var hostName = new Common.ASCIIEncoding().GetString(addressBuffer); //ipAddress = Dns.GetHostEntry(hostName).AddressList[0]; } break; case 0x04: { addressBuffer = new byte[16]; if (SocketAbstraction.Read(socket, addressBuffer, 0, 16, timeout) == 0) { // SOCKS client closed connection return(false); } ipAddress = new IPAddress(addressBuffer); } break; default: throw new ProxyException(string.Format("SOCKS5: Address type '{0}' is not supported.", addressType)); } var portBuffer = new byte[2]; if (SocketAbstraction.Read(socket, portBuffer, 0, portBuffer.Length, timeout) == 0) { // SOCKS client closed connection return(false); } var port = (uint)(portBuffer[0] * 256 + portBuffer[1]); var host = ipAddress.ToString(); RaiseRequestReceived(host, port); channel.Open(host, port, this, socket); SocketAbstraction.SendByte(socket, 0x05); if (channel.IsOpen) { SocketAbstraction.SendByte(socket, 0x00); } else { SocketAbstraction.SendByte(socket, 0x01); } // reserved SocketAbstraction.SendByte(socket, 0x00); if (ipAddress.AddressFamily == AddressFamily.InterNetwork) { SocketAbstraction.SendByte(socket, 0x01); } else if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { SocketAbstraction.SendByte(socket, 0x04); } else { throw new NotSupportedException("Not supported address family."); } var addressBytes = ipAddress.GetAddressBytes(); SocketAbstraction.Send(socket, addressBytes, 0, addressBytes.Length); SocketAbstraction.Send(socket, portBuffer, 0, portBuffer.Length); return(true); }
private void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket) { var httpResponseRe = new Regex(@"HTTP/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$"); var httpHeaderRe = new Regex(@"(?<fieldName>[^\[\]()<>@,;:\""/?={} \t]+):(?<fieldValue>.+)?"); SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(string.Format("CONNECT {0}:{1} HTTP/1.0\r\n", connectionInfo.Host, connectionInfo.Port))); // Sent proxy authorization if specified if (!string.IsNullOrEmpty(connectionInfo.ProxyUsername)) { var authorization = string.Format("Proxy-Authorization: Basic {0}\r\n", Convert.ToBase64String(SshData.Ascii.GetBytes(string.Format("{0}:{1}", connectionInfo.ProxyUsername, connectionInfo.ProxyPassword)))); SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(authorization)); } SocketAbstraction.Send(socket, SshData.Ascii.GetBytes("\r\n")); HttpStatusCode?statusCode = null; var contentLength = 0; while (true) { var response = SocketReadLine(socket, connectionInfo.Timeout); if (response == null) { // server shut down socket break; } if (statusCode == null) { var statusMatch = httpResponseRe.Match(response); if (statusMatch.Success) { var httpStatusCode = statusMatch.Result("${statusCode}"); statusCode = (HttpStatusCode)int.Parse(httpStatusCode); if (statusCode != HttpStatusCode.OK) { throw new ProxyException(string.Format("HTTP: Status code {0}, \"{1}\"", httpStatusCode, statusMatch.Result("${reasonPhrase}"))); } } continue; } // continue on parsing message headers coming from the server var headerMatch = httpHeaderRe.Match(response); if (headerMatch.Success) { var fieldName = headerMatch.Result("${fieldName}"); if (fieldName.Equals("Content-Length", StringComparison.OrdinalIgnoreCase)) { contentLength = int.Parse(headerMatch.Result("${fieldValue}")); } continue; } // check if we've reached the CRLF which separates request line and headers from the message body if (response.Length == 0) { // read response body if specified if (contentLength > 0) { var contentBody = new byte[contentLength]; SocketRead(socket, contentBody, 0, contentLength, connectionInfo.Timeout); } break; } } if (statusCode == null) { throw new ProxyException("HTTP response does not contain status line."); } }