com.sshtools.net.SocksProxyTransport Maven / Gradle / Ivy
The newest version!
/**
* Copyright 2003-2016 SSHTOOLS Limited. All Rights Reserved.
*
* For product documentation visit https://www.sshtools.com/
*
* This file is part of J2SSH Maverick.
*
* J2SSH Maverick is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* J2SSH Maverick is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with J2SSH Maverick. If not, see .
*/
package com.sshtools.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import com.sshtools.ssh.SshTransport;
/**
* Provides an {@link com.sshtools.ssh.SshTransport} implementation that can
* route the connection through a SOCKS 4 or SOCKS 5 proxy.
*
* @author Lee David Painter
*/
public class SocksProxyTransport extends Socket implements SshTransport {
public static final int SOCKS4 = 0x04;
public static final int SOCKS5 = 0x05;
private static final int CONNECT = 0x01;
private static final int NULL_TERMINATION = 0x00;
private final static String[] SOCKSV5_ERROR = { "Success",
"General SOCKS server failure",
"Connection not allowed by ruleset", "Network unreachable",
"Host unreachable", "Connection refused", "TTL expired",
"Command not supported", "Address type not supported" };
private final static String[] SOCKSV4_ERROR = {
"Request rejected or failed",
"SOCKS server cannot connect to identd on the client",
"The client program and identd report different user-ids" };
private String proxyHost;
private int proxyPort;
private String remoteHost;
private int remotePort;
private int socksVersion;
private String username;
private String password;
private boolean localLookup;
private String providerDetail;
private SocksProxyTransport(String remoteHost, int remotePort,
String proxyHost, int proxyPort, int socksVersion)
throws IOException, UnknownHostException {
super(proxyHost, proxyPort);
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
this.remoteHost = remoteHost;
this.remotePort = remotePort;
this.socksVersion = socksVersion;
}
/**
* Connect the socket to a SOCKS 4 proxy and request forwarding to our
* remote host.
*
* @param remoteHost
* @param remotePort
* @param proxyHost
* @param proxyPort
* @param userId
* @return SocksProxyTransport
* @throws IOException
* @throws UnknownHostException
*/
public static SocksProxyTransport connectViaSocks4Proxy(String remoteHost,
int remotePort, String proxyHost, int proxyPort, String userId)
throws IOException, UnknownHostException {
SocksProxyTransport proxySocket = new SocksProxyTransport(remoteHost,
remotePort, proxyHost, proxyPort, SOCKS4);
proxySocket.username = userId;
try {
InputStream proxyIn = proxySocket.getInputStream();
OutputStream proxyOut = proxySocket.getOutputStream();
InetAddress hostAddr = InetAddress.getByName(remoteHost);
proxyOut.write(SOCKS4);
proxyOut.write(CONNECT);
proxyOut.write((remotePort >>> 8) & 0xff);
proxyOut.write(remotePort & 0xff);
proxyOut.write(hostAddr.getAddress());
proxyOut.write(userId.getBytes());
proxyOut.write(NULL_TERMINATION);
proxyOut.flush();
int res = proxyIn.read();
if (res == -1) {
throw new IOException("SOCKS4 server " + proxyHost + ":"
+ proxyPort + " disconnected");
}
if (res != 0x00) {
throw new IOException("Invalid response from SOCKS4 server ("
+ res + ") " + proxyHost + ":" + proxyPort);
}
int code = proxyIn.read();
if (code != 90) {
if ((code > 90) && (code < 93)) {
throw new IOException(
"SOCKS4 server unable to connect, reason: "
+ SOCKSV4_ERROR[code - 91]);
}
throw new IOException(
"SOCKS4 server unable to connect, reason: " + code);
}
byte[] data = new byte[6];
if (proxyIn.read(data, 0, 6) != 6) {
throw new IOException(
"SOCKS4 error reading destination address/port");
}
proxySocket.setProviderDetail(data[2] + "." + data[3] + "."
+ data[4] + "." + data[5] + ":"
+ ((data[0] << 8) | data[1]));
} catch (SocketException e) {
throw new SocketException("Error communicating with SOCKS4 server "
+ proxyHost + ":" + proxyPort + ", " + e.getMessage());
}
return proxySocket;
}
private void setProviderDetail(String providerDetail) {
this.providerDetail = providerDetail;
}
/**
* Connect the socket to a SOCKS 5 proxy and request forwarding to our
* remote host.
*
* @param remoteHost
* @param remotePort
* @param proxyHost
* @param proxyPort
* @param localLookup
* @param username
* @param password
* @return SocksProxyTransport
* @throws IOException
* @throws UnknownHostException
*/
public static SocksProxyTransport connectViaSocks5Proxy(String remoteHost,
int remotePort, String proxyHost, int proxyPort,
boolean localLookup, String username, String password)
throws IOException, UnknownHostException {
SocksProxyTransport proxySocket = new SocksProxyTransport(remoteHost,
remotePort, proxyHost, proxyPort, SOCKS5);
proxySocket.username = username;
proxySocket.password = password;
proxySocket.localLookup = localLookup;
try {
InputStream proxyIn = proxySocket.getInputStream();
OutputStream proxyOut = proxySocket.getOutputStream();
byte[] request = { (byte) SOCKS5, (byte) 0x02, (byte) 0x00,
(byte) 0x02 };
// byte[] reply = new byte[2];
proxyOut.write(request);
proxyOut.flush();
int res = proxyIn.read();
if (res == -1) {
throw new IOException("SOCKS5 server " + proxyHost + ":"
+ proxyPort + " disconnected");
}
if (res != 0x05) {
throw new IOException("Invalid response from SOCKS5 server ("
+ res + ") " + proxyHost + ":" + proxyPort);
}
int method = proxyIn.read();
switch (method) {
case 0x00:
break;
case 0x02:
performAuthentication(proxyIn, proxyOut, username, password,
proxyHost, proxyPort);
break;
default:
throw new IOException(
"SOCKS5 server does not support our authentication methods");
}
if (localLookup) {
InetAddress hostAddr;
try {
hostAddr = InetAddress.getByName(remoteHost);
} catch (UnknownHostException e) {
throw new IOException("Can't do local lookup on: "
+ remoteHost + ", try socks5 without local lookup");
}
request = new byte[] { (byte) SOCKS5, (byte) 0x01, (byte) 0x00,
(byte) 0x01 };
proxyOut.write(request);
proxyOut.write(hostAddr.getAddress());
} else {
request = new byte[] { (byte) SOCKS5, (byte) 0x01, (byte) 0x00,
(byte) 0x03 };
proxyOut.write(request);
proxyOut.write(remoteHost.length());
proxyOut.write(remoteHost.getBytes());
}
proxyOut.write((remotePort >>> 8) & 0xff);
proxyOut.write(remotePort & 0xff);
proxyOut.flush();
res = proxyIn.read();
if (res != 0x05) {
throw new IOException("Invalid response from SOCKS5 server ("
+ res + ") " + proxyHost + ":" + proxyPort);
}
int status = proxyIn.read();
if (status != 0x00) {
if ((status > 0) && (status < 9)) {
throw new IOException(
"SOCKS5 server unable to connect, reason: "
+ SOCKSV5_ERROR[status]);
}
throw new IOException(
"SOCKS5 server unable to connect, reason: " + status);
}
proxyIn.read();
int aType = proxyIn.read();
byte[] data = new byte[255];
switch (aType) {
case 0x01:
if (proxyIn.read(data, 0, 4) != 4) {
throw new IOException("SOCKS5 error reading address");
}
proxySocket.setProviderDetail(data[0] + "." + data[1] + "."
+ data[2] + "." + data[3]);
break;
case 0x03:
int n = proxyIn.read();
if (proxyIn.read(data, 0, n) != n) {
throw new IOException("SOCKS5 error reading address");
}
proxySocket.setProviderDetail(new String(data));
break;
default:
throw new IOException("SOCKS5 gave unsupported address type: "
+ aType);
}
if (proxyIn.read(data, 0, 2) != 2) {
throw new IOException("SOCKS5 error reading port");
}
proxySocket.setProviderDetail(proxySocket.getProviderDetail()
+ (":" + ((data[0] << 8) | data[1])));
} catch (SocketException e) {
throw new SocketException("Error communicating with SOCKS5 server "
+ proxyHost + ":" + proxyPort + ", " + e.getMessage());
}
return proxySocket;
}
private String getProviderDetail() {
return providerDetail;
}
private static void performAuthentication(InputStream proxyIn,
OutputStream proxyOut, String username, String password,
String proxyHost, int proxyPort) throws IOException {
proxyOut.write(0x01);
proxyOut.write(username.length());
proxyOut.write(username.getBytes());
proxyOut.write(password.length());
proxyOut.write(password.getBytes());
int res = proxyIn.read();
if ((res != 0x01) && (res != 0x05)) {
throw new IOException("Invalid response from SOCKS5 server (" + res
+ ") " + proxyHost + ":" + proxyPort);
}
if (proxyIn.read() != 0x00) {
throw new IOException("Invalid username/password for SOCKS5 server");
}
}
public String toString() {
return "SocksProxySocket[addr=" + getInetAddress() + ",port="
+ getPort() + ",localport=" + getLocalPort() + "]";
}
public static SocksProxyTransport connectViaSocks5Proxy(String remoteHost,
int remotePort, String proxyHost, int proxyPort, String username,
String password) throws IOException, UnknownHostException {
return connectViaSocks5Proxy(remoteHost, remotePort, proxyHost,
proxyPort, false, username, password);
}
public String getHost() {
return remoteHost;
}
public SshTransport duplicate() throws IOException {
switch (socksVersion) {
case SOCKS4:
return connectViaSocks4Proxy(remoteHost, remotePort, proxyHost,
proxyPort, username);
default:
return connectViaSocks5Proxy(remoteHost, remotePort, proxyHost,
proxyPort, localLookup, username, password);
}
}
}