From 9b3322b1b870b3e677723d33ea6502c2faf22173 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Sat, 8 Nov 2008 10:01:55 +0000 Subject: * Add dynamic port forwarding. --- lib/src/main/java/com/trilead/ssh2/Connection.java | 52 ++++++ .../com/trilead/ssh2/DynamicPortForwarder.java | 67 +++++++ .../trilead/ssh2/channel/DynamicAcceptThread.java | 199 +++++++++++++++++++++ 3 files changed, 318 insertions(+) create mode 100644 lib/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java create mode 100644 lib/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java diff --git a/lib/src/main/java/com/trilead/ssh2/Connection.java b/lib/src/main/java/com/trilead/ssh2/Connection.java index 8fbf6fa..2b244c2 100644 --- a/lib/src/main/java/com/trilead/ssh2/Connection.java +++ b/lib/src/main/java/com/trilead/ssh2/Connection.java @@ -932,6 +932,58 @@ public class Connection return new LocalStreamForwarder(cm, host_to_connect, port_to_connect); } + /** + * Creates a new {@link DynamicPortForwarder}. A + * DynamicPortForwarder forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host that is chosen via + * the SOCKS protocol. + *

+ * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param local_port + * @return A {@link DynamicPortForwarder} object. + * @throws IOException + */ + public synchronized DynamicPortForwarder createDynamicPortForwarder(int local_port) throws IOException + { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new DynamicPortForwarder(cm, local_port); + } + + /** + * Creates a new {@link DynamicPortForwarder}. A + * DynamicPortForwarder forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host that is chosen via + * the SOCKS protocol. + *

+ * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param addr + * specifies the InetSocketAddress where the local socket shall + * be bound to. + * @return A {@link DynamicPortForwarder} object. + * @throws IOException + */ + public synchronized DynamicPortForwarder createDynamicPortForwarder(InetSocketAddress addr) throws IOException + { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new DynamicPortForwarder(cm, addr); + } + /** * Create a very basic {@link SCPClient} that can be used to copy files * from/to the SSH-2 server. diff --git a/lib/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java b/lib/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java new file mode 100644 index 0000000..1fece14 --- /dev/null +++ b/lib/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java @@ -0,0 +1,67 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any dater version. + + This program 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +package com.trilead.ssh2; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import com.trilead.ssh2.channel.ChannelManager; +import com.trilead.ssh2.channel.DynamicAcceptThread; + +/** + * A DynamicPortForwarder forwards TCP/IP connections to a local + * port via the secure tunnel to another host which is selected via the + * SOCKS protocol. Checkout {@link Connection#createDynamicPortForwarder(int)} + * on how to create one. + * + * @author Kenny Root + * @version $Id: $ + */ +public class DynamicPortForwarder { + ChannelManager cm; + + DynamicAcceptThread dat; + + DynamicPortForwarder(ChannelManager cm, int local_port) + throws IOException + { + this.cm = cm; + + dat = new DynamicAcceptThread(cm, local_port); + dat.setDaemon(true); + dat.start(); + } + + DynamicPortForwarder(ChannelManager cm, InetSocketAddress addr) throws IOException { + this.cm = cm; + + dat = new DynamicAcceptThread(cm, addr); + dat.setDaemon(true); + dat.start(); + } + + /** + * Stop TCP/IP forwarding of newly arriving connections. + * + * @throws IOException + */ + public void close() throws IOException + { + dat.stopWorking(); + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java b/lib/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java new file mode 100644 index 0000000..18fa279 --- /dev/null +++ b/lib/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java @@ -0,0 +1,199 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; + +import net.sourceforge.jsocks.Proxy; +import net.sourceforge.jsocks.ProxyMessage; +import net.sourceforge.jsocks.ProxyServer; +import net.sourceforge.jsocks.Socks4Message; +import net.sourceforge.jsocks.Socks5Message; +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 extends ProxyServer { + private ServerAuthenticator auth; + private Socket sock; + private InputStream in; + private OutputStream out; + private ProxyMessage msg; + + public DynamicAcceptRunnable(ServerAuthenticator auth, Socket sock) { + super(auth, sock); + this.auth = auth; + this.sock = sock; + } + + protected void startSession() throws IOException { + sock.setSoTimeout(ProxyServer.iddleTimeout); + + 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); + } + + protected void onConnect(ProxyMessage msg) throws IOException { + ProxyMessage response = null; + Channel cn = null; + StreamForwarder r2l = null; + StreamForwarder l2r = null; + + try { + /* + * This may fail, e.g., if the remote port is closed (in + * optimistic terms: not open yet) + */ + + cn = cm.openDirectTCPIPChannel(msg.host, msg.port, + sock.getInetAddress().getHostAddress(), + sock.getPort()); + + } 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, null, 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(); + + if (msg instanceof Socks5Message) { + response = new Socks5Message(Proxy.SOCKS_SUCCESS, sock + .getLocalAddress(), sock.getLocalPort()); + } else { + response = new Socks4Message(Socks4Message.REPLY_OK, sock + .getLocalAddress(), sock.getLocalPort()); + + } + response.write(out); + } + } + + public DynamicAcceptThread(ChannelManager cm, int local_port) + throws IOException { + this.cm = cm; + + ss = new ServerSocket(local_port); + } + + public DynamicAcceptThread(ChannelManager cm, InetSocketAddress localAddress) + throws IOException { + this.cm = cm; + + ss = new ServerSocket(); + ss.bind(localAddress); + } + + 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) { + } + } +} -- cgit v1.2.3