view src/com/trilead/ssh2/channel/DynamicAcceptThread.java @ 98:16e023784917

protect both read calls with test for blocking
author Carl Byington <carl@five-ten-sg.com>
date Tue, 17 Jun 2014 11:35:01 -0700
parents 0ce5cc452d02
children
line wrap: on
line source

/*
 * 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) {
        }
    }
}