All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy