You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by sp...@apache.org on 2009/11/17 04:26:30 UTC

svn commit: r881138 - in /mina/sshd/trunk/sshd-core/src: main/java/org/apache/sshd/ main/java/org/apache/sshd/server/ main/java/org/apache/sshd/server/channel/ main/java/org/apache/sshd/server/session/ test/java/org/apache/sshd/ test/java/org/apache/ss...

Author: spearce
Date: Tue Nov 17 03:26:29 2009
New Revision: 881138

URL: http://svn.apache.org/viewvc?rev=881138&view=rev
Log:
SSHD-60: Filter TCP/IP port forwarding requests and fix stream close

Aside from filtering the port forwarding requests we also now notify
a client when the peer it has forwarded a port to has disconnected.

Added:
    mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/TcpIpForwardFilter.java
    mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/OpenChannelException.java
    mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/util/BogusTcpIpForwardFilter.java
Modified:
    mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/SshServer.java
    mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
    mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelDirectTcpip.java
    mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
    mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/TcpipForwardSupport.java
    mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java

Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/SshServer.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/SshServer.java?rev=881138&r1=881137&r2=881138&view=diff
==============================================================================
--- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/SshServer.java (original)
+++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/SshServer.java Tue Nov 17 03:26:29 2009
@@ -103,6 +103,7 @@
     protected List<NamedFactory<Command>> subsystemFactories;
     protected PasswordAuthenticator passwordAuthenticator;
     protected PublickeyAuthenticator publickeyAuthenticator;
+    protected TcpIpForwardFilter tcpIpForwardFilter;
 
     public SshServer() {
     }
@@ -200,6 +201,14 @@
         this.publickeyAuthenticator = publickeyAuthenticator;
     }
 
+    public TcpIpForwardFilter getTcpIpForwardFilter() {
+        return tcpIpForwardFilter;
+    }
+
+    public void setTcpIpForwardFilter(TcpIpForwardFilter tcpIpForwardFilter) {
+        this.tcpIpForwardFilter = tcpIpForwardFilter;
+    }
+
     protected void checkConfig() {
         if (getPort() < 0) {
             throw new IllegalArgumentException("Bad port number: " + port);
@@ -401,6 +410,15 @@
                 return username != null && username.equals(password);
             }
         });
+        sshd.setTcpIpForwardFilter(new TcpIpForwardFilter() {
+            public boolean canListen(InetSocketAddress address, ServerSession session) {
+                return true;
+            }
+
+            public boolean canConnect(InetSocketAddress address, ServerSession session) {
+                return true;
+            }
+        });
         sshd.start();
     }
 

Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java?rev=881138&r1=881137&r2=881138&view=diff
==============================================================================
--- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java (original)
+++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java Tue Nov 17 03:26:29 2009
@@ -60,6 +60,15 @@
     PasswordAuthenticator getPasswordAuthenticator();
 
     /**
+     * Retrieve the <code>TcpIpForwardFilter</code> to be used by the SSH server.
+     * If no filter has been configured (i.e. this method returns
+     * <code>null</code>), then all forwarding requests will be rejected.
+     *
+     * @return the <code>TcpIpForwardFilter</code> or <code>null</code>
+     */
+    TcpIpForwardFilter getTcpIpForwardFilter();
+
+    /**
      * Retrieve the <code>ShellFactory</code> object to be used to create shells.
      *
      * @return a valid <code>ShellFactory</code> object or <code>null</code> if shells

Added: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/TcpIpForwardFilter.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/TcpIpForwardFilter.java?rev=881138&view=auto
==============================================================================
--- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/TcpIpForwardFilter.java (added)
+++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/TcpIpForwardFilter.java Tue Nov 17 03:26:29 2009
@@ -0,0 +1,61 @@
+/*
+ * 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.server;
+
+import org.apache.sshd.server.session.ServerSession;
+
+import java.net.InetSocketAddress;
+
+/**
+ * Determines if a TCP/IP forwarding will be permitted.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface TcpIpForwardFilter {
+    /**
+     * Determine if the session may listen for inbound connections.
+     * <p>
+     * This server process will open a new listen socket on the address given
+     * by the client (usually 127.0.0.1 but may be any address).  Any inbound
+     * connections to this socket will be tunneled over the session to the
+     * client, which the client will then forward the connection to another
+     * host on the client's side of the network.
+     *
+     * @param address address the client has requested this server listen
+     *  for inbound connections on, and relay them through the client.
+     * @param session session requesting permission to listen for connections.
+     * @return true if the socket is permitted; false if it must be denied.
+     */
+    boolean canListen(InetSocketAddress address, ServerSession session);
+
+    /**
+     * Determine if the session may create an outbound connection.
+     * <p>
+     * This server process will connect to another server listening on the
+     * address specified by the client.  Usually this is to another port on
+     * the same host (127.0.0.1) but may be to any other system this server
+     * can reach on the server's side of the network.
+     *
+     * @param address address the client has requested this server listen
+     *  for inbound connections on, and relay them through the client.
+     * @param session session requesting permission to listen for connections.
+     * @return true if the socket is permitted; false if it must be denied.
+     */
+    boolean canConnect(InetSocketAddress address, ServerSession session);
+}

Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelDirectTcpip.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelDirectTcpip.java?rev=881138&r1=881137&r2=881138&view=diff
==============================================================================
--- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelDirectTcpip.java (original)
+++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelDirectTcpip.java Tue Nov 17 03:26:29 2009
@@ -21,6 +21,7 @@
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.io.OutputStream;
+import java.net.ConnectException;
 import java.net.InetSocketAddress;
 
 import org.apache.mina.core.buffer.IoBuffer;
