Mercurial > 510Connectbot
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) { + } + } +}