You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2014/11/17 21:12:48 UTC
[2/2] mina-sshd git commit: [SSHD-371] Support Socks proxy with ssh
tunnelling on the client side
[SSHD-371] Support Socks proxy with ssh tunnelling on the client side
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/4b7a87f1
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/4b7a87f1
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/4b7a87f1
Branch: refs/heads/master
Commit: 4b7a87f1a887c82e1f324856e82d47d89d5f1968
Parents: b0cce8c
Author: Guillaume Nodet <gn...@apache.org>
Authored: Mon Nov 17 21:12:33 2014 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Mon Nov 17 21:12:33 2014 +0100
----------------------------------------------------------------------
.../java/org/apache/sshd/ClientSession.java | 17 ++
.../main/java/org/apache/sshd/SshClient.java | 63 ++--
.../sshd/client/session/ClientSessionImpl.java | 8 +
.../org/apache/sshd/common/TcpipForwarder.java | 5 +
.../common/forward/DefaultTcpipForwarder.java | 143 ++++++----
.../apache/sshd/common/forward/SocksProxy.java | 286 +++++++++++++++++++
.../test/java/org/apache/sshd/ProxyTest.java | 154 ++++++++++
7 files changed, 598 insertions(+), 78 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4b7a87f1/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
index cbcc507..482719b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
@@ -194,6 +194,23 @@ public interface ClientSession extends Session {
void stopRemotePortForwarding(SshdSocketAddress remote) throws IOException;
/**
+ * Start dynamic local port forwarding using a SOCKS proxy.
+ *
+ * @param local
+ * @return
+ * @throws IOException
+ */
+ SshdSocketAddress startDynamicPortForwarding(SshdSocketAddress local) throws IOException;
+
+ /**
+ * Stop a previously started dynamic port forwarding.
+ *
+ * @param local
+ * @throws IOException
+ */
+ void stopDynamicPortForwarding(SshdSocketAddress local) throws IOException;
+
+ /**
* Wait for a specific state.
*/
int waitFor(int mask, long timeout);
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4b7a87f1/sshd-core/src/main/java/org/apache/sshd/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/SshClient.java
index b9fe07d..33b7eeb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/SshClient.java
@@ -62,6 +62,7 @@ import org.apache.sshd.common.Closeable;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.SshdSocketAddress;
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.io.DefaultIoServiceFactoryFactory;
import org.apache.sshd.common.io.IoConnectFuture;
@@ -362,6 +363,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
boolean agentForward = false;
List<String> command = null;
int logLevel = 0;
+ int socksPort = -1;
boolean error = false;
List<String> identities = new ArrayList<String>();
@@ -373,6 +375,13 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
break;
}
port = Integer.parseInt(args[++i]);
+ } else if (command == null && "-D".equals(args[i])) {
+ if (i + 1 >= args.length) {
+ System.err.println("option requires an argument: " + args[i]);
+ error = true;
+ break;
+ }
+ socksPort = Integer.parseInt(args[++i]);
} else if (command == null && "-l".equals(args[i])) {
if (i + 1 >= args.length) {
System.err.println("option requires an argument: " + args[i]);
@@ -386,11 +395,11 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
logLevel += 2;
} else if (command == null && "-vvv".equals(args[i])) {
logLevel += 3;
- } else if ("-A".equals(args[i])) {
+ } else if (command == null && "-A".equals(args[i])) {
agentForward = true;
- } else if ("-a".equals(args[i])) {
+ } else if (command == null && "-a".equals(args[i])) {
agentForward = false;
- } else if ("-i".equals(args[i])) {
+ } else if (command == null && "-i".equals(args[i])) {
if (i + 1 >= args.length) {
System.err.println("option requires and argument: " + args[i]);
error = true;
@@ -402,7 +411,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
error = true;
break;
} else {
- if (host == null) {
+ if (command == null && host == null) {
host = args[i];
} else {
if (command == null) {
@@ -417,7 +426,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
error = true;
}
if (error) {
- System.err.println("usage: ssh [-A|-a] [-v[v][v]] [-l login] [-p port] hostname [command]");
+ System.err.println("usage: ssh [-A|-a] [-v[v][v]] [-D socksPort] [-l login] [-p port] hostname [command]");
System.exit(-1);
}
if (logLevel <= 0) {
@@ -475,6 +484,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
public void welcome(String banner) {
System.out.println(banner);
}
+
public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
String[] answers = new String[prompt.length];
try {
@@ -507,28 +517,33 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
ClientSession session = client.connect(login, host, port).await().getSession();
session.auth().verify();
- ClientChannel channel;
- if (command == null) {
- channel = session.createChannel(ClientChannel.CHANNEL_SHELL);
- ((ChannelShell) channel).setAgentForwarding(agentForward);
- channel.setIn(new NoCloseInputStream(System.in));
+ if (socksPort >= 0) {
+ session.startDynamicPortForwarding(new SshdSocketAddress("localhost", socksPort));
+ Thread.sleep(Long.MAX_VALUE);
} else {
- channel = session.createChannel(ClientChannel.CHANNEL_EXEC);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- Writer w = new OutputStreamWriter(baos);
- for (String cmd : command) {
- w.append(cmd).append(" ");
+ ClientChannel channel;
+ if (command == null) {
+ channel = session.createChannel(ClientChannel.CHANNEL_SHELL);
+ ((ChannelShell) channel).setAgentForwarding(agentForward);
+ channel.setIn(new NoCloseInputStream(System.in));
+ } else {
+ channel = session.createChannel(ClientChannel.CHANNEL_EXEC);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Writer w = new OutputStreamWriter(baos);
+ for (String cmd : command) {
+ w.append(cmd).append(" ");
+ }
+ w.append("\n");
+ w.close();
+ channel.setIn(new ByteArrayInputStream(baos.toByteArray()));
}
- w.append("\n");
- w.close();
- channel.setIn(new ByteArrayInputStream(baos.toByteArray()));
+ channel.setOut(new NoCloseOutputStream(System.out));
+ channel.setErr(new NoCloseOutputStream(System.err));
+ channel.open().await();
+ channel.waitFor(ClientChannel.CLOSED, 0);
+ session.close(false);
+ client.stop();
}
- channel.setOut(new NoCloseOutputStream(System.out));
- channel.setErr(new NoCloseOutputStream(System.err));
- channel.open().await();
- channel.waitFor(ClientChannel.CLOSED, 0);
- session.close(false);
- client.stop();
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4b7a87f1/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index ea3b53d..e4c0258 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -260,6 +260,14 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
getConnectionService().getTcpipForwarder().stopRemotePortForwarding(remote);
}
+ public SshdSocketAddress startDynamicPortForwarding(SshdSocketAddress local) throws IOException {
+ return getConnectionService().getTcpipForwarder().startDynamicPortForwarding(local);
+ }
+
+ public void stopDynamicPortForwarding(SshdSocketAddress local) throws IOException {
+ getConnectionService().getTcpipForwarder().stopDynamicPortForwarding(local);
+ }
+
protected void handleMessage(Buffer buffer) throws Exception {
synchronized (lock) {
super.handleMessage(buffer);
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4b7a87f1/sshd-core/src/main/java/org/apache/sshd/common/TcpipForwarder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/TcpipForwarder.java b/sshd-core/src/main/java/org/apache/sshd/common/TcpipForwarder.java
index aedbf54..cfed8e3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/TcpipForwarder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/TcpipForwarder.java
@@ -84,4 +84,9 @@ public interface TcpipForwarder extends Closeable {
*/
@Deprecated
void close();
+
+ SshdSocketAddress startDynamicPortForwarding(SshdSocketAddress local) throws IOException;
+
+ void stopDynamicPortForwarding(SshdSocketAddress local) throws IOException;
+
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4b7a87f1/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
index d3256ad..b1b340e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
@@ -49,12 +49,13 @@ import org.apache.sshd.common.util.Readable;
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class DefaultTcpipForwarder extends CloseableUtils.AbstractInnerCloseable implements TcpipForwarder, IoHandler {
+public class DefaultTcpipForwarder extends CloseableUtils.AbstractInnerCloseable implements TcpipForwarder {
private final ConnectionService service;
private final Session session;
private final Map<Integer, SshdSocketAddress> localToRemote = new HashMap<Integer, SshdSocketAddress>();
private final Map<Integer, SshdSocketAddress> remoteToLocal = new HashMap<Integer, SshdSocketAddress>();
+ private final Map<Integer, SocksProxy> dynamicLocal = new HashMap<Integer, SocksProxy>();
private final Set<SshdSocketAddress> localForwards = new HashSet<SshdSocketAddress>();
protected IoAcceptor acceptor;
@@ -83,7 +84,7 @@ public class DefaultTcpipForwarder extends CloseableUtils.AbstractInnerCloseable
if (isClosing()) {
throw new IllegalStateException("TcpipForwarder is closing");
}
- SshdSocketAddress bound = doBind(local);
+ SshdSocketAddress bound = doBind(local, new StaticIoHandler());
localToRemote.put(bound.getPort(), remote);
return bound;
}
@@ -124,6 +125,36 @@ public class DefaultTcpipForwarder extends CloseableUtils.AbstractInnerCloseable
}
}
+ public synchronized SshdSocketAddress startDynamicPortForwarding(SshdSocketAddress local) throws IOException {
+ if (local == null) {
+ throw new IllegalArgumentException("Local address is null");
+ }
+ if (local.getPort() < 0) {
+ throw new IllegalArgumentException("Invalid local port: " + local.getPort());
+ }
+ if (isClosed()) {
+ throw new IllegalStateException("TcpipForwarder is closed");
+ }
+ if (isClosing()) {
+ throw new IllegalStateException("TcpipForwarder is closing");
+ }
+ SocksProxy socksProxy = new SocksProxy(service);
+ SshdSocketAddress bound = doBind(local, new SocksProxy(service));
+ dynamicLocal.put(bound.getPort(), socksProxy);
+ return bound;
+ }
+
+ public synchronized void stopDynamicPortForwarding(SshdSocketAddress local) throws IOException {
+ Closeable obj = dynamicLocal.remove(local.getPort());
+ if (obj != null) {
+ obj.close(true);
+ acceptor.unbind(local.toInetSocketAddress());
+ if (acceptor.getBoundAddresses().isEmpty()) {
+ close();
+ }
+ }
+ }
+
public synchronized SshdSocketAddress getForwardedPort(int remotePort) {
return remoteToLocal.get(remotePort);
}
@@ -139,7 +170,7 @@ public class DefaultTcpipForwarder extends CloseableUtils.AbstractInnerCloseable
if (filter == null || !filter.canListen(local, session)) {
throw new IOException("Rejected address: " + local);
}
- SshdSocketAddress bound = doBind(local);
+ SshdSocketAddress bound = doBind(local, new StaticIoHandler());
localForwards.add(bound);
return bound;
}
@@ -160,64 +191,16 @@ public class DefaultTcpipForwarder extends CloseableUtils.AbstractInnerCloseable
@Override
protected synchronized Closeable getInnerCloseable() {
- return builder().close(acceptor).build();
- }
-
- //
- // IoHandler implementation
- //
-
- public void sessionCreated(final IoSession session) throws Exception {
- final TcpipClientChannel channel;
- int localPort = ((InetSocketAddress) session.getLocalAddress()).getPort();
- if (localToRemote.containsKey(localPort)) {
- SshdSocketAddress remote = localToRemote.get(localPort);
- channel = new TcpipClientChannel(TcpipClientChannel.Type.Direct, session, remote);
- } else {
- channel = new TcpipClientChannel(TcpipClientChannel.Type.Forwarded, session, null);
- }
- session.setAttribute(TcpipClientChannel.class, channel);
- this.service.registerChannel(channel);
- channel.open().addListener(new SshFutureListener<OpenFuture>() {
- public void operationComplete(OpenFuture future) {
- Throwable t = future.getException();
- if (t != null) {
- DefaultTcpipForwarder.this.service.unregisterChannel(channel);
- channel.close(false);
- }
- }
- });
- }
-
- public void sessionClosed(IoSession session) throws Exception {
- TcpipClientChannel channel = (TcpipClientChannel) session.getAttribute(TcpipClientChannel.class);
- if (channel != null) {
- log.debug("IoSession {} closed, will now close the channel", session);
- channel.close(false);
- }
- }
-
- public void messageReceived(IoSession session, Readable message) throws Exception {
- TcpipClientChannel channel = (TcpipClientChannel) session.getAttribute(TcpipClientChannel.class);
- Buffer buffer = new Buffer();
- buffer.putBuffer(message);
- channel.waitFor(ClientChannel.OPENED | ClientChannel.CLOSED, Long.MAX_VALUE);
- channel.getInvertedIn().write(buffer.array(), buffer.rpos(), buffer.available());
- channel.getInvertedIn().flush();
- }
-
- public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
- cause.printStackTrace();
- session.close(false);
+ return builder().parallel(dynamicLocal.values()).close(acceptor).build();
}
//
// Private methods
//
- private SshdSocketAddress doBind(SshdSocketAddress address) throws IOException {
+ private SshdSocketAddress doBind(SshdSocketAddress address, IoHandler handler) throws IOException {
if (acceptor == null) {
- acceptor = session.getFactoryManager().getIoServiceFactory().createAcceptor(this);
+ acceptor = session.getFactoryManager().getIoServiceFactory().createAcceptor(handler);
}
Set<SocketAddress> before = acceptor.getBoundAddresses();
try {
@@ -244,4 +227,56 @@ public class DefaultTcpipForwarder extends CloseableUtils.AbstractInnerCloseable
return getClass().getSimpleName() + "[" + session + "]";
}
+ //
+ // Static IoHandler implementation
+ //
+
+ class StaticIoHandler implements IoHandler {
+
+ public void sessionCreated(final IoSession session) throws Exception {
+ final TcpipClientChannel channel;
+ int localPort = ((InetSocketAddress) session.getLocalAddress()).getPort();
+ if (localToRemote.containsKey(localPort)) {
+ SshdSocketAddress remote = localToRemote.get(localPort);
+ channel = new TcpipClientChannel(TcpipClientChannel.Type.Direct, session, remote);
+ } else {
+ channel = new TcpipClientChannel(TcpipClientChannel.Type.Forwarded, session, null);
+ }
+ session.setAttribute(TcpipClientChannel.class, channel);
+ service.registerChannel(channel);
+ channel.open().addListener(new SshFutureListener<OpenFuture>() {
+ public void operationComplete(OpenFuture future) {
+ Throwable t = future.getException();
+ if (t != null) {
+ DefaultTcpipForwarder.this.service.unregisterChannel(channel);
+ channel.close(false);
+ }
+ }
+ });
+ }
+
+ public void sessionClosed(IoSession session) throws Exception {
+ TcpipClientChannel channel = (TcpipClientChannel) session.getAttribute(TcpipClientChannel.class);
+ if (channel != null) {
+ log.debug("IoSession {} closed, will now close the channel", session);
+ channel.close(false);
+ }
+ }
+
+ public void messageReceived(IoSession session, Readable message) throws Exception {
+ TcpipClientChannel channel = (TcpipClientChannel) session.getAttribute(TcpipClientChannel.class);
+ Buffer buffer = new Buffer();
+ buffer.putBuffer(message);
+ channel.waitFor(ClientChannel.OPENED | ClientChannel.CLOSED, Long.MAX_VALUE);
+ channel.getInvertedIn().write(buffer.array(), buffer.rpos(), buffer.available());
+ channel.getInvertedIn().flush();
+ }
+
+ public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
+ cause.printStackTrace();
+ session.close(false);
+ }
+
+ }
+
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4b7a87f1/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java b/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
new file mode 100644
index 0000000..3f61908
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.common.forward;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.sshd.client.future.OpenFuture;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.SshdSocketAddress;
+import org.apache.sshd.common.future.SshFutureListener;
+import org.apache.sshd.common.io.IoHandler;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.session.ConnectionService;
+import org.apache.sshd.common.util.Buffer;
+import org.apache.sshd.common.util.CloseableUtils;
+
+/**
+ * SOCKS proxy server, supporting simple socks4/5 protocols.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SocksProxy extends CloseableUtils.AbstractCloseable implements IoHandler {
+
+ private final ConnectionService service;
+ private final Map<IoSession, Proxy> proxies = new ConcurrentHashMap<IoSession, Proxy>();
+
+ public SocksProxy(ConnectionService service) {
+ this.service = service;
+ }
+
+ public void sessionCreated(IoSession session) throws Exception {
+ if (isClosing()) {
+ throw new SshException("SocksProxy is closing or closed");
+ }
+ }
+
+ public void sessionClosed(IoSession session) throws Exception {
+ Proxy proxy = proxies.remove(session);
+ if (proxy != null) {
+ proxy.close();
+ }
+ }
+
+ public void messageReceived(final IoSession session, org.apache.sshd.common.util.Readable message) throws Exception {
+ Buffer buffer = new Buffer(message.available());
+ buffer.putBuffer(message);
+ Proxy proxy = proxies.get(session);
+ if (proxy == null) {
+ int version = buffer.getByte();
+ if (version == 0x04) {
+ proxy = new Socks4(session);
+ } else if (version == 0x05) {
+ proxy = new Socks5(session);
+ } else {
+ throw new IllegalStateException("Unsupported version: " + version);
+ }
+ proxy.onMessage(buffer);
+ proxies.put(session, proxy);
+ } else {
+ proxy.onMessage(buffer);
+ }
+ }
+
+ public void exceptionCaught(IoSession ioSession, Throwable cause) throws Exception {
+ log.warn("Exception caught, closing socks proxy", cause);
+ ioSession.close(false);
+ }
+
+ public abstract class Proxy {
+
+ IoSession session;
+ TcpipClientChannel channel;
+
+ protected Proxy(IoSession session) {
+ this.session = session;
+ }
+
+ protected void onMessage(Buffer buffer) throws IOException {
+ channel.getInvertedIn().write(buffer.array(), buffer.rpos(), buffer.available());
+ channel.getInvertedIn().flush();
+ }
+
+ public void close() {
+ if (channel != null) {
+ channel.close(false);
+ }
+ }
+
+ protected int getUByte(Buffer buffer) {
+ return buffer.getByte() & 0xFF;
+ }
+
+ protected int getUShort(Buffer buffer) {
+ return (getUByte(buffer) << 8) + getUByte(buffer);
+ }
+ }
+
+ public class Socks4 extends Proxy {
+ public Socks4(IoSession session) {
+ super(session);
+ }
+
+ @Override
+ protected void onMessage(Buffer buffer) throws IOException {
+ if (channel == null) {
+ int cmd = buffer.getByte();
+ if (cmd != 1) {
+ throw new IllegalStateException("Unsupported socks command: " + cmd);
+ }
+ int port = getUShort(buffer);
+ String host = Integer.toString(getUByte(buffer)) + "."
+ + Integer.toString(getUByte(buffer)) + "."
+ + Integer.toString(getUByte(buffer)) + "."
+ + Integer.toString(getUByte(buffer));
+ String userId = getNTString(buffer);
+ // Socks4a
+ if (host.startsWith("0.0.0.")) {
+ host = getNTString(buffer);
+ }
+ log.debug("Received socks4 connection request to {}:{}", host, port);
+ SshdSocketAddress remote = new SshdSocketAddress(host, port);
+ channel = new TcpipClientChannel(TcpipClientChannel.Type.Direct, session, remote);
+ service.registerChannel(channel);
+ channel.open().addListener(new SshFutureListener<OpenFuture>() {
+ public void operationComplete(OpenFuture future) {
+ onChannelOpened(future);
+ }
+ });
+ } else {
+ super.onMessage(buffer);
+ }
+ }
+
+ protected void onChannelOpened(OpenFuture future) {
+ Buffer buffer = new Buffer(8);
+ buffer.putByte((byte) 0x00);
+ Throwable t = future.getException();
+ if (t != null) {
+ service.unregisterChannel(channel);
+ channel.close(false);
+ buffer.putByte((byte) 0x5b);
+ } else {
+ buffer.putByte((byte) 0x5a);
+ }
+ buffer.putByte((byte) 0x00);
+ buffer.putByte((byte) 0x00);
+ buffer.putByte((byte) 0x00);
+ buffer.putByte((byte) 0x00);
+ buffer.putByte((byte) 0x00);
+ buffer.putByte((byte) 0x00);
+ session.write(buffer);
+ }
+
+ private String getNTString(Buffer buffer) {
+ StringBuilder sb = new StringBuilder();
+ char c;
+ while ((c = (char) getUByte(buffer)) != 0) {
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ }
+
+ public class Socks5 extends Proxy {
+
+ byte[] authMethods;
+ Buffer response;
+
+ public Socks5(IoSession session) {
+ super(session);
+ }
+
+ @Override
+ protected void onMessage(Buffer buffer) throws IOException {
+ if (authMethods == null) {
+ int nbAuthMethods = getUByte(buffer);
+ authMethods = new byte[nbAuthMethods];
+ buffer.getRawBytes(authMethods);
+ boolean foundNoAuth = false;
+ for (int i = 0; i < nbAuthMethods; i++) {
+ foundNoAuth |= authMethods[i] == 0;
+ }
+ buffer = new Buffer(8);
+ buffer.putByte((byte) 0x05);
+ buffer.putByte((byte) (foundNoAuth ? 0x00 : 0xFF));
+ session.write(buffer);
+ if (!foundNoAuth) {
+ throw new IllegalStateException("Received socks5 greeting without NoAuth method");
+ } else {
+ log.debug("Received socks5 greeting");
+ }
+ } else if (channel == null) {
+ response = buffer;
+ int version = getUByte(buffer);
+ if (version != 0x05) {
+ throw new IllegalStateException("Unexpected version: " + version);
+ }
+ int cmd = buffer.getByte();
+ if (cmd != 1) {
+ throw new IllegalStateException("Unsupported socks command: " + cmd);
+ }
+ final int res = buffer.getByte();
+ int type = buffer.getByte();
+ String host;
+ if (type == 0x01) {
+ host = Integer.toString(getUByte(buffer)) + "."
+ + Integer.toString(getUByte(buffer)) + "."
+ + Integer.toString(getUByte(buffer)) + "."
+ + Integer.toString(getUByte(buffer));
+ } else if (type == 0x03) {
+ host = getBLString(buffer);
+ } else if (type == 0x04) {
+ host = Integer.toHexString(getUShort(buffer)) + ":"
+ + Integer.toHexString(getUShort(buffer)) + ":"
+ + Integer.toHexString(getUShort(buffer)) + ":"
+ + Integer.toHexString(getUShort(buffer)) + ":"
+ + Integer.toHexString(getUShort(buffer)) + ":"
+ + Integer.toHexString(getUShort(buffer)) + ":"
+ + Integer.toHexString(getUShort(buffer)) + ":"
+ + Integer.toHexString(getUShort(buffer));
+ } else {
+ throw new IllegalStateException("Unsupported address type: " + type);
+ }
+ int port = getUShort(buffer);
+ log.debug("Received socks5 connection request to {}:{}", host, port);
+ SshdSocketAddress remote = new SshdSocketAddress(host, port);
+ channel = new TcpipClientChannel(TcpipClientChannel.Type.Direct, session, remote);
+ service.registerChannel(channel);
+ channel.open().addListener(new SshFutureListener<OpenFuture>() {
+ public void operationComplete(OpenFuture future) {
+ onChannelOpened(future);
+ }
+ });
+ } else {
+ log.debug("Received socks5 connection message");
+ super.onMessage(buffer);
+ }
+ }
+
+ protected void onChannelOpened(OpenFuture future) {
+ int wpos = response.wpos();
+ response.rpos(0);
+ response.wpos(1);
+ Throwable t = future.getException();
+ if (t != null) {
+ service.unregisterChannel(channel);
+ channel.close(false);
+ response.putByte((byte) 0x01);
+ } else {
+ response.putByte((byte) 0x00);
+ }
+ response.wpos(wpos);
+ session.write(response);
+ }
+
+ private String getBLString(Buffer buffer) {
+ int length = getUByte(buffer);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ sb.append((char) getUByte(buffer));
+ }
+ return sb.toString();
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4b7a87f1/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java b/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
new file mode 100644
index 0000000..e8170a5
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd;
+
+import java.io.IOError;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.Socket;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.mina.core.buffer.IoBuffer;
+import org.apache.mina.core.service.IoAcceptor;
+import org.apache.mina.core.service.IoHandlerAdapter;
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
+import org.apache.sshd.common.SshdSocketAddress;
+import org.apache.sshd.util.BaseTest;
+import org.apache.sshd.util.BogusForwardingFilter;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.sshd.util.Utils.getFreePort;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Port forwarding tests
+ */
+public class ProxyTest extends BaseTest {
+
+ private final org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
+
+ private SshServer sshd;
+ private int sshPort;
+ private int echoPort;
+ private IoAcceptor acceptor;
+ private SshClient client;
+
+ @Before
+ public void setUp() throws Exception {
+ sshPort = getFreePort();
+ echoPort = getFreePort();
+
+ sshd = SshServer.setUpDefaultServer();
+ sshd.getProperties().put(SshServer.WINDOW_SIZE, "2048");
+ sshd.getProperties().put(SshServer.MAX_PACKET_SIZE, "256");
+ sshd.setPort(sshPort);
+ sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+ sshd.setShellFactory(new EchoShellFactory());
+ sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+ sshd.setTcpipForwardingFilter(new BogusForwardingFilter());
+ sshd.start();
+
+ NioSocketAcceptor acceptor = new NioSocketAcceptor();
+ acceptor.setHandler(new IoHandlerAdapter() {
+ @Override
+ public void messageReceived(IoSession session, Object message) throws Exception {
+ IoBuffer recv = (IoBuffer) message;
+ IoBuffer sent = IoBuffer.allocate(recv.remaining());
+ sent.put(recv);
+ sent.flip();
+ session.write(sent);
+ }
+ });
+ acceptor.setReuseAddress(true);
+ acceptor.bind(new InetSocketAddress(echoPort));
+ this.acceptor = acceptor;
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (sshd != null) {
+ sshd.stop(true);
+ }
+ if (acceptor != null) {
+ acceptor.dispose(true);
+ }
+ if (client != null) {
+ client.stop();
+ }
+ }
+
+ @Test
+ public void testSocksProxy() throws Exception {
+ ClientSession session = createNativeSession();
+
+ SshdSocketAddress dynamic = session.startDynamicPortForwarding(new SshdSocketAddress("localhost", 0));
+
+ for (int i = 0; i < 10; i++) {
+ Socket s = new Socket(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("localhost", dynamic.getPort())));
+ s.connect(new InetSocketAddress("localhost", echoPort));
+ s.getOutputStream().write("foo".getBytes());
+ s.getOutputStream().flush();
+ byte[] buf = new byte[1024];
+ int l = s.getInputStream().read(buf);
+ s.close();
+ assertEquals("foo", new String(buf, 0, l));
+ }
+
+ session.stopDynamicPortForwarding(dynamic);
+
+ try {
+ Socket s = new Socket(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("localhost", dynamic.getPort())));
+ s.connect(new InetSocketAddress("localhost", echoPort));
+ s.getOutputStream().write("foo".getBytes());
+ fail("Expected IOException");
+ } catch (IOException e) {
+ // expected
+ }
+
+ session.close(false).await();
+ }
+
+ protected ClientSession createNativeSession() throws Exception {
+ client = SshClient.setUpDefaultClient();
+ client.getProperties().put(SshServer.WINDOW_SIZE, "2048");
+ client.getProperties().put(SshServer.MAX_PACKET_SIZE, "256");
+ client.setTcpipForwardingFilter(new BogusForwardingFilter());
+ client.start();
+
+ ClientSession session = client.connect("sshd", "localhost", sshPort).await().getSession();
+ session.addPasswordIdentity("sshd");
+ session.auth().verify();
+ return session;
+ }
+
+
+}
+
+