@@ -41,6 +42,8 @@
 import org.apache.sshd.common.future.SshFuture;
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.util.Buffer;
+import org.apache.sshd.server.TcpIpForwardFilter;
+import org.apache.sshd.server.session.ServerSession;
 
 /**
  * TODO Add javadoc
@@ -71,6 +74,16 @@
         final OpenFuture f = new DefaultOpenFuture(this);
         String hostToConnect = buffer.getString();
         int portToConnect = buffer.getInt();
+        InetSocketAddress address = new InetSocketAddress(hostToConnect, portToConnect);
+
+        final ServerSession serverSession = (ServerSession)getSession();
+        final TcpIpForwardFilter filter = serverSession.getServerFactoryManager().getTcpIpForwardFilter();
+        if (filter == null || !filter.canConnect(address, serverSession)) {
+            super.close(true);
+            f.setException(new OpenChannelException(SshConstants.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "connect denied"));
+            return f;
+        }
+
         String originatorIpAddress = buffer.getString();
         int originatorPort = buffer.getInt();
         log.info("Receiving request for direct tcpip: hostToConnect={}, portToConnect={}, originatorIpAddress={}, originatorPort={}",
@@ -87,27 +100,61 @@
                 out.write(b, 0, r);
                 out.flush();
             }
+
+            @Override
+            public void sessionClosed(IoSession session) throws Exception {
+                sendEof();
+            }
         };
         connector.setHandler(handler);
-        ConnectFuture future = connector.connect(new InetSocketAddress(hostToConnect, portToConnect));
+        ConnectFuture future = connector.connect(address);
         future.addListener(new IoFutureListener<ConnectFuture>() {
             public void operationComplete(ConnectFuture future) {
                 if (future.isConnected()) {
                     ioSession = future.getSession();
                     f.setOpened();
                 } else if (future.getException() != null) {
-                    close(false);
-                    f.setException(future.getException());
+                    closeImmediately0();
+                    if (future.getException() instanceof ConnectException) {
+                        f.setException(new OpenChannelException(
+                            SshConstants.SSH_OPEN_CONNECT_FAILED,
+                            future.getException().getMessage(),
+                            future.getException()));
+                    } else {
+                        f.setException(future.getException());
+                    }
                 }
             }
         });
         return f;
     }
 
+    private void closeImmediately0() {
+        // We need to close the channel immediately to remove it from the
+        // server session's channel table and *not* send a packet to the
+        // client.  A notification was already sent by our caller, or will
+        // be sent after we return.
+        //
+        super.close(true);
+
+        // We also need to dispose of the connector, but unfortunately we
+        // are being invoked by the connector thread or the connector's
+        // own processor thread.  Disposing of the connector within either
+        // causes deadlock.  Instead create a new thread to dispose of the
+        // connector in the background.
+        //
+        new Thread("ChannelDirectTcpip-ConnectorCleanup") {
+            @Override
+            public void run() {
+                connector.dispose();
+            }
+        }.start();
+    }
+
     public CloseFuture close(boolean immediately) {
         return super.close(immediately).addListener(new SshFutureListener() {
             public void operationComplete(SshFuture sshFuture) {
-                connector.dispose();
+                closeImmediately0();
             }
         });
     }

Added: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/OpenChannelException.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/OpenChannelException.java?rev=881138&view=auto
==============================================================================
--- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/OpenChannelException.java (added)
+++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/OpenChannelException.java Tue Nov 17 03:26:29 2009
@@ -0,0 +1,54 @@
+/*
+ * 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.server.channel;
+
+import org.apache.sshd.common.SshConstants;
+
+/**
+ * Documents failure of a channel to open as expected.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenChannelException extends Exception {
+    private final int code;
+
+    public OpenChannelException(int code, String message) {
+        this(code, message, null);
+    }
+
+    public OpenChannelException(int code, String message, Throwable cause) {
+        super(message, cause);
+        this.code = code;
+    }
+
+    /**
+     * The reason code as specified by RFC 4254.
+     * <ul>
+     * <li>{@link SshConstants#SSH_OPEN_ADMINISTRATIVELY_PROHIBITED}
+     * <li>{@link SshConstants#SSH_OPEN_CONNECT_FAILED}
+     * <li>{@link SshConstants#SSH_OPEN_UNKNOWN_CHANNEL_TYPE}
+     * <li>{@link SshConstants#SSH_OPEN_RESOURCE_SHORTAGE}
+     * </ul>
+     *
+     * @return reason code; 0 if no standardized reason code is given.
+     */
+    public int getReasonCode() {
+        return code;
+    }
+}

Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java?rev=881138&r1=881137&r2=881138&view=diff
==============================================================================
--- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java (original)
+++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java Tue Nov 17 03:26:29 2009
@@ -39,6 +39,7 @@
 import org.apache.sshd.common.util.Buffer;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.UserAuth;
