72467d916c
Tests are updated to use the loopback address instead of the wildcard to avoid traffic being routed to a different server than what was intended by the test. Reviewed-by: chegar
544 lines
17 KiB
Java
544 lines
17 KiB
Java
/*
|
|
* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code 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
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
import java.net.*;
|
|
import java.io.*;
|
|
import java.util.HashMap;
|
|
|
|
public class SocksServer extends Thread implements Closeable {
|
|
// Some useful SOCKS constant
|
|
|
|
static final int PROTO_VERS4 = 4;
|
|
static final int PROTO_VERS = 5;
|
|
static final int DEFAULT_PORT = 1080;
|
|
|
|
static final int NO_AUTH = 0;
|
|
static final int GSSAPI = 1;
|
|
static final int USER_PASSW = 2;
|
|
static final int NO_METHODS = -1;
|
|
|
|
static final int CONNECT = 1;
|
|
static final int BIND = 2;
|
|
static final int UDP_ASSOC = 3;
|
|
|
|
static final int IPV4 = 1;
|
|
static final int DOMAIN_NAME = 3;
|
|
static final int IPV6 = 4;
|
|
|
|
static final int REQUEST_OK = 0;
|
|
static final int GENERAL_FAILURE = 1;
|
|
static final int NOT_ALLOWED = 2;
|
|
static final int NET_UNREACHABLE = 3;
|
|
static final int HOST_UNREACHABLE = 4;
|
|
static final int CONN_REFUSED = 5;
|
|
static final int TTL_EXPIRED = 6;
|
|
static final int CMD_NOT_SUPPORTED = 7;
|
|
static final int ADDR_TYPE_NOT_SUP = 8;
|
|
|
|
private int port;
|
|
private ServerSocket server;
|
|
private boolean useV4 = false;
|
|
private HashMap<String,String> users = new HashMap<>();
|
|
private volatile boolean done = false;
|
|
// Inner class to handle protocol with client
|
|
// This is the bulk of the work (protocol handler)
|
|
class ClientHandler extends Thread {
|
|
private InputStream in;
|
|
private OutputStream out;
|
|
private Socket client;
|
|
private Socket dest;
|
|
|
|
// Simple tunneling class, moving bits from one stream to another
|
|
|
|
class Tunnel extends Thread {
|
|
private InputStream tin;
|
|
private OutputStream tout;
|
|
|
|
Tunnel(InputStream in, OutputStream out) {
|
|
tin = in;
|
|
tout = out;
|
|
}
|
|
|
|
public void run() {
|
|
int b;
|
|
while (true) {
|
|
try {
|
|
b = tin.read();
|
|
if (b == -1) {
|
|
tin.close();
|
|
tout.close();
|
|
return;
|
|
}
|
|
tout.write(b);
|
|
tout.flush();
|
|
} catch (IOException e) {
|
|
// actually exit from the thread
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ClientHandler(Socket s) throws IOException {
|
|
client = s;
|
|
in = new BufferedInputStream(client.getInputStream());
|
|
out = new BufferedOutputStream(client.getOutputStream());
|
|
}
|
|
|
|
private void readBuf(InputStream is, byte[] buf) throws IOException {
|
|
int l = buf.length;
|
|
int count = 0;
|
|
int i;
|
|
do {
|
|
i = is.read(buf, count, l - count);
|
|
if (i == -1)
|
|
throw new IOException("unexpected EOF");
|
|
count += i;
|
|
} while (count < l);
|
|
}
|
|
|
|
|
|
private boolean userPassAuth() throws IOException {
|
|
int ver = in.read();
|
|
int ulen = in.read();
|
|
if (ulen <= 0)
|
|
throw new SocketException("SOCKS protocol error");
|
|
byte[] buf = new byte[ulen];
|
|
readBuf(in, buf);
|
|
String uname = new String(buf);
|
|
String password = null;
|
|
ulen = in.read();
|
|
if (ulen < 0)
|
|
throw new SocketException("SOCKS protocol error");
|
|
if (ulen > 0) {
|
|
buf = new byte[ulen];
|
|
readBuf(in, buf);
|
|
password = new String(buf);
|
|
}
|
|
// Check username/password validity here
|
|
System.err.println("User: '" + uname);
|
|
System.err.println("PSWD: '" + password);
|
|
if (users.containsKey(uname)) {
|
|
String p1 = users.get(uname);
|
|
System.err.println("p1 = " + p1);
|
|
if (p1.equals(password)) {
|
|
out.write(PROTO_VERS);
|
|
out.write(REQUEST_OK);
|
|
out.flush();
|
|
return true;
|
|
}
|
|
}
|
|
out.write(PROTO_VERS);
|
|
out.write(NOT_ALLOWED);
|
|
out.flush();
|
|
return false;
|
|
}
|
|
|
|
private void purge() throws IOException {
|
|
boolean done = false;
|
|
int i = 0;
|
|
client.setSoTimeout(1000);
|
|
while(!done && i != -1) {
|
|
try {
|
|
i = in.read();
|
|
} catch(IOException e) {
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Handle the SOCKS version 4 protocl
|
|
|
|
private void getRequestV4() throws IOException {
|
|
int ver = in.read();
|
|
int cmd = in.read();
|
|
if (ver == -1 || cmd == -1) {
|
|
// EOF
|
|
in.close();
|
|
out.close();
|
|
return;
|
|
}
|
|
|
|
if (ver != 0 && ver != 4) {
|
|
out.write(PROTO_VERS4);
|
|
out.write(91); // Bad Request
|
|
out.write(0);
|
|
out.write(0);
|
|
out.write(0);
|
|
out.write(0);
|
|
out.write(0);
|
|
out.write(0);
|
|
out.write(0);
|
|
out.flush();
|
|
purge();
|
|
out.close();
|
|
in.close();
|
|
return;
|
|
}
|
|
|
|
if (cmd == CONNECT) {
|
|
int port = ((in.read() & 0xff) << 8);
|
|
port += (in.read() & 0xff);
|
|
byte[] buf = new byte[4];
|
|
readBuf(in, buf);
|
|
InetAddress addr = InetAddress.getByAddress(buf);
|
|
// We don't use the username...
|
|
int c;
|
|
do {
|
|
c = (in.read() & 0xff);
|
|
} while (c!=0);
|
|
boolean ok = true;
|
|
try {
|
|
dest = new Socket(addr, port);
|
|
} catch (IOException e) {
|
|
ok = false;
|
|
}
|
|
if (!ok) {
|
|
out.write(PROTO_VERS4);
|
|
out.write(91);
|
|
out.write(0);
|
|
out.write(0);
|
|
out.write(buf);
|
|
out.flush();
|
|
purge();
|
|
out.close();
|
|
in.close();
|
|
return;
|
|
}
|
|
out.write(PROTO_VERS4);
|
|
out.write(90); // Success
|
|
out.write((port >> 8) & 0xff);
|
|
out.write(port & 0xff);
|
|
out.write(buf);
|
|
out.flush();
|
|
InputStream in2 = new BufferedInputStream(dest.getInputStream());
|
|
OutputStream out2 = new BufferedOutputStream(dest.getOutputStream());
|
|
|
|
Tunnel tunnel = new Tunnel(in2, out);
|
|
tunnel.start();
|
|
|
|
int b = 0;
|
|
do {
|
|
try {
|
|
b = in.read();
|
|
if (b == -1) {
|
|
in.close();
|
|
out2.close();
|
|
return;
|
|
}
|
|
out2.write(b);
|
|
out2.flush();
|
|
} catch (IOException ex) {
|
|
}
|
|
} while (!client.isClosed());
|
|
}
|
|
}
|
|
|
|
|
|
// Negociate the authentication scheme with the client
|
|
private void negociate() throws IOException {
|
|
int ver = in.read();
|
|
int n = in.read();
|
|
byte[] buf = null;
|
|
if (n > 0) {
|
|
buf = new byte[n];
|
|
readBuf(in, buf);
|
|
}
|
|
int scheme = NO_AUTH;
|
|
for (int i = 0; i < n; i++)
|
|
if (buf[i] == USER_PASSW)
|
|
scheme = USER_PASSW;
|
|
out.write(PROTO_VERS);
|
|
out.write(scheme);
|
|
out.flush();
|
|
if (scheme == USER_PASSW)
|
|
userPassAuth();
|
|
}
|
|
|
|
// Send error message then close the streams
|
|
private void sendError(int code) {
|
|
try {
|
|
out.write(PROTO_VERS);
|
|
out.write(code);
|
|
out.write(0);
|
|
out.write(IPV4);
|
|
for (int i=0; i<6; i++)
|
|
out.write(0);
|
|
out.flush();
|
|
out.close();
|
|
} catch (IOException ex) {
|
|
}
|
|
}
|
|
|
|
// Actually connect the proxy to the destination then initiate tunneling
|
|
|
|
private void doConnect(InetSocketAddress addr) throws IOException {
|
|
dest = new Socket();
|
|
try {
|
|
dest.connect(addr, 10000);
|
|
} catch (SocketTimeoutException ex) {
|
|
sendError(HOST_UNREACHABLE);
|
|
return;
|
|
} catch (ConnectException cex) {
|
|
sendError(CONN_REFUSED);
|
|
return;
|
|
}
|
|
// Success
|
|
InetAddress iadd = addr.getAddress();
|
|
if (iadd instanceof Inet4Address) {
|
|
out.write(PROTO_VERS);
|
|
out.write(REQUEST_OK);
|
|
out.write(0);
|
|
out.write(IPV4);
|
|
out.write(iadd.getAddress());
|
|
} else if (iadd instanceof Inet6Address) {
|
|
out.write(PROTO_VERS);
|
|
out.write(REQUEST_OK);
|
|
out.write(0);
|
|
out.write(IPV6);
|
|
out.write(iadd.getAddress());
|
|
} else {
|
|
sendError(GENERAL_FAILURE);
|
|
return;
|
|
}
|
|
out.write((addr.getPort() >> 8) & 0xff);
|
|
out.write((addr.getPort() >> 0) & 0xff);
|
|
out.flush();
|
|
|
|
InputStream in2 = new BufferedInputStream(dest.getInputStream());
|
|
OutputStream out2 = new BufferedOutputStream(dest.getOutputStream());
|
|
|
|
Tunnel tunnel = new Tunnel(in2, out);
|
|
tunnel.start();
|
|
|
|
int b = 0;
|
|
do {
|
|
// Note that the socket might be closed from another thread (the tunnel)
|
|
try {
|
|
b = in.read();
|
|
if (b == -1) {
|
|
in.close();
|
|
out2.close();
|
|
return;
|
|
}
|
|
out2.write(b);
|
|
out2.flush();
|
|
} catch(IOException ioe) {
|
|
}
|
|
} while (!client.isClosed());
|
|
}
|
|
|
|
private void doBind(InetSocketAddress addr) throws IOException {
|
|
ServerSocket svr = new ServerSocket();
|
|
svr.bind(null);
|
|
InetSocketAddress bad = (InetSocketAddress) svr.getLocalSocketAddress();
|
|
out.write(PROTO_VERS);
|
|
out.write(REQUEST_OK);
|
|
out.write(0);
|
|
out.write(IPV4);
|
|
out.write(bad.getAddress().getAddress());
|
|
out.write((bad.getPort() >> 8) & 0xff);
|
|
out.write((bad.getPort() & 0xff));
|
|
out.flush();
|
|
dest = svr.accept();
|
|
bad = (InetSocketAddress) dest.getRemoteSocketAddress();
|
|
out.write(PROTO_VERS);
|
|
out.write(REQUEST_OK);
|
|
out.write(0);
|
|
out.write(IPV4);
|
|
out.write(bad.getAddress().getAddress());
|
|
out.write((bad.getPort() >> 8) & 0xff);
|
|
out.write((bad.getPort() & 0xff));
|
|
out.flush();
|
|
InputStream in2 = dest.getInputStream();
|
|
OutputStream out2 = dest.getOutputStream();
|
|
|
|
Tunnel tunnel = new Tunnel(in2, out);
|
|
tunnel.start();
|
|
|
|
int b = 0;
|
|
do {
|
|
// Note that the socket might be close from another thread (the tunnel)
|
|
try {
|
|
b = in.read();
|
|
if (b == -1) {
|
|
in.close();
|
|
out2.close();
|
|
return;
|
|
}
|
|
out2.write(b);
|
|
out2.flush();
|
|
} catch(IOException ioe) {
|
|
}
|
|
} while (!client.isClosed());
|
|
|
|
}
|
|
|
|
// Handle the SOCKS v5 requests
|
|
|
|
private void getRequest() throws IOException {
|
|
int ver = in.read();
|
|
int cmd = in.read();
|
|
if (ver == -1 || cmd == -1) {
|
|
in.close();
|
|
out.close();
|
|
return;
|
|
}
|
|
int rsv = in.read();
|
|
int atyp = in.read();
|
|
String addr = null;
|
|
int port = 0;
|
|
|
|
switch(atyp) {
|
|
case IPV4:
|
|
{
|
|
byte[] buf = new byte[4];
|
|
readBuf(in, buf);
|
|
addr = InetAddress.getByAddress(buf).getHostAddress();
|
|
}
|
|
break;
|
|
case DOMAIN_NAME:
|
|
{
|
|
int i = in.read();
|
|
byte[] buf = new byte[i];
|
|
readBuf(in, buf);
|
|
addr = new String(buf);
|
|
}
|
|
break;
|
|
case IPV6:
|
|
{
|
|
byte[] buf = new byte[16];
|
|
readBuf(in, buf);
|
|
addr = InetAddress.getByAddress(buf).getHostAddress();
|
|
}
|
|
break;
|
|
}
|
|
|
|
port = ((in.read()&0xff) << 8);
|
|
port += (in.read()&0xff);
|
|
|
|
InetSocketAddress socAddr = new InetSocketAddress(addr, port);
|
|
switch(cmd) {
|
|
case CONNECT:
|
|
doConnect(socAddr);
|
|
break;
|
|
case BIND:
|
|
doBind(socAddr);
|
|
break;
|
|
case UDP_ASSOC:
|
|
// doUDP(socAddr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void run() {
|
|
String line = null;
|
|
try {
|
|
if (useV4) {
|
|
getRequestV4();
|
|
} else {
|
|
negociate();
|
|
getRequest();
|
|
}
|
|
} catch (IOException ex) {
|
|
try {
|
|
sendError(GENERAL_FAILURE);
|
|
} catch (Exception e) {
|
|
}
|
|
} finally {
|
|
try {
|
|
client.close();
|
|
} catch (IOException e2) {
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public SocksServer(int port, boolean v4) throws IOException {
|
|
this(port);
|
|
this.useV4 = v4;
|
|
}
|
|
|
|
public SocksServer(int port) throws IOException {
|
|
this.port = port;
|
|
server = new ServerSocket();
|
|
if (port == 0) {
|
|
server.bind(null);
|
|
this.port = server.getLocalPort();
|
|
} else {
|
|
server.bind(new InetSocketAddress(port));
|
|
}
|
|
}
|
|
|
|
public SocksServer(InetAddress addr, int port, boolean useV4) throws IOException {
|
|
this.port = port;
|
|
this.useV4 = useV4;
|
|
server = new ServerSocket();
|
|
if (port == 0 && addr == null) {
|
|
server.bind(null);
|
|
this.port = server.getLocalPort();
|
|
} else if (port == 0 && addr != null) {
|
|
server.bind(new InetSocketAddress(addr, 0));
|
|
this.port = server.getLocalPort();
|
|
} else if (addr == null) {
|
|
assert port != 0;
|
|
server.bind(new InetSocketAddress(port));
|
|
} else {
|
|
assert port != 0;
|
|
server.bind(new InetSocketAddress(addr, port));
|
|
}
|
|
}
|
|
|
|
public SocksServer() throws IOException {
|
|
this (DEFAULT_PORT);
|
|
}
|
|
|
|
public void addUser(String user, String passwd) {
|
|
users.put(user, passwd);
|
|
}
|
|
|
|
public int getPort() {
|
|
return port;
|
|
}
|
|
|
|
public void close() {
|
|
done = true;
|
|
try { server.close(); } catch (IOException unused) {}
|
|
}
|
|
|
|
public void run() {
|
|
ClientHandler cl = null;
|
|
while (!done) {
|
|
try {
|
|
Socket s = server.accept();
|
|
cl = new ClientHandler(s);
|
|
cl.start();
|
|
} catch (IOException ex) {
|
|
if (cl != null)
|
|
cl.interrupt();
|
|
}
|
|
}
|
|
}
|
|
}
|