Minor refactoring of our SOCKS client support.

Use big endian format for port number. Fixes #447.
This commit is contained in:
Gert Driesen
2018-07-29 22:15:48 +02:00
parent 7b43bdcce9
commit adeeb8c231
+212 -117
View File
@@ -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
/// </para>
/// </remarks>
#else
/// <summary>
/// Gets a value indicating whether the socket is connected.
/// </summary>
/// <returns>
/// <c>true</c> if the socket is connected; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// We verify whether <see cref="Socket.Connected"/> is <c>true</c>. However, this only returns the state
/// of the socket as of the last I/O operation.
/// </remarks>
/// <summary>
/// Gets a value indicating whether the socket is connected.
/// </summary>
/// <returns>
/// <c>true</c> if the socket is connected; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// We verify whether <see cref="Socket.Connected"/> is <c>true</c>. However, this only returns the state
/// of the socket as of the last I/O operation.
/// </remarks>
#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);
}
/// <summary>
/// https://tools.ietf.org/html/rfc1929
/// </summary>
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/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$");