diff src/net/sourceforge/jsocks/ProxyServer.java @ 0:0ce5cc452d02

initial version
author Carl Byington <carl@five-ten-sg.com>
date Thu, 22 May 2014 10:41:19 -0700
parents
children 205ee2873330
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/sourceforge/jsocks/ProxyServer.java	Thu May 22 10:41:19 2014 -0700
@@ -0,0 +1,639 @@
+package net.sourceforge.jsocks;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PushbackInputStream;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.NoRouteToHostException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import net.sourceforge.jsocks.server.ServerAuthenticator;
+
+/**
+    SOCKS4 and SOCKS5 proxy, handles both protocols simultaniously.
+    Implements all SOCKS commands, including UDP relaying.
+    <p>
+    In order to use it you will need to implement ServerAuthenticator
+    interface. There is an implementation of this interface which does
+    no authentication ServerAuthenticatorNone, but it is very dangerous
+    to use, as it will give access to your local network to anybody
+    in the world. One should never use this authentication scheme unless
+    one have pretty good reason to do so.
+    There is a couple of other authentication schemes in socks.server package.
+    @see socks.server.ServerAuthenticator
+*/
+public class ProxyServer implements Runnable {
+
+    ServerAuthenticator auth;
+    ProxyMessage msg = null;
+
+    Socket sock = null, remote_sock = null;
+    ServerSocket ss = null;
+    UDPRelayServer relayServer = null;
+    InputStream in, remote_in;
+    OutputStream out, remote_out;
+
+    int mode;
+    static final int START_MODE  = 0;
+    static final int ACCEPT_MODE = 1;
+    static final int PIPE_MODE   = 2;
+    static final int ABORT_MODE  = 3;
+
+    static final int BUF_SIZE    = 8192;
+
+    Thread pipe_thread1, pipe_thread2;
+    long lastReadTime;
+
+    protected static int iddleTimeout    = 180000; //3 minutes
+    static int acceptTimeout = 180000; //3 minutes
+
+    static PrintStream log = null;
+    static Proxy proxy;
+
+
+//Public Constructors
+/////////////////////
+
+
+    /**
+     Creates a proxy server with given Authentication scheme.
+     @param auth Authentication scheme to be used.
+     */
+    public ProxyServer(ServerAuthenticator auth) {
+        this.auth = auth;
+    }
+
+//Other constructors
+////////////////////
+
+    protected ProxyServer(ServerAuthenticator auth, Socket s) {
+        this.auth  = auth;
+        this.sock  = s;
+        mode = START_MODE;
+    }
+
+//Public methods
+/////////////////
+
+    /**
+      Set the logging stream. Specifying null disables logging.
+    */
+    public static void setLog(OutputStream out) {
+        if (out == null) {
+            log = null;
+        }
+        else {
+            log = new PrintStream(out, true);
+        }
+
+        UDPRelayServer.log = log;
+    }
+
+    /**
+     Set proxy.
+     <p>
+     Allows Proxy chaining so that one Proxy server is connected to another
+     and so on. If proxy supports SOCKSv4, then only some SOCKSv5 requests
+     can be handled, UDP would not work, however CONNECT and BIND will be
+     translated.
+
+     @param p Proxy which should be used to handle user requests.
+    */
+    public static void setProxy(Proxy p) {
+        proxy = p;
+        UDPRelayServer.proxy = proxy;
+    }
+
+    /**
+     Get proxy.
+     @return Proxy wich is used to handle user requests.
+    */
+    public static Proxy getProxy() {
+        return proxy;
+    }
+
+    /**
+     Sets the timeout for connections, how long shoud server wait
+     for data to arrive before dropping the connection.<br>
+     Zero timeout implies infinity.<br>
+     Default timeout is 3 minutes.
+    */
+    public static void setIddleTimeout(int timeout) {
+        iddleTimeout = timeout;
+    }
+    /**
+     Sets the timeout for BIND command, how long the server should
+     wait for the incoming connection.<br>
+     Zero timeout implies infinity.<br>
+     Default timeout is 3 minutes.
+    */
+    public static void setAcceptTimeout(int timeout) {
+        acceptTimeout = timeout;
+    }
+
+    /**
+     Sets the timeout for UDPRelay server.<br>
+     Zero timeout implies infinity.<br>
+     Default timeout is 3 minutes.
+    */
+    public static void setUDPTimeout(int timeout) {
+        UDPRelayServer.setTimeout(timeout);
+    }
+
+    /**
+      Sets the size of the datagrams used in the UDPRelayServer.<br>
+      Default size is 64K, a bit more than maximum possible size of the
+      datagram.
+     */
+    public static void setDatagramSize(int size) {
+        UDPRelayServer.setDatagramSize(size);
+    }
+
+
+    /**
+      Start the Proxy server at given port.<br>
+      This methods blocks.
+     */
+    public void start(int port) {
+        start(port, 5, null);
+    }
+
+    /**
+      Create a server with the specified port, listen backlog, and local
+      IP address to bind to. The localIP argument can be used on a multi-homed
+      host for a ServerSocket that will only accept connect requests to one of
+      its addresses. If localIP is null, it will default accepting connections
+      on any/all local addresses. The port must be between 0 and 65535,
+      inclusive. <br>
+      This methods blocks.
+     */
+    public void start(int port, int backlog, InetAddress localIP) {
+        try {
+            ss = new ServerSocket(port, backlog, localIP);
+            log("Starting SOCKS Proxy on:" + ss.getInetAddress().getHostAddress() + ":"
+                + ss.getLocalPort());
+
+            while (true) {
+                Socket s = ss.accept();
+                log("Accepted from:" + s.getInetAddress().getHostName() + ":"
+                    + s.getPort());
+                ProxyServer ps = new ProxyServer(auth, s);
+                (new Thread(ps)).start();
+            }
+        }
+        catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+        finally {
+        }
+    }
+
+    /**
+      Stop server operation.It would be wise to interrupt thread running the
+      server afterwards.
+     */
+    public void stop() {
+        try {
+            if (ss != null) ss.close();
+        }
+        catch (IOException ioe) {
+        }
+    }
+
+//Runnable interface
+////////////////////
+    public void run() {
+        switch (mode) {
+            case START_MODE:
+                try {
+                    startSession();
+                }
+                catch (IOException ioe) {
+                    handleException(ioe);
+                    //ioe.printStackTrace();
+                }
+                finally {
+                    abort();
+
+                    if (auth != null) auth.endSession();
+
+                    log("Main thread(client->remote)stopped.");
+                }
+
+                break;
+
+            case ACCEPT_MODE:
+                try {
+                    doAccept();
+                    mode = PIPE_MODE;
+                    pipe_thread1.interrupt(); //Tell other thread that connection have
+                    //been accepted.
+                    pipe(remote_in, out);
+                }
+                catch (IOException ioe) {
+                    //log("Accept exception:"+ioe);
+                    handleException(ioe);
+                }
+                finally {
+                    abort();
+                    log("Accept thread(remote->client) stopped");
+                }
+
+                break;
+
+            case PIPE_MODE:
+                try {
+                    pipe(remote_in, out);
+                }
+                catch (IOException ioe) {
+                }
+                finally {
+                    abort();
+                    log("Support thread(remote->client) stopped");
+                }
+
+                break;
+
+            case ABORT_MODE:
+                break;
+
+            default:
+                log("Unexpected MODE " + mode);
+        }
+    }
+
+//Private methods
+/////////////////
+    private void startSession() throws IOException {
+        sock.setSoTimeout(iddleTimeout);
+
+        try {
+            auth = auth.startSession(sock);
+        }
+        catch (IOException ioe) {
+            log("Auth throwed exception:" + ioe);
+            auth = null;
+            return;
+        }
+
+        if (auth == null) { //Authentication failed
+            log("Authentication failed");
+            return;
+        }
+
+        in = auth.getInputStream();
+        out = auth.getOutputStream();
+        msg = readMsg(in);
+        handleRequest(msg);
+    }
+
+    protected void handleRequest(ProxyMessage msg)
+    throws IOException {
+        if (!auth.checkRequest(msg)) throw new
+            SocksException(Proxy.SOCKS_FAILURE);
+
+        if (msg.ip == null) {
+            if (msg instanceof Socks5Message) {
+                msg.ip = InetAddress.getByName(msg.host);
+            }
+            else
+                throw new SocksException(Proxy.SOCKS_FAILURE);
+        }
+
+        log(msg);
+
+        switch (msg.command) {
+            case Proxy.SOCKS_CMD_CONNECT:
+                onConnect(msg);
+                break;
+
+            case Proxy.SOCKS_CMD_BIND:
+                onBind(msg);
+                break;
+
+            case Proxy.SOCKS_CMD_UDP_ASSOCIATE:
+                onUDP(msg);
+                break;
+
+            default:
+                throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED);
+        }
+    }
+
+    private void handleException(IOException ioe) {
+        //If we couldn't read the request, return;
+        if (msg == null) return;
+
+        //If have been aborted by other thread
+        if (mode == ABORT_MODE) return;
+
+        //If the request was successfully completed, but exception happened later
+        if (mode == PIPE_MODE) return;
+
+        int error_code = Proxy.SOCKS_FAILURE;
+
+        if (ioe instanceof SocksException)
+            error_code = ((SocksException)ioe).errCode;
+        else if (ioe instanceof NoRouteToHostException)
+            error_code = Proxy.SOCKS_HOST_UNREACHABLE;
+        else if (ioe instanceof ConnectException)
+            error_code = Proxy.SOCKS_CONNECTION_REFUSED;
+        else if (ioe instanceof InterruptedIOException)
+            error_code = Proxy.SOCKS_TTL_EXPIRE;
+
+        if (error_code > Proxy.SOCKS_ADDR_NOT_SUPPORTED || error_code < 0) {
+            error_code = Proxy.SOCKS_FAILURE;
+        }
+
+        sendErrorMessage(error_code);
+    }
+
+    private void onConnect(ProxyMessage msg) throws IOException {
+        Socket s;
+        ProxyMessage response = null;
+        s = new Socket(msg.ip, msg.port);
+        log("Connected to " + s.getInetAddress() + ":" + s.getPort());
+
+        if (msg instanceof Socks5Message) {
+            response = new Socks5Message(Proxy.SOCKS_SUCCESS,
+                                         s.getLocalAddress(),
+                                         s.getLocalPort());
+        }
+        else {
+            response = new Socks4Message(Socks4Message.REPLY_OK,
+                                         s.getLocalAddress(), s.getLocalPort());
+        }
+
+        response.write(out);
+        startPipe(s);
+    }
+
+    private void onBind(ProxyMessage msg) throws IOException {
+        ProxyMessage response = null;
+
+        if (proxy == null)
+            ss = new ServerSocket(0);
+        else
+            ss = new SocksServerSocket(proxy, msg.ip, msg.port);
+
+        ss.setSoTimeout(acceptTimeout);
+        log("Trying accept on " + ss.getInetAddress() + ":" + ss.getLocalPort());
+
+        if (msg.version == 5)
+            response = new Socks5Message(Proxy.SOCKS_SUCCESS, ss.getInetAddress(),
+                                         ss.getLocalPort());
+        else
+            response = new Socks4Message(Socks4Message.REPLY_OK,
+                                         ss.getInetAddress(),
+                                         ss.getLocalPort());
+
+        response.write(out);
+        mode = ACCEPT_MODE;
+        pipe_thread1 = Thread.currentThread();
+        pipe_thread2 = new Thread(this);
+        pipe_thread2.start();
+        //Make timeout infinit.
+        sock.setSoTimeout(0);
+        int eof = 0;
+
+        try {
+            while ((eof = in.read()) >= 0) {
+                if (mode != ACCEPT_MODE) {
+                    if (mode != PIPE_MODE) return; //Accept failed
+
+                    remote_out.write(eof);
+                    break;
+                }
+            }
+        }
+        catch (EOFException eofe) {
+            //System.out.println("EOF exception");
+            return;//Connection closed while we were trying to accept.
+        }
+        catch (InterruptedIOException iioe) {
+            //Accept thread interrupted us.
+            //System.out.println("Interrupted");
+            if (mode != PIPE_MODE)
+                return;//If accept thread was not successfull return.
+        }
+        finally {
+            //System.out.println("Finnaly!");
+        }
+
+        if (eof < 0) //Connection closed while we were trying to accept;
+            return;
+
+        //Do not restore timeout, instead timeout is set on the
+        //remote socket. It does not make any difference.
+        pipe(in, remote_out);
+    }
+
+    private void onUDP(ProxyMessage msg) throws IOException {
+        if (msg.ip.getHostAddress().equals("0.0.0.0"))
+            msg.ip = sock.getInetAddress();
+
+        log("Creating UDP relay server for " + msg.ip + ":" + msg.port);
+        relayServer = new UDPRelayServer(msg.ip, msg.port,
+                                         Thread.currentThread(), sock, auth);
+        ProxyMessage response;
+        response = new Socks5Message(Proxy.SOCKS_SUCCESS,
+                                     relayServer.relayIP, relayServer.relayPort);
+        response.write(out);
+        relayServer.start();
+        //Make timeout infinit.
+        sock.setSoTimeout(0);
+
+        try {
+            while (in.read() >= 0) /*do nothing*/;
+        }
+        catch (EOFException eofe) {
+        }
+    }
+
+//Private methods
+//////////////////
+
+    private void doAccept() throws IOException {
+        Socket s;
+        long startTime = System.currentTimeMillis();
+
+        while (true) {
+            s = ss.accept();
+
+            if (s.getInetAddress().equals(msg.ip)) {
+                //got the connection from the right host
+                //Close listenning socket.
+                ss.close();
+                break;
+            }
+            else if (ss instanceof SocksServerSocket) {
+                //We can't accept more then one connection
+                s.close();
+                ss.close();
+                throw new SocksException(Proxy.SOCKS_FAILURE);
+            }
+            else {
+                if (acceptTimeout != 0) { //If timeout is not infinit
+                    int newTimeout = acceptTimeout - (int)(System.currentTimeMillis() -
+                                                           startTime);
+
+                    if (newTimeout <= 0) throw new InterruptedIOException(
+                            "In doAccept()");
+
+                    ss.setSoTimeout(newTimeout);
+                }
+
+                s.close(); //Drop all connections from other hosts
+            }
+        }
+
+        //Accepted connection
+        remote_sock = s;
+        remote_in = s.getInputStream();
+        remote_out = s.getOutputStream();
+        //Set timeout
+        remote_sock.setSoTimeout(iddleTimeout);
+        log("Accepted from " + s.getInetAddress() + ":" + s.getPort());
+        ProxyMessage response;
+
+        if (msg.version == 5)
+            response = new Socks5Message(Proxy.SOCKS_SUCCESS, s.getInetAddress(),
+                                         s.getPort());
+        else
+            response = new Socks4Message(Socks4Message.REPLY_OK,
+                                         s.getInetAddress(), s.getPort());
+
+        response.write(out);
+    }
+
+    protected ProxyMessage readMsg(InputStream in) throws IOException {
+        PushbackInputStream push_in;
+
+        if (in instanceof PushbackInputStream)
+            push_in = (PushbackInputStream) in;
+        else
+            push_in = new PushbackInputStream(in);
+
+        int version = push_in.read();
+        push_in.unread(version);
+        ProxyMessage msg;
+
+        if (version == 5) {
+            msg = new Socks5Message(push_in, false);
+        }
+        else if (version == 4) {
+            msg = new Socks4Message(push_in, false);
+        }
+        else {
+            throw new SocksException(Proxy.SOCKS_FAILURE);
+        }
+
+        return msg;
+    }
+
+    private void startPipe(Socket s) {
+        mode = PIPE_MODE;
+        remote_sock = s;
+
+        try {
+            remote_in = s.getInputStream();
+            remote_out = s.getOutputStream();
+            pipe_thread1 = Thread.currentThread();
+            pipe_thread2 = new Thread(this);
+            pipe_thread2.start();
+            pipe(in, remote_out);
+        }
+        catch (IOException ioe) {
+        }
+    }
+
+    private void sendErrorMessage(int error_code) {
+        ProxyMessage err_msg;
+
+        if (msg instanceof Socks4Message)
+            err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED);
+        else
+            err_msg = new Socks5Message(error_code);
+
+        try {
+            err_msg.write(out);
+        }
+        catch (IOException ioe) {}
+    }
+
+    private synchronized void abort() {
+        if (mode == ABORT_MODE) return;
+
+        mode = ABORT_MODE;
+
+        try {
+            log("Aborting operation");
+
+            if (remote_sock != null) remote_sock.close();
+
+            if (sock != null) sock.close();
+
+            if (relayServer != null) relayServer.stop();
+
+            if (ss != null) ss.close();
+
+            if (pipe_thread1 != null) pipe_thread1.interrupt();
+
+            if (pipe_thread2 != null) pipe_thread2.interrupt();
+        }
+        catch (IOException ioe) {}
+    }
+
+    static final void log(String s) {
+        if (log != null) {
+            log.println(s);
+            log.flush();
+        }
+    }
+
+    static final void log(ProxyMessage msg) {
+        log("Request version:" + msg.version +
+            "\tCommand: " + command2String(msg.command));
+        log("IP:" + msg.ip + "\tPort:" + msg.port +
+            (msg.version == 4 ? "\tUser:" + msg.user : ""));
+    }
+
+    private void pipe(InputStream in, OutputStream out) throws IOException {
+        lastReadTime = System.currentTimeMillis();
+        byte[] buf = new byte[BUF_SIZE];
+        int len = 0;
+
+        while (len >= 0) {
+            try {
+                if (len != 0) {
+                    out.write(buf, 0, len);
+                    out.flush();
+                }
+
+                len = in.read(buf);
+                lastReadTime = System.currentTimeMillis();
+            }
+            catch (InterruptedIOException iioe) {
+                if (iddleTimeout == 0) return; //Other thread interrupted us.
+
+                long timeSinceRead = System.currentTimeMillis() - lastReadTime;
+
+                if (timeSinceRead >= iddleTimeout - 1000) //-1s for adjustment.
+                    return;
+
+                len = 0;
+            }
+        }
+    }
+    static final String command_names[] = {"CONNECT", "BIND", "UDP_ASSOCIATE"};
+
+    static final String command2String(int cmd) {
+        if (cmd > 0 && cmd < 4) return command_names[cmd - 1];
+        else return "Unknown Command " + cmd;
+    }
+}