diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs
index 3e0143b4..3d4d09f1 100644
--- a/src/Renci.SshNet/Session.cs
+++ b/src/Renci.SshNet/Session.cs
@@ -1301,7 +1301,7 @@ namespace Renci.SshNet
_sshMessageFactory.DisableNonKeyExchangeMessages();
_keyExchange = _serviceFactory.CreateKeyExchange(ConnectionInfo.KeyExchangeAlgorithms,
- message.KeyExchangeAlgorithms);
+ message.KeyExchangeAlgorithms);
ConnectionInfo.CurrentKeyExchangeAlgorithm = _keyExchange.Name;
@@ -1726,7 +1726,7 @@ namespace Renci.SshNet
// SshConnectionException will interrupt the message listener loop (if not already interrupted)
// and the exception itself will be ignored (in RaiseError)
throw new SshConnectionException("An established connection was aborted by the server.",
- DisconnectReason.ConnectionLost);
+ DisconnectReason.ConnectionLost);
}
return bytesRead;
}
@@ -1772,16 +1772,16 @@ namespace Renci.SshNet
///
///
#else
- ///
- /// Gets a value indicating whether the socket is connected.
- ///
- ///
- /// true if the socket is connected; otherwise, false.
- ///
- ///
- /// We verify whether is true. However, this only returns the state
- /// of the socket as of the last I/O operation.
- ///
+///
+/// Gets a value indicating whether the socket is connected.
+///
+///
+/// true if the socket is connected; otherwise, false.
+///
+///
+/// We verify whether is true. However, this only returns the state
+/// of the socket as of the last I/O operation.
+///
#endif
private bool IsSocketConnected()
{
@@ -1952,9 +1952,9 @@ namespace Renci.SshNet
break;
}
#elif FEATURE_SOCKET_POLL
- // when Socket.Select(IList, IList, IList, Int32) is not available or is buggy, we use
- // Socket.Poll(Int, SelectMode) to block until either data is available or the socket
- // is closed
+// when Socket.Select(IList, IList, IList, Int32) is not available or is buggy, we use
+// Socket.Poll(Int, SelectMode) to block until either data is available or the socket
+// is closed
_socket.Poll(-1, SelectMode.SelectRead);
if (!_socket.IsConnected())
@@ -2002,33 +2002,12 @@ namespace Renci.SshNet
return buffer[0];
}
- private void SocketWriteByte(byte data)
- {
- SocketAbstraction.Send(_socket, new[] {data});
- }
-
private void ConnectSocks4()
{
- // Send socks version number
- SocketWriteByte(0x04);
+ var connectionRequest = CreateSocks4ConnectionRequest(ConnectionInfo.Host, (ushort) ConnectionInfo.Port, ConnectionInfo.ProxyUsername);
+ SocketAbstraction.Send(_socket, connectionRequest);
- // Send command code
- SocketWriteByte(0x01);
-
- // Send port
- SocketWriteByte((byte)(ConnectionInfo.Port / 0xFF));
- SocketWriteByte((byte)(ConnectionInfo.Port % 0xFF));
-
- // Send IP
- var ipAddress = DnsAbstraction.GetHostAddresses(ConnectionInfo.Host)[0];
- SocketAbstraction.Send(_socket, ipAddress.GetAddressBytes());
-
- // Send username
- var username = SshData.Ascii.GetBytes(ConnectionInfo.ProxyUsername);
- SocketAbstraction.Send(_socket, username);
- SocketWriteByte(0x00);
-
- // Read 0
+ // Read null byte
if (SocketReadByte() != 0)
{
throw new ProxyException("SOCKS4: Null is expected.");
@@ -2057,15 +2036,18 @@ namespace Renci.SshNet
private void ConnectSocks5()
{
- // Send socks version number
- SocketWriteByte(0x05);
-
- // Send number of supported authentication methods
- SocketWriteByte(0x02);
-
- // Send supported authentication methods
- SocketWriteByte(0x00); // No authentication
- SocketWriteByte(0x02); // Username/Password
+ 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();
if (socksVersion != 0x05)
@@ -2077,78 +2059,24 @@ namespace Renci.SshNet
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);
- // Send version
- SocketWriteByte(0x01);
-
- var username = SshData.Ascii.GetBytes(ConnectionInfo.ProxyUsername);
-
- if (username.Length > byte.MaxValue)
- throw new ProxyException("Proxy username is too long.");
-
- // Send username length
- SocketWriteByte((byte)username.Length);
-
- // Send username
- SocketAbstraction.Send(_socket, username);
-
- var password = SshData.Ascii.GetBytes(ConnectionInfo.ProxyPassword);
-
- if (password.Length > byte.MaxValue)
- throw new ProxyException("Proxy password is too long.");
-
- // Send username length
- SocketWriteByte((byte)password.Length);
-
- // Send username
- SocketAbstraction.Send(_socket, password);
-
- var serverVersion = SocketReadByte();
-
- if (serverVersion != 1)
+ if (authenticationResult[0] != 0x01)
throw new ProxyException("SOCKS5: Server authentication version is not valid.");
-
- var statusCode = SocketReadByte();
- if (statusCode != 0)
+ 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.");
}
- // Send socks version number
- SocketWriteByte(0x05);
-
- // Send command code
- SocketWriteByte(0x01); // establish a TCP/IP stream connection
-
- // Send reserved, must be 0x00
- SocketWriteByte(0x00);
-
- var ip = DnsAbstraction.GetHostAddresses(ConnectionInfo.Host)[0];
-
- // Send address type and address
- if (ip.AddressFamily == AddressFamily.InterNetwork)
- {
- SocketWriteByte(0x01);
- var address = ip.GetAddressBytes();
- SocketAbstraction.Send(_socket, address);
- }
- else if (ip.AddressFamily == AddressFamily.InterNetworkV6)
- {
- SocketWriteByte(0x04);
- var address = ip.GetAddressBytes();
- SocketAbstraction.Send(_socket, address);
- }
- else
- {
- throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", ip));
- }
-
- // Send port
- SocketWriteByte((byte)(ConnectionInfo.Port / 0xFF));
- SocketWriteByte((byte)(ConnectionInfo.Port % 0xFF));
+ var connectionRequest = CreateSocks5ConnectionRequest(ConnectionInfo.Host, (ushort) ConnectionInfo.Port);
+ SocketAbstraction.Send(_socket, connectionRequest);
// Read Server SOCKS5 version
if (SocketReadByte() != 5)
@@ -2183,22 +2111,22 @@ namespace Renci.SshNet
throw new ProxyException("SOCKS5: Not valid response.");
}
- // Read 0
+ // Read reserved byte
if (SocketReadByte() != 0)
{
throw new ProxyException("SOCKS5: 0 byte is expected.");
}
var addressType = SocketReadByte();
- var responseIp = new byte[16];
-
switch (addressType)
{
case 0x01:
- SocketRead(responseIp, 0, 4);
+ var ipv4 = new byte[4];
+ SocketRead(ipv4, 0, 4);
break;
case 0x04:
- SocketRead(responseIp, 0, 16);
+ var ipv6 = new byte[16];
+ SocketRead(ipv6, 0, 16);
break;
default:
throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType));
@@ -2210,6 +2138,173 @@ namespace Renci.SshNet
SocketRead(port, 0, 2);
}
+ ///
+ /// https://tools.ietf.org/html/rfc1929
+ ///
+ private static byte[] CreateSocks5UserNameAndPasswordAuthenticationRequest(string username, string password)
+ {
+ if (username.Length > byte.MaxValue)
+ throw new ProxyException("Proxy username is too long.");
+ if (password.Length > byte.MaxValue)
+ throw new ProxyException("Proxy password is too long.");
+
+ var authenticationRequest = new byte
+ [
+ // Version of the negotiation
+ 1 +
+ // Length of the username
+ 1 +
+ // Username
+ username.Length +
+ // Length of the password
+ 1 +
+ // Password
+ password.Length
+ ];
+
+ var index = 0;
+
+ // Version of the negiotiation
+ authenticationRequest[index++] = 0x01;
+
+ // Length of the username
+ authenticationRequest[index++] = (byte) username.Length;
+
+ // Username
+ SshData.Ascii.GetBytes(username, 0, username.Length, authenticationRequest, index);
+ index += username.Length;
+
+ // Length of the password
+ authenticationRequest[index++] = (byte) password.Length;
+
+ // Password
+ SshData.Ascii.GetBytes(password, 0, password.Length, authenticationRequest, index);
+
+ return authenticationRequest;
+ }
+
+ private static byte[] CreateSocks4ConnectionRequest(string hostname, ushort port, string username)
+ {
+ var addressBytes = GetSocks4DestinationAddress(hostname);
+
+ var connectionRequest = new byte
+ [
+ // SOCKS version number
+ 1 +
+ // Command code
+ 1 +
+ // Port number
+ 2 +
+ // IP address
+ addressBytes.Length +
+ // Username
+ username.Length +
+ // Null terminator
+ 1
+ ];
+
+ var index = 0;
+
+ // SOCKS version number
+ connectionRequest[index++] = 0x04;
+
+ // Command code
+ connectionRequest[index++] = 0x01; // establish a TCP/IP stream connection
+
+ // Port number
+ Pack.UInt16ToBigEndian(port, connectionRequest, index);
+ index += 2;
+
+ // Address
+ Buffer.BlockCopy(addressBytes, 0, connectionRequest, index, addressBytes.Length);
+ index += addressBytes.Length;
+
+ connectionRequest[index] = 0x00;
+
+ return connectionRequest;
+ }
+
+ private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port)
+ {
+ byte addressType;
+ var addressBytes = GetSocks5DestinationAddress(hostname, out addressType);
+
+ var connectionRequest = new byte
+ [
+ // SOCKS version number
+ 1 +
+ // Command code
+ 1 +
+ // Reserved
+ 1 +
+ // Address type
+ 1 +
+ // Address
+ addressBytes.Length +
+ // Port number
+ 2
+ ];
+
+ var index = 0;
+
+ // SOCKS version number
+ connectionRequest[index++] = 0x05;
+
+ // Command code
+ connectionRequest[index++] = 0x01; // establish a TCP/IP stream connection
+
+ // Reserved
+ connectionRequest[index++] = 0x00;
+
+ // Address type
+ connectionRequest[index++] = addressType;
+
+ // Address
+ Buffer.BlockCopy(addressBytes, 0, connectionRequest, index, addressBytes.Length);
+ index += addressBytes.Length;
+
+ // Port number
+ Pack.UInt16ToBigEndian(port, connectionRequest, index);
+
+ return connectionRequest;
+ }
+
+ private static byte[] GetSocks4DestinationAddress(string hostname)
+ {
+ var ip = DnsAbstraction.GetHostAddresses(hostname)[0];
+
+ if (ip.AddressFamily != AddressFamily.InterNetwork)
+ {
+ throw new ProxyException("SOCKS4 only supports IPv4.");
+ }
+
+ return ip.GetAddressBytes();
+ }
+
+ private static byte[] GetSocks5DestinationAddress(string hostname, out byte addressType)
+ {
+ var ip = DnsAbstraction.GetHostAddresses(hostname)[0];
+
+ byte[] address;
+
+ if (ip.AddressFamily == AddressFamily.InterNetwork)
+ {
+ addressType = 0x01; // IPv4
+ address = ip.GetAddressBytes();
+ }
+ else if (ip.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ addressType = 0x04; // IPv6
+ address = ip.GetAddressBytes();
+ }
+ else
+ {
+ throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", ip));
+ }
+
+ return address;
+ }
+
private void ConnectHttp()
{
var httpResponseRe = new Regex(@"HTTP/(?\d[.]\d) (?\d{3}) (?.+)$");