ProxySOCKS5.java
/*
* Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials provided with
* the distribution.
*
* 3. The names of the authors may not be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This file depends on following documents, - RFC 1928 SOCKS Protocol Verseion 5 - RFC 1929
* Username/Password Authentication for SOCKS V5.
*/
package com.jcraft.jsch;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class ProxySOCKS5 implements Proxy {
private static int DEFAULTPORT = 1080;
private String proxy_host;
private int proxy_port;
private InputStream in;
private OutputStream out;
private Socket socket;
private String user;
private String passwd;
public ProxySOCKS5(String proxy_host) {
int port = DEFAULTPORT;
String host = proxy_host;
if (proxy_host.indexOf(':') != -1) {
try {
host = proxy_host.substring(0, proxy_host.indexOf(':'));
port = Integer.parseInt(proxy_host.substring(proxy_host.indexOf(':') + 1));
} catch (Exception e) {
}
}
this.proxy_host = host;
this.proxy_port = port;
}
public ProxySOCKS5(String proxy_host, int proxy_port) {
this.proxy_host = proxy_host;
this.proxy_port = proxy_port;
}
public void setUserPasswd(String user, String passwd) {
this.user = user;
this.passwd = passwd;
}
@Override
public void connect(SocketFactory socket_factory, String host, int port, int timeout)
throws JSchException {
try {
if (socket_factory == null) {
socket = Util.createSocket(proxy_host, proxy_port, timeout);
// socket=new Socket(proxy_host, proxy_port);
in = socket.getInputStream();
out = socket.getOutputStream();
} else {
socket = socket_factory.createSocket(proxy_host, proxy_port);
in = socket_factory.getInputStream(socket);
out = socket_factory.getOutputStream(socket);
}
if (timeout > 0) {
socket.setSoTimeout(timeout);
}
socket.setTcpNoDelay(true);
byte[] buf = new byte[1024];
int index = 0;
/*
* +----+----------+----------+ |VER | NMETHODS | METHODS | +----+----------+----------+ | 1 |
* 1 | 1 to 255 | +----+----------+----------+
*
* The VER field is set to X'05' for this version of the protocol. The NMETHODS field contains
* the number of method identifier octets that appear in the METHODS field.
*
* The values currently defined for METHOD are:
*
* o X'00' NO AUTHENTICATION REQUIRED o X'01' GSSAPI o X'02' USERNAME/PASSWORD o X'03' to
* X'7F' IANA ASSIGNED o X'80' to X'FE' RESERVED FOR PRIVATE METHODS o X'FF' NO ACCEPTABLE
* METHODS
*/
buf[index++] = 5;
buf[index++] = 2;
buf[index++] = 0; // NO AUTHENTICATION REQUIRED
buf[index++] = 2; // USERNAME/PASSWORD
out.write(buf, 0, index);
/*
* The server selects from one of the methods given in METHODS, and sends a METHOD selection
* message:
*
* +----+--------+ |VER | METHOD | +----+--------+ | 1 | 1 | +----+--------+
*/
// in.read(buf, 0, 2);
fill(in, buf, 2);
boolean check = false;
switch ((buf[1]) & 0xff) {
case 0: // NO AUTHENTICATION REQUIRED
check = true;
break;
case 2: // USERNAME/PASSWORD
if (user == null || passwd == null)
break;
/*
* Once the SOCKS V5 server has started, and the client has selected the Username/Password
* Authentication protocol, the Username/Password subnegotiation begins. This begins with
* the client producing a Username/Password request:
*
* +----+------+----------+------+----------+ |VER | ULEN | UNAME | PLEN | PASSWD |
* +----+------+----------+------+----------+ | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
* +----+------+----------+------+----------+
*
* The VER field contains the current version of the subnegotiation, which is X'01'. The
* ULEN field contains the length of the UNAME field that follows. The UNAME field
* contains the username as known to the source operating system. The PLEN field contains
* the length of the PASSWD field that follows. The PASSWD field contains the password
* association with the given UNAME.
*/
index = 0;
buf[index++] = 1;
buf[index++] = (byte) (user.length());
System.arraycopy(Util.str2byte(user), 0, buf, index, user.length());
index += user.length();
buf[index++] = (byte) (passwd.length());
System.arraycopy(Util.str2byte(passwd), 0, buf, index, passwd.length());
index += passwd.length();
out.write(buf, 0, index);
/*
* The server verifies the supplied UNAME and PASSWD, and sends the following response:
*
* +----+--------+ |VER | STATUS | +----+--------+ | 1 | 1 | +----+--------+
*
* A STATUS field of X'00' indicates success. If the server returns a `failure' (STATUS
* value other than X'00') status, it MUST close the connection.
*/
// in.read(buf, 0, 2);
fill(in, buf, 2);
if (buf[1] == 0)
check = true;
break;
default:
}
if (!check) {
try {
socket.close();
} catch (Exception eee) {
}
throw new JSchProxyException("fail in SOCKS5 proxy");
}
/*
* The SOCKS request is formed as follows:
*
* +----+-----+-------+------+----------+----------+ |VER | CMD | RSV | ATYP | DST.ADDR |
* DST.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable
* | 2 | +----+-----+-------+------+----------+----------+
*
* Where:
*
* o VER protocol version: X'05' o CMD o CONNECT X'01' o BIND X'02' o UDP ASSOCIATE X'03' o
* RSV RESERVED o ATYP address type of following address o IP V4 address: X'01' o DOMAINNAME:
* X'03' o IP V6 address: X'04' o DST.ADDR desired destination address o DST.PORT desired
* destination port in network octet order
*/
index = 0;
buf[index++] = 5;
buf[index++] = 1; // CONNECT
buf[index++] = 0;
byte[] hostb = Util.str2byte(host);
int len = hostb.length;
buf[index++] = 3; // DOMAINNAME
buf[index++] = (byte) (len);
System.arraycopy(hostb, 0, buf, index, len);
index += len;
buf[index++] = (byte) (port >>> 8);
buf[index++] = (byte) (port & 0xff);
out.write(buf, 0, index);
/*
* The SOCKS request information is sent by the client as soon as it has established a
* connection to the SOCKS server, and completed the authentication negotiations. The server
* evaluates the request, and returns a reply formed as follows:
*
* +----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP | BND.ADDR |
* BND.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable
* | 2 | +----+-----+-------+------+----------+----------+
*
* Where:
*
* o VER protocol version: X'05' o REP Reply field: o X'00' succeeded o X'01' general SOCKS
* server failure o X'02' connection not allowed by ruleset o X'03' Network unreachable o
* X'04' Host unreachable o X'05' Connection refused o X'06' TTL expired o X'07' Command not
* supported o X'08' Address type not supported o X'09' to X'FF' unassigned o RSV RESERVED o
* ATYP address type of following address o IP V4 address: X'01' o DOMAINNAME: X'03' o IP V6
* address: X'04' o BND.ADDR server bound address o BND.PORT server bound port in network
* octet order
*/
// in.read(buf, 0, 4);
fill(in, buf, 4);
if (buf[1] != 0) {
try {
socket.close();
} catch (Exception eee) {
}
throw new JSchProxyException("ProxySOCKS5: server returns " + buf[1]);
}
switch (buf[3] & 0xff) {
case 1:
// in.read(buf, 0, 6);
fill(in, buf, 6);
break;
case 3:
// in.read(buf, 0, 1);
fill(in, buf, 1);
// in.read(buf, 0, buf[0]+2);
fill(in, buf, (buf[0] & 0xff) + 2);
break;
case 4:
// in.read(buf, 0, 18);
fill(in, buf, 18);
break;
default:
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
try {
if (socket != null)
socket.close();
} catch (Exception eee) {
}
String message = "ProxySOCKS5: " + e.toString();
throw new JSchProxyException(message, e);
}
}
@Override
public InputStream getInputStream() {
return in;
}
@Override
public OutputStream getOutputStream() {
return out;
}
@Override
public Socket getSocket() {
return socket;
}
@Override
public void close() {
try {
if (in != null)
in.close();
if (out != null)
out.close();
if (socket != null)
socket.close();
} catch (Exception e) {
}
in = null;
out = null;
socket = null;
}
public static int getDefaultPort() {
return DEFAULTPORT;
}
private void fill(InputStream in, byte[] buf, int len) throws JSchException, IOException {
int s = 0;
while (s < len) {
int i = in.read(buf, s, len - s);
if (i <= 0) {
throw new JSchProxyException("ProxySOCKS5: stream is closed");
}
s += i;
}
}
}