+import org.apache.sshd.server.channel.OpenChannelException;;
 
 /**
  *
@@ -411,8 +412,13 @@
                     } else if (future.getException() != null) {
                         Buffer buf = createBuffer(SshConstants.Message.SSH_MSG_CHANNEL_OPEN_FAILURE);
                         buf.putInt(id);
-                        buf.putInt(0);
-                        buf.putString("Error opening channel: " + future.getException().getMessage());
+                        if (future.getException() instanceof OpenChannelException) {
+                            buf.putInt(((OpenChannelException)future.getException()).getReasonCode());
+                            buf.putString(future.getException().getMessage());
+                        } else {
+                            buf.putInt(0);
+                            buf.putString("Error opening channel: " + future.getException().getMessage());
+                        }
                         buf.putString("");
                         writePacket(buf);
                     }

Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/TcpipForwardSupport.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/TcpipForwardSupport.java?rev=881138&r1=881137&r2=881138&view=diff
==============================================================================
--- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/TcpipForwardSupport.java (original)
+++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/TcpipForwardSupport.java Tue Nov 17 03:26:29 2009
@@ -34,21 +34,21 @@
 import org.apache.sshd.client.channel.AbstractClientChannel;
 import org.apache.sshd.client.future.DefaultOpenFuture;
 import org.apache.sshd.client.future.OpenFuture;
-import org.apache.sshd.common.Session;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.channel.ChannelOutputStream;
 import org.apache.sshd.common.util.Buffer;
+import org.apache.sshd.server.TcpIpForwardFilter;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class TcpipForwardSupport extends IoHandlerAdapter {
 
-    private final Session session;
+    private final ServerSession session;
     private IoAcceptor acceptor;
 
-    public TcpipForwardSupport(Session session) {
+    public TcpipForwardSupport(ServerSession session) {
         this.session = session;
     }
 
@@ -65,15 +65,38 @@
     public synchronized void close() {
         if (acceptor != null) {
             acceptor.dispose();
+            acceptor = null;
         }
     }
 
     synchronized void request(Buffer buffer, boolean wantReply) throws IOException {
-        initialize();
         String address = buffer.getString();
         int port = buffer.getInt();
+        InetSocketAddress addr = new InetSocketAddress(address, port);
+
+        final TcpIpForwardFilter filter = session.getServerFactoryManager().getTcpIpForwardFilter();
+        if (filter == null || !filter.canListen(addr, session)) {
+            if (wantReply) {
+                buffer = session.createBuffer(SshConstants.Message.SSH_MSG_REQUEST_FAILURE);
+                session.writePacket(buffer);
+            }
+            return;
+        }
+
+        initialize();
         Set<SocketAddress> a1 = acceptor.getLocalAddresses();
-        acceptor.bind(new InetSocketAddress(address, port));
+        try {
+            acceptor.bind(addr);
+        } catch (IOException bindErr) {
+            if (acceptor.getLocalAddresses().isEmpty()) {
+                close();
+            }
+            if (wantReply) {
+                buffer = session.createBuffer(SshConstants.Message.SSH_MSG_REQUEST_FAILURE);
+                session.writePacket(buffer);
+            }
+            return;
+        }
         Set<SocketAddress> a2 = acceptor.getLocalAddresses();
         a2.removeAll(a1);
         if (a2.size() == 1) {
@@ -92,7 +115,9 @@
     synchronized void cancel(Buffer buffer, boolean wantReply) throws IOException {
         String address = buffer.getString();
         int port = buffer.getInt();
-        acceptor.unbind(new InetSocketAddress(address, port));
+        if (acceptor != null) {
+            acceptor.unbind(new InetSocketAddress(address, port));
+        }
         if (wantReply) {
             buffer = session.createBuffer(SshConstants.Message.SSH_MSG_REQUEST_SUCCESS);
             session.writePacket(buffer);
@@ -185,6 +210,11 @@
             serverSession.write(buf);
         }
 
+        @Override
+        public void handleEof() throws IOException {
+            super.handleEof();
+            serverSession.close(false);
+        }
     }
 
 

Modified: mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java?rev=881138&r1=881137&r2=881138&view=diff
==============================================================================
--- mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java (original)
+++ mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java Tue Nov 17 03:26:29 2009
@@ -37,6 +37,7 @@
 import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
 import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
 import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.BogusTcpIpForwardFilter;
 import org.apache.sshd.util.EchoShellFactory;
 import org.junit.After;
 import org.junit.Before;
@@ -75,6 +76,7 @@
         sshd.setKeyPairProvider(new FileKeyPairProvider(new String[] { "src/test/resources/hostkey.pem" }));
         sshd.setShellFactory(new EchoShellFactory());
         sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.setTcpIpForwardFilter(new BogusTcpIpForwardFilter());
         sshd.start();
 
         NioSocketAcceptor acceptor = new NioSocketAcceptor();

Added: mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/util/BogusTcpIpForwardFilter.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/util/BogusTcpIpForwardFilter.java?rev=881138&view=auto
==============================================================================
--- mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/util/BogusTcpIpForwardFilter.java (added)
+++ mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/util/BogusTcpIpForwardFilter.java Tue Nov 17 03:26:29 2009
@@ -0,0 +1,39 @@
+/*
+ * 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.util;
+
+import org.apache.sshd.server.TcpIpForwardFilter;
+import org.apache.sshd.server.session.ServerSession;
+
+import java.net.InetSocketAddress;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class BogusTcpIpForwardFilter implements TcpIpForwardFilter {
+    public boolean canConnect(InetSocketAddress address, ServerSession session) {
+        return true;
+    }
+
+    public boolean canListen(InetSocketAddress address, ServerSession session) {
+        return true;
+    }
+}