/* * Copyright (c) 2002, 2012, 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 { // 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); } catch (IOException e) { // actually exit from the thread return; } } } } ClientHandler(Socket s) throws IOException { client = s; in = client.getInputStream(); out = 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 = dest.getInputStream(); OutputStream out2 = 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); } 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 = dest.getInputStream(); OutputStream out2 = 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); } 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); } 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); int i = 0; StringBuffer sb = new StringBuffer(); for (i = 0; i < 4; i++) { sb.append(buf[i]&0xff); if (i < 3) sb.append('.'); } addr = sb.toString(); } 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); int i = 0; StringBuffer sb = new StringBuffer(); for (i = 0; i<16; i++) { sb.append(Integer.toHexString(buf[i]&0xff)); if (i < 15) sb.append(':'); } addr = sb.toString(); } 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() throws IOException { this (DEFAULT_PORT); } public void addUser(String user, String passwd) { users.put(user, passwd); } public int getPort() { return port; } public void terminate() { 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(); } } } }