diff src/com/trilead/ssh2/channel/DynamicAcceptThread.java @ 0:0ce5cc452d02

initial version
author Carl Byington <carl@five-ten-sg.com>
date Thu, 22 May 2014 10:41:19 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/trilead/ssh2/channel/DynamicAcceptThread.java	Thu May 22 10:41:19 2014 -0700
@@ -0,0 +1,295 @@
+/*
+ * ConnectBot: simple, powerful, open-source SSH client for Android
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NoRouteToHostException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import net.sourceforge.jsocks.Proxy;
+import net.sourceforge.jsocks.ProxyMessage;
+import net.sourceforge.jsocks.Socks4Message;
+import net.sourceforge.jsocks.Socks5Message;
+import net.sourceforge.jsocks.SocksException;
+import net.sourceforge.jsocks.server.ServerAuthenticator;
+import net.sourceforge.jsocks.server.ServerAuthenticatorNone;
+
+/**
+ * DynamicAcceptThread.
+ *
+ * @author Kenny Root
+ * @version $Id$
+ */
+public class DynamicAcceptThread extends Thread implements IChannelWorkerThread {
+    private ChannelManager cm;
+    private ServerSocket ss;
+
+    class DynamicAcceptRunnable implements Runnable {
+        private static final int idleTimeout    = 180000; //3 minutes
+
+        private ServerAuthenticator auth;
+        private Socket sock;
+        private InputStream in;
+        private OutputStream out;
+        private ProxyMessage msg;
+
+        public DynamicAcceptRunnable(ServerAuthenticator auth, Socket sock) {
+            this.auth = auth;
+            this.sock = sock;
+            setName("DynamicAcceptRunnable");
+        }
+
+        public void run() {
+            try {
+                startSession();
+            }
+            catch (IOException ioe) {
+                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);
+            }
+            finally {
+                if (auth != null)
+                    auth.endSession();
+            }
+        }
+
+        private 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 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 void handleRequest(ProxyMessage msg) throws IOException {
+            if (!auth.checkRequest(msg))
+                throw new SocksException(Proxy.SOCKS_FAILURE);
+
+            switch (msg.command) {
+                case Proxy.SOCKS_CMD_CONNECT:
+                    onConnect(msg);
+                    break;
+
+                default:
+                    throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED);
+            }
+        }
+
+        private void startSession() throws IOException {
+            sock.setSoTimeout(idleTimeout);
+
+            try {
+                auth = auth.startSession(sock);
+            }
+            catch (IOException ioe) {
+                System.out.println("Could not start SOCKS session");
+                ioe.printStackTrace();
+                auth = null;
+                return;
+            }
+
+            if (auth == null) { // Authentication failed
+                System.out.println("SOCKS auth failed");
+                return;
+            }
+
+            in = auth.getInputStream();
+            out = auth.getOutputStream();
+            msg = readMsg(in);
+            handleRequest(msg);
+        }
+
+        private void onConnect(ProxyMessage msg) throws IOException {
+            ProxyMessage response = null;
+            Channel cn = null;
+            StreamForwarder r2l = null;
+            StreamForwarder l2r = null;
+
+            if (msg instanceof Socks5Message) {
+                response = new Socks5Message(Proxy.SOCKS_SUCCESS, (InetAddress)null, 0);
+            }
+            else {
+                response = new Socks4Message(Socks4Message.REPLY_OK, (InetAddress)null, 0);
+            }
+
+            response.write(out);
+            String destHost = msg.host;
+
+            if (msg.ip != null)
+                destHost = msg.ip.getHostAddress();
+
+            try {
+                /*
+                 * This may fail, e.g., if the remote port is closed (in
+                 * optimistic terms: not open yet)
+                 */
+                cn = cm.openDirectTCPIPChannel(destHost, msg.port,
+                                               "127.0.0.1", 0);
+            }
+            catch (IOException e) {
+                /*
+                 * Simply close the local socket and wait for the next incoming
+                 * connection
+                 */
+                try {
+                    sock.close();
+                }
+                catch (IOException ignore) {
+                }
+
+                return;
+            }
+
+            try {
+                r2l = new StreamForwarder(cn, null, sock, cn.stdoutStream, out, "RemoteToLocal");
+                l2r = new StreamForwarder(cn, r2l, sock, in, cn.stdinStream, "LocalToRemote");
+            }
+            catch (IOException e) {
+                try {
+                    /*
+                     * This message is only visible during debugging, since we
+                     * discard the channel immediatelly
+                     */
+                    cn.cm.closeChannel(cn,
+                                       "Weird error during creation of StreamForwarder ("
+                                       + e.getMessage() + ")", true);
+                }
+                catch (IOException ignore) {
+                }
+
+                return;
+            }
+
+            r2l.setDaemon(true);
+            l2r.setDaemon(true);
+            r2l.start();
+            l2r.start();
+        }
+    }
+
+    public DynamicAcceptThread(ChannelManager cm, int local_port)
+    throws IOException {
+        this.cm = cm;
+        setName("DynamicAcceptThread");
+        ss = new ServerSocket(local_port);
+    }
+
+    public DynamicAcceptThread(ChannelManager cm, InetSocketAddress localAddress)
+    throws IOException {
+        this.cm = cm;
+        ss = new ServerSocket();
+        ss.bind(localAddress);
+    }
+
+    @Override
+    public void run() {
+        try {
+            cm.registerThread(this);
+        }
+        catch (IOException e) {
+            stopWorking();
+            return;
+        }
+
+        while (true) {
+            Socket sock = null;
+
+            try {
+                sock = ss.accept();
+            }
+            catch (IOException e) {
+                stopWorking();
+                return;
+            }
+
+            DynamicAcceptRunnable dar = new DynamicAcceptRunnable(new ServerAuthenticatorNone(), sock);
+            Thread t = new Thread(dar);
+            t.setDaemon(true);
+            t.start();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see com.trilead.ssh2.channel.IChannelWorkerThread#stopWorking()
+     */
+    public void stopWorking() {
+        try {
+            /* This will lead to an IOException in the ss.accept() call */
+            ss.close();
+        }
+        catch (IOException e) {
+        }
+    }
+}