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 2018/04/19 06:47:12 UTC

[1/2] mina-sshd git commit: [SSHD-821] Support for async keyboard authentication

Repository: mina-sshd
Updated Branches:
  refs/heads/master 5fc90bd04 -> 598c991fe


[SSHD-821] Support for async keyboard authentication


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/5c1c8a98
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/5c1c8a98
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/5c1c8a98

Branch: refs/heads/master
Commit: 5c1c8a9830ad5b622b15055bfc7205aa2fd53e98
Parents: 5fc90bd
Author: Guillaume Nodet <gn...@apache.org>
Authored: Wed Apr 18 14:17:28 2018 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Thu Apr 19 08:42:02 2018 +0200

----------------------------------------------------------------------
 .../sshd/server/auth/AsyncAuthException.java    |  94 ++++++++++++
 .../org/apache/sshd/server/auth/UserAuth.java   |   6 +-
 .../auth/password/PasswordAuthenticator.java    |   5 +-
 .../auth/pubkey/PublickeyAuthenticator.java     |   4 +-
 .../server/session/ServerUserAuthService.java   |  21 ++-
 .../server/auth/AsyncAuthInteractiveTest.java   | 106 +++++++++++++
 .../apache/sshd/server/auth/AsyncAuthTest.java  | 103 +++++++++++++
 .../sshd/server/auth/AsyncAuthTestBase.java     | 147 +++++++++++++++++++
 sshd-mina/pom.xml                               |   2 +
 9 files changed, 483 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/auth/AsyncAuthException.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/AsyncAuthException.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/AsyncAuthException.java
new file mode 100644
index 0000000..0a95986
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/AsyncAuthException.java
@@ -0,0 +1,94 @@
+/*
+ * 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.auth;
+
+import java.lang.reflect.Array;
+import java.util.function.Consumer;
+
+import org.apache.sshd.common.RuntimeSshException;
+
+/**
+ *
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class AsyncAuthException extends RuntimeSshException {
+
+    private static final long serialVersionUID = 6741236101797649869L;
+
+    protected Object listener;
+    protected Boolean authed;
+
+    public AsyncAuthException() {
+        super();
+    }
+
+    public void setAuthed(boolean authed) {
+        Object listener;
+        synchronized (this) {
+            if (this.authed != null) {
+                return;
+            }
+            this.authed = authed;
+            listener = this.listener;
+        }
+        if (listener != null) {
+            if (listener instanceof Consumer) {
+                asListener(listener).accept(authed);
+            } else {
+                int l = Array.getLength(listener);
+                for (int i = 0; i < l; i++) {
+                    Consumer<Boolean> lst = asListener(Array.get(listener, i));
+                    if (lst != null) {
+                        lst.accept(authed);
+                    }
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected static Consumer<Boolean> asListener(Object listener) {
+        return (Consumer<Boolean>) listener;
+    }
+
+    public void addListener(Consumer<Boolean> listener) {
+        Boolean result;
+        synchronized (this) {
+            if (this.listener == null) {
+                this.listener = listener;
+            } else if (this.listener instanceof Consumer) {
+                this.listener = new Object[] {this.listener, listener };
+            } else {
+                Object[] ol = (Object[]) this.listener;
+                int l = ol.length;
+                Object[] nl = new Object[l + 1];
+                System.arraycopy(ol, 0, nl, 0, l);
+                nl[l] = listener;
+                this.listener = nl;
+            }
+            result = this.authed;
+        }
+        if (result != null) {
+            listener.accept(result);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
index 91b919c..d26da38 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
@@ -42,9 +42,10 @@ public interface UserAuth extends ServerSessionHolder, UserAuthInstance<ServerSe
      * @param buffer   the request buffer containing parameters specific to this request
      * @return <code>true</code> if the authentication succeeded, <code>false</code> if the authentication
      * failed and {@code null} if not finished yet
+     * @throws AsyncAuthException if the service is willing to perform an asynchronous authentication
      * @throws Exception if the authentication fails
      */
-    Boolean auth(ServerSession session, String username, String service, Buffer buffer) throws Exception;
+    Boolean auth(ServerSession session, String username, String service, Buffer buffer) throws AsyncAuthException, Exception;
 
     /**
      * Handle another step in the authentication process.
@@ -52,9 +53,10 @@ public interface UserAuth extends ServerSessionHolder, UserAuthInstance<ServerSe
      * @param buffer the request buffer containing parameters specific to this request
      * @return <code>true</code> if the authentication succeeded, <code>false</code> if the authentication
      * failed and {@code null} if not finished yet
+     * @throws AsyncAuthException if the service is willing to perform an asynchronous authentication
      * @throws Exception if the authentication fails
      */
-    Boolean next(Buffer buffer) throws Exception;
+    Boolean next(Buffer buffer) throws AsyncAuthException, Exception;
 
     /**
      * Free any system resources used by the module.

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java
index 902c128..fcc91e1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordAuthenticator.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sshd.server.auth.password;
 
+import org.apache.sshd.server.auth.AsyncAuthException;
 import org.apache.sshd.server.session.ServerSession;
 
 /**
@@ -36,6 +37,8 @@ public interface PasswordAuthenticator {
      * @return {@code true} indicating if authentication succeeded
      * @throws PasswordChangeRequiredException If the password is expired or
      * not strong enough to suit the server's policy
+     * @throws AsyncAuthException If the authentication is performed asynchronously
      */
-    boolean authenticate(String username, String password, ServerSession session) throws PasswordChangeRequiredException;
+    boolean authenticate(String username, String password, ServerSession session)
+            throws PasswordChangeRequiredException, AsyncAuthException;
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
index 5146ea7..2d7a908 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
@@ -20,6 +20,7 @@ package org.apache.sshd.server.auth.pubkey;
 
 import java.security.PublicKey;
 
+import org.apache.sshd.server.auth.AsyncAuthException;
 import org.apache.sshd.server.session.ServerSession;
 
 /**
@@ -38,6 +39,7 @@ public interface PublickeyAuthenticator {
      * @param key      the key
      * @param session  the server session
      * @return a boolean indicating if authentication succeeded or not
+     * @throws AsyncAuthException If the authentication is performed asynchronously
      */
-    boolean authenticate(String username, PublicKey key, ServerSession session);
+    boolean authenticate(String username, PublicKey key, ServerSession session) throws AsyncAuthException;
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
index 28ea4d4..2c72b6d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
@@ -55,6 +55,7 @@ import org.apache.sshd.common.util.closeable.AbstractCloseable;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.ServerAuthenticationManager;
 import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.auth.AsyncAuthException;
 import org.apache.sshd.server.auth.UserAuth;
 import org.apache.sshd.server.auth.UserAuthNoneFactory;
 import org.apache.sshd.server.auth.WelcomeBannerPhase;
@@ -143,7 +144,7 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
     }
 
     @Override
-    public void process(int cmd, Buffer buffer) throws Exception {
+    public synchronized void process(int cmd, Buffer buffer) throws Exception {
         Boolean authed = Boolean.FALSE;
         ServerSession session = getServerSession();
         boolean debugEnabled = log.isDebugEnabled();
@@ -196,6 +197,9 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
                 currentAuth = ValidateUtils.checkNotNull(factory.create(), "No authenticator created for method=%s", method);
                 try {
                     authed = currentAuth.auth(session, username, service, buffer);
+                } catch (AsyncAuthException async) {
+                    async.addListener(authenticated -> asyncAuth(cmd, buffer, authenticated));
+                    return;
                 } catch (Exception e) {
                     if (debugEnabled) {
                         log.debug("process({}) Failed ({}) to authenticate using factory method={}: {}",
@@ -228,6 +232,9 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
             buffer.rpos(buffer.rpos() - 1);
             try {
                 authed = currentAuth.next(buffer);
+            } catch (AsyncAuthException async) {
+                async.addListener(authenticated -> asyncAuth(cmd, buffer, authenticated));
+                return;
             } catch (Exception e) {
                 // Continue
                 if (debugEnabled) {
@@ -249,6 +256,18 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
         }
     }
 
+    protected synchronized void asyncAuth(int cmd, Buffer buffer, boolean authed) {
+        try {
+            if (authed) {
+                handleAuthenticationSuccess(cmd, buffer);
+            } else {
+                handleAuthenticationFailure(cmd, buffer);
+            }
+        } catch (Exception e) {
+            log.warn("Error performing async authentication: {}", e.getMessage(), e);
+        }
+    }
+
     protected void handleAuthenticationInProgress(int cmd, Buffer buffer) throws Exception {
         String username = (currentAuth == null) ? null : currentAuth.getUsername();
         if (log.isDebugEnabled()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthInteractiveTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthInteractiveTest.java b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthInteractiveTest.java
new file mode 100644
index 0000000..42763ef
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthInteractiveTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.auth;
+
+import com.jcraft.jsch.ChannelShell;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.UserInfo;
+
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AsyncAuthInteractiveTest extends AsyncAuthTestBase {
+
+    public AsyncAuthInteractiveTest() {
+        super();
+    }
+
+    protected boolean authenticate() throws Exception {
+
+        JSch jsch = new JSch();
+        Session session;
+        ChannelShell channel;
+
+        session = jsch.getSession("whatever", "localhost", port);
+        session.setUserInfo(new UserInfo() {
+            @Override
+            public String getPassphrase() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public String getPassword() {
+                return "whocares";
+            }
+
+            @Override
+            public boolean promptPassword(String s) {
+                return true;
+            }
+
+            @Override
+            public boolean promptPassphrase(String s) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public boolean promptYesNo(String s) {
+                return true;
+            }
+
+            @Override
+            public void showMessage(String s) {
+                // Do nothing
+            }
+        });
+        try {
+            session.connect();
+        } catch (JSchException e) {
+            switch (e.getMessage()) {
+                case "Auth cancel":
+                case "Auth fail":
+                    return false;
+                default:
+                    throw e;
+            }
+        }
+        channel = (ChannelShell) session.openChannel("shell");
+        channel.connect();
+
+        try {
+            channel.disconnect();
+        } catch (Exception ignore) {
+            // ignore
+        }
+
+        try {
+            session.disconnect();
+        } catch (Exception ignore) {
+            // ignore
+        }
+
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTest.java b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTest.java
new file mode 100644
index 0000000..5f8f591
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.auth;
+
+import com.jcraft.jsch.ChannelShell;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.UserInfo;
+
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AsyncAuthTest extends AsyncAuthTestBase {
+
+    public AsyncAuthTest() {
+        super();
+    }
+
+    protected boolean authenticate() throws Exception {
+
+        JSch jsch = new JSch();
+        Session session;
+        ChannelShell channel;
+
+        session = jsch.getSession("whatever", "localhost", port);
+        session.setPassword("whocares");
+        session.setUserInfo(new UserInfo() {
+            @Override
+            public String getPassphrase() {
+                return null;
+            }
+
+            @Override
+            public String getPassword() {
+                return null;
+            }
+
+            @Override
+            public boolean promptPassword(String s) {
+                return false;
+            }
+
+            @Override
+            public boolean promptPassphrase(String s) {
+                return false;
+            }
+
+            @Override
+            public boolean promptYesNo(String s) {
+                return true;
+            } // Accept all server keys
+
+            @Override
+            public void showMessage(String s) {
+                // Do nothing
+            }
+        });
+        try {
+            session.connect();
+        } catch (JSchException e) {
+            if (e.getMessage().equals("Auth cancel")) {
+                return false;
+            } else {
+                throw e;
+            }
+        }
+        channel = (ChannelShell) session.openChannel("shell");
+        channel.connect();
+
+        try {
+            channel.disconnect();
+        } catch (Exception ignore) {
+        }
+
+        try {
+            session.disconnect();
+        } catch (Exception ignore) {
+        }
+
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTestBase.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTestBase.java b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTestBase.java
new file mode 100644
index 0000000..4328117
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/server/auth/AsyncAuthTestBase.java
@@ -0,0 +1,147 @@
+/*
+ * 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.auth;
+
+import java.io.File;
+
+import com.jcraft.jsch.JSchException;
+
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.EchoShellFactory;
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AsyncAuthTestBase extends BaseTestSupport {
+
+    SshServer server;
+    int port;
+
+    private PasswordAuthenticator authenticator;
+
+    public AsyncAuthTestBase() {
+        super();
+    }
+
+    public void startServer() throws Exception {
+        startServer(null);
+    }
+
+    public void startServer(Integer timeout) throws Exception {
+        if (server != null) {
+            fail("Server already started");
+        }
+        server = SshServer.setUpDefaultServer();
+        if (timeout != null) {
+            server.getProperties().put(FactoryManager.AUTH_TIMEOUT, timeout.toString());
+        }
+        server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
+        server.setPasswordAuthenticator((username, password, session) -> authenticator.authenticate(username, password, session));
+        server.setShellFactory(new EchoShellFactory());
+        server.start();
+        port = server.getPort();
+    }
+
+    @After
+    public void stopServer() throws Exception {
+        if (server != null) {
+            server.stop();
+        }
+        server = null;
+    }
+
+    @Test
+    public void testSyncAuthFailed() throws Exception {
+        startServer();
+        authenticator = (username, x, sess) -> false;
+        assertFalse(authenticate());
+    }
+
+    @Test
+    public void testSyncAuthSucceeded() throws Exception {
+        startServer();
+        authenticator = (username, x, sess) -> true;
+        assertTrue(authenticate());
+    }
+
+    @Test
+    public void testAsyncAuthFailed() throws Exception {
+        startServer();
+        authenticator = (username, x, sess) -> async(200, false);
+        assertFalse(authenticate());
+    }
+
+    @Test
+    public void testAsyncAuthSucceeded() throws Exception {
+        startServer();
+        authenticator = (username, x, sess) -> async(200, true);
+        assertTrue(authenticate());
+    }
+
+    @Test
+    public void testAsyncAuthTimeout() throws Exception {
+        startServer(500);
+        authenticator = (username, x, sess) -> asyncTimeout();
+        try {
+            authenticate();
+        } catch (JSchException e) {
+            assertTrue("Unexpected failure " + e.getMessage(), e.getMessage().startsWith("SSH_MSG_DISCONNECT"));
+        }
+    }
+
+    @Test
+    public void testAsyncAuthSucceededAfterTimeout() throws Exception {
+        startServer(500);
+        authenticator = (username, x, sess) -> async(1000, true);
+        try {
+            authenticate();
+        } catch (JSchException e) {
+            assertTrue("Unexpected failure " + e.getMessage(), e.getMessage().startsWith("SSH_MSG_DISCONNECT"));
+        }
+    }
+
+    private boolean asyncTimeout() {
+        throw new AsyncAuthException();
+    }
+
+    private boolean async(int delay, boolean result) {
+        AsyncAuthException auth = new AsyncAuthException();
+        new Thread(() -> doAsync(delay, result, auth)).start();
+        throw auth;
+    }
+
+    private void doAsync(int delay, boolean result, AsyncAuthException auth) {
+        try {
+            Thread.sleep(delay);
+        } catch (InterruptedException ignore) {
+            // ignore
+        } finally {
+            auth.setAuthed(result);
+        }
+    }
+
+    protected abstract boolean authenticate() throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/5c1c8a98/sshd-mina/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-mina/pom.xml b/sshd-mina/pom.xml
index 1000f8a..bb62820 100644
--- a/sshd-mina/pom.xml
+++ b/sshd-mina/pom.xml
@@ -180,6 +180,8 @@
                         <exclude>**/MacTest.java</exclude>
                         <exclude>**/SpringConfigTest.java</exclude>
                         <exclude>**/ConcurrentConnectionTest.java</exclude>
+                        <exclude>**/AsyncAuthTest.java</exclude>
+                        <exclude>**/AsyncAuthInteractiveTest.java</exclude>
                     </excludes>
                         <!-- No need to re-run core tests that do not involve session creation -->
                     <excludedGroups>org.apache.sshd.util.test.NoIoTestCase</excludedGroups>


[2/2] mina-sshd git commit: [SSHD-817] Netty nio provider

Posted by gn...@apache.org.
[SSHD-817] Netty nio provider


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/598c991f
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/598c991f
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/598c991f

Branch: refs/heads/master
Commit: 598c991fe4cc609c43f972fe025775fcf734933b
Parents: 5c1c8a9
Author: Guillaume Nodet <gn...@apache.org>
Authored: Tue Apr 17 22:16:55 2018 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Thu Apr 19 08:43:30 2018 +0200

----------------------------------------------------------------------
 pom.xml                                         |   1 +
 sshd-netty/pom.xml                              | 214 +++++++++++++++++++
 .../org/apache/sshd/netty/NettyIoAcceptor.java  | 174 +++++++++++++++
 .../org/apache/sshd/netty/NettyIoConnector.java | 125 +++++++++++
 .../org/apache/sshd/netty/NettyIoService.java   |  55 +++++
 .../sshd/netty/NettyIoServiceFactory.java       |  75 +++++++
 .../netty/NettyIoServiceFactoryFactory.java     |  49 +++++
 .../org/apache/sshd/netty/NettyIoSession.java   | 211 ++++++++++++++++++
 .../org/apache/sshd/netty/NettySupport.java     |  45 ++++
 ...pache.sshd.common.io.IoServiceFactoryFactory |  20 ++
 10 files changed, 969 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index e3f9353..1d75f92 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1007,6 +1007,7 @@
         <module>sshd-core</module>
         <module>sshd-sftp</module>
         <module>sshd-mina</module>
+        <module>sshd-netty</module>
         <module>sshd-ldap</module>
         <module>sshd-git</module>
         <module>sshd-contrib</module>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-netty/pom.xml b/sshd-netty/pom.xml
new file mode 100644
index 0000000..fdb0f3e
--- /dev/null
+++ b/sshd-netty/pom.xml
@@ -0,0 +1,214 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+
+    <!--
+
+        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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.sshd</groupId>
+        <artifactId>sshd</artifactId>
+        <version>1.7.1-SNAPSHOT</version>
+        <relativePath>..</relativePath>
+    </parent>
+
+    <artifactId>sshd-netty</artifactId>
+    <name>Apache Mina SSHD :: Netty</name>
+    <packaging>jar</packaging>
+    <inceptionYear>2008</inceptionYear>
+
+    <properties>
+        <projectRoot>${project.basedir}/..</projectRoot>
+        <netty.version>4.1.1.Final</netty.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport</artifactId>
+            <version>${netty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-handler</artifactId>
+            <version>${netty.version}</version>
+        </dependency>
+
+        <!-- test dependencies -->
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-core</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-sftp</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.i2p.crypto</groupId>
+            <artifactId>eddsa</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jzlib</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-httpclient</groupId>
+            <artifactId>commons-httpclient</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.ethz.ganymed</groupId>
+            <artifactId>ganymed-ssh2</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.servicemix.bundles</groupId>
+            <artifactId>org.apache.servicemix.bundles.not-yet-commons-ssl</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <testSourceDirectory>${build.directory}/test-sources</testSourceDirectory>
+        <testResources>
+            <testResource>
+                <directory>${build.directory}/test-resources</directory>
+            </testResource>
+        </testResources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-test-resources</id>
+                        <phase>generate-test-resources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${build.directory}/test-resources</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${projectRoot}/sshd-core/src/test/resources</directory>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-test-sources</id>
+                        <phase>generate-test-sources</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${build.directory}/test-sources</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>${projectRoot}/sshd-core/src/test/java</directory>
+                                    <excludes>
+                                        <exclude>**/ProxyTest.java</exclude>
+                                        <exclude>**/PortForwardingTest.java</exclude>
+                                        <exclude>**/PortForwardingLoadTest.java</exclude>
+                                    </excludes>
+                                </resource>
+                                <resource>
+                                    <directory>${projectRoot}/sshd-sftp/src/test/java</directory>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                    <reportsDirectory>${project.build.directory}/surefire-reports-netty</reportsDirectory>
+                    <systemProperties>
+                        <org.apache.sshd.common.io.IoServiceFactoryFactory>org.apache.sshd.netty.NettyIoServiceFactoryFactory</org.apache.sshd.common.io.IoServiceFactoryFactory>
+                    </systemProperties>
+                    <excludes>
+                            <!-- These tests use NIO explicitly -->
+                        <exclude>**/*LoadTest.java</exclude>
+                        <exclude>**/ProxyTest.java</exclude>
+                        <exclude>**/Nio2ServiceTest.java</exclude>
+                            <!-- TODO need some more research as to why this fails on MINA but not on NIO2 -->
+                        <exclude>**/ClientDeadlockTest.java</exclude>
+                        <exclude>**/ApacheServer*Test.java</exclude>
+                        <exclude>**/ClientTest.java</exclude>
+                        <exclude>**/SpaceAvailableExtensionImplTest.java</exclude>
+                    </excludes>
+                        <!-- No need to re-run core tests that do not involve session creation -->
+                    <excludedGroups>org.apache.sshd.util.test.NoIoTestCase</excludedGroups>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <additionalparam>-Xdoclint:none</additionalparam>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoAcceptor.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoAcceptor.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoAcceptor.java
new file mode 100644
index 0000000..8d88cdf
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoAcceptor.java
@@ -0,0 +1,174 @@
+/*
+ * 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.netty;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.io.IoAcceptor;
+import org.apache.sshd.common.io.IoHandler;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.util.concurrent.GlobalEventExecutor;
+
+/**
+ * The Netty based IoAcceptor implementation.
+ *
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoAcceptor extends NettyIoService implements IoAcceptor {
+
+    protected final ServerBootstrap bootstrap = new ServerBootstrap();
+    protected final DefaultCloseFuture closeFuture = new DefaultCloseFuture(toString(), lock);
+    protected final Map<SocketAddress, Channel> boundAddresses = new HashMap<>();
+    protected final IoHandler handler;
+
+    public NettyIoAcceptor(NettyIoServiceFactory factory, IoHandler handler) {
+        this.factory = factory;
+        this.handler = handler;
+        channelGroup = new DefaultChannelGroup("sshd-acceptor-channels", GlobalEventExecutor.INSTANCE);
+        bootstrap.group(factory.eventLoopGroup)
+                .channel(NioServerSocketChannel.class)
+                .option(ChannelOption.SO_BACKLOG, 100)
+                .handler(new LoggingHandler(LogLevel.INFO))
+                .childHandler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    public void initChannel(SocketChannel ch) throws Exception {
+                        ChannelPipeline p = ch.pipeline();
+                        p.addLast(new NettyIoSession(NettyIoAcceptor.this, handler).adapter);
+                    }
+                });
+    }
+
+    @Override
+    public void bind(Collection<? extends SocketAddress> addresses) throws IOException {
+        for (SocketAddress address : addresses) {
+            bind(address);
+        }
+    }
+
+    @Override
+    public void bind(SocketAddress address) throws IOException {
+        InetSocketAddress inetAddress = (InetSocketAddress) address;
+        ChannelFuture f = bootstrap.bind(inetAddress);
+        Channel channel = f.channel();
+        channelGroup.add(channel);
+        try {
+            f.sync();
+            SocketAddress bound = channel.localAddress();
+            boundAddresses.put(bound, channel);
+            channel.closeFuture().addListener(fut -> boundAddresses.remove(bound));
+        } catch (InterruptedException e) {
+            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    @Override
+    public void unbind(Collection<? extends SocketAddress> addresses) {
+        CountDownLatch latch = new CountDownLatch(addresses.size());
+        for (SocketAddress address : addresses) {
+            Channel channel = boundAddresses.get(address);
+            if (channel != null) {
+                ChannelFuture fut;
+                if (channel.isOpen()) {
+                    fut = channel.close();
+                } else {
+                    fut = channel.closeFuture();
+                }
+                fut.addListener(f -> latch.countDown());
+            } else {
+                latch.countDown();
+            }
+        }
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    @Override
+    public void unbind(SocketAddress address) {
+        Channel channel = boundAddresses.get(address);
+        if (channel != null) {
+            ChannelFuture fut;
+            if (channel.isOpen()) {
+                fut = channel.close();
+            } else {
+                fut = channel.closeFuture();
+            }
+            try {
+                fut.sync();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    @Override
+    public void unbind() {
+        Collection<SocketAddress> addresses = getBoundAddresses();
+        if (log.isDebugEnabled()) {
+            log.debug("Unbinding {}", addresses);
+        }
+
+        unbind(addresses);
+    }
+
+    @Override
+    public Set<SocketAddress> getBoundAddresses() {
+        return new HashSet<>(boundAddresses.keySet());
+    }
+
+    @Override
+    protected CloseFuture doCloseGracefully() {
+        channelGroup.close().addListener(fut -> closeFuture.setClosed());
+        return closeFuture;
+    }
+
+    @Override
+    protected void doCloseImmediately() {
+        doCloseGracefully();
+        super.doCloseImmediately();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoConnector.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoConnector.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoConnector.java
new file mode 100644
index 0000000..ebced0d
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoConnector.java
@@ -0,0 +1,125 @@
+/*
+ * 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.netty;
+
+import java.net.SocketAddress;
+
+import org.apache.sshd.common.future.DefaultSshFuture;
+import org.apache.sshd.common.io.IoConnectFuture;
+import org.apache.sshd.common.io.IoConnector;
+import org.apache.sshd.common.io.IoHandler;
+import org.apache.sshd.common.io.IoSession;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.util.concurrent.GlobalEventExecutor;
+
+/**
+ * The Netty based IoConnector implementation.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoConnector extends NettyIoService implements IoConnector {
+
+    protected final Bootstrap bootstrap = new Bootstrap();
+    protected final IoHandler handler;
+
+    public NettyIoConnector(NettyIoServiceFactory factory, IoHandler handler) {
+        this.factory = factory;
+        this.handler = handler;
+        channelGroup = new DefaultChannelGroup("sshd-connector-channels", GlobalEventExecutor.INSTANCE);
+        bootstrap.group(factory.eventLoopGroup)
+                .channel(NioSocketChannel.class)
+                .option(ChannelOption.SO_BACKLOG, 100)
+                .handler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    protected void initChannel(SocketChannel ch) throws Exception {
+                        NettyIoSession session = new NettyIoSession(NettyIoConnector.this, handler);
+                        ChannelPipeline p = ch.pipeline();
+                        p.addLast(new LoggingHandler(LogLevel.INFO));
+                        p.addLast(session.adapter);
+                    }
+                });
+    }
+
+    @Override
+    public IoConnectFuture connect(SocketAddress address) {
+        boolean debugEnabled = log.isDebugEnabled();
+        if (debugEnabled) {
+            log.debug("Connecting to {}", address);
+        }
+
+        IoConnectFuture future = new DefaultIoConnectFuture(address, null);
+        ChannelFuture chf = bootstrap.connect(address);
+        Channel channel = chf.channel();
+        channel.attr(CONNECT_FUTURE_KEY).set(future);
+        chf.addListener(cf -> {
+            Throwable t = chf.cause();
+            if (t != null) {
+                future.setException(t);
+            } else if (chf.isCancelled()) {
+                future.cancel();
+            }
+        });
+        return future;
+    }
+
+    public static class DefaultIoConnectFuture extends DefaultSshFuture<IoConnectFuture> implements IoConnectFuture {
+        public DefaultIoConnectFuture(Object id, Object lock) {
+            super(id, lock);
+        }
+
+        @Override
+        public IoSession getSession() {
+            Object v = getValue();
+            return v instanceof IoSession ? (IoSession) v : null;
+        }
+
+        @Override
+        public Throwable getException() {
+            Object v = getValue();
+            return v instanceof Throwable ? (Throwable) v : null;
+        }
+
+        @Override
+        public boolean isConnected() {
+            return getValue() instanceof IoSession;
+        }
+
+        @Override
+        public void setSession(IoSession session) {
+            setValue(session);
+        }
+
+        @Override
+        public void setException(Throwable exception) {
+            setValue(exception);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoService.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoService.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoService.java
new file mode 100644
index 0000000..9bd3ca3
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoService.java
@@ -0,0 +1,55 @@
+/*
+ * 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.netty;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.sshd.common.io.IoConnectFuture;
+import org.apache.sshd.common.io.IoService;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.util.closeable.AbstractCloseable;
+
+import io.netty.channel.group.ChannelGroup;
+import io.netty.util.AttributeKey;
+
+/**
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoService extends AbstractCloseable implements IoService {
+
+    public static final AttributeKey<IoConnectFuture> CONNECT_FUTURE_KEY = AttributeKey.valueOf(IoConnectFuture.class.getName());
+
+    protected final AtomicLong sessionSeq = new AtomicLong();
+    protected final Map<Long, IoSession> sessions = new ConcurrentHashMap<>();
+    protected NettyIoServiceFactory factory;
+    protected ChannelGroup channelGroup;
+
+    public NettyIoService() {
+        super();
+    }
+
+    @Override
+    public Map<Long, IoSession> getManagedSessions() {
+        return sessions;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactory.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactory.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactory.java
new file mode 100644
index 0000000..2bc3f97
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactory.java
@@ -0,0 +1,75 @@
+/*
+ * 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.netty;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.io.IoAcceptor;
+import org.apache.sshd.common.io.IoConnector;
+import org.apache.sshd.common.io.IoHandler;
+import org.apache.sshd.common.io.IoServiceFactory;
+import org.apache.sshd.common.util.closeable.AbstractCloseable;
+
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+
+/**
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoServiceFactory extends AbstractCloseable implements IoServiceFactory {
+
+    protected final EventLoopGroup eventLoopGroup;
+    protected final boolean closeEventLoopGroup;
+
+    public NettyIoServiceFactory() {
+        this(null);
+    }
+
+    public NettyIoServiceFactory(EventLoopGroup group) {
+        this.eventLoopGroup = group != null ? group : new NioEventLoopGroup();
+        this.closeEventLoopGroup = group == null;
+    }
+
+    @Override
+    public IoConnector createConnector(IoHandler handler) {
+        return new NettyIoConnector(this, handler);
+    }
+
+    @Override
+    public IoAcceptor createAcceptor(IoHandler handler) {
+        return new NettyIoAcceptor(this, handler);
+    }
+
+    @Override
+    protected CloseFuture doCloseGracefully() {
+        if (closeEventLoopGroup) {
+            eventLoopGroup.shutdownGracefully().addListener(fut -> closeFuture.setClosed());
+        } else {
+            closeFuture.setClosed();
+        }
+        return closeFuture;
+    }
+
+    @Override
+    protected void doCloseImmediately() {
+        doCloseGracefully();
+        super.doCloseImmediately();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactoryFactory.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactoryFactory.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactoryFactory.java
new file mode 100644
index 0000000..bead4aa
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoServiceFactoryFactory.java
@@ -0,0 +1,49 @@
+/*
+ * 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.netty;
+
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.io.IoServiceFactory;
+import org.apache.sshd.common.io.IoServiceFactoryFactory;
+
+import io.netty.channel.EventLoopGroup;
+
+/**
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoServiceFactoryFactory implements IoServiceFactoryFactory {
+
+    protected final EventLoopGroup eventLoopGroup;
+
+    public NettyIoServiceFactoryFactory() {
+        this(null);
+    }
+
+    public NettyIoServiceFactoryFactory(EventLoopGroup eventLoopGroup) {
+        this.eventLoopGroup = eventLoopGroup;
+    }
+
+    @Override
+    public IoServiceFactory create(FactoryManager manager) {
+        return new NettyIoServiceFactory(eventLoopGroup);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java
new file mode 100644
index 0000000..57aecc6
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java
@@ -0,0 +1,211 @@
+/*
+ * 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.netty;
+
+import java.net.SocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.io.AbstractIoWriteFuture;
+import org.apache.sshd.common.io.IoConnectFuture;
+import org.apache.sshd.common.io.IoHandler;
+import org.apache.sshd.common.io.IoService;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.closeable.AbstractCloseable;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelPromise;
+
+/**
+ * The Netty based IoSession implementation.
+ *
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NettyIoSession extends AbstractCloseable implements IoSession {
+
+    protected final Map<Object, Object> attributes = new HashMap<>();
+    protected final NettyIoService service;
+    protected final IoHandler handler;
+    protected final DefaultCloseFuture closeFuture = new DefaultCloseFuture(toString(), lock);
+    protected final long id;
+    protected ChannelHandlerContext context;
+    protected SocketAddress remoteAddr;
+    protected ChannelFuture prev;
+    protected final ChannelInboundHandlerAdapter adapter = new Adapter();
+
+    public NettyIoSession(NettyIoService service, IoHandler handler) {
+        this.service = service;
+        this.handler = handler;
+        this.id = service.sessionSeq.incrementAndGet();
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    @Override
+    public Object getAttribute(Object key) {
+        return attributes.get(key);
+    }
+
+    @Override
+    public Object setAttribute(Object key, Object value) {
+        return attributes.put(key, value);
+    }
+
+    @Override
+    public Object setAttributeIfAbsent(Object key, Object value) {
+        return attributes.putIfAbsent(key, value);
+    }
+
+    @Override
+    public Object removeAttribute(Object key) {
+        return attributes.remove(key);
+    }
+
+    @Override
+    public SocketAddress getRemoteAddress() {
+        return remoteAddr;
+    }
+
+    @Override
+    public SocketAddress getLocalAddress() {
+        return context.channel().localAddress();
+    }
+
+    @Override
+    public IoWriteFuture writePacket(Buffer buffer) {
+        ByteBuf buf = Unpooled.buffer(buffer.available());
+        buf.writeBytes(buffer.array(), buffer.rpos(), buffer.available());
+        DefaultIoWriteFuture msg = new DefaultIoWriteFuture(getRemoteAddress(), null);
+        ChannelPromise next = context.newPromise();
+        prev.addListener(whatever -> {
+            if (context != null) {
+                context.writeAndFlush(buf, next);
+            }
+        });
+        prev = next;
+        next.addListener(fut -> {
+            if (fut.isSuccess()) {
+                msg.setValue(Boolean.TRUE);
+            } else {
+                msg.setValue(fut.cause());
+            }
+        });
+        return msg;
+    }
+
+    @Override
+    public IoService getService() {
+        return service;
+    }
+
+    @Override
+    protected CloseFuture doCloseGracefully() {
+        context.writeAndFlush(Unpooled.EMPTY_BUFFER).
+                addListener(ChannelFutureListener.CLOSE).
+                addListener(fut -> {
+                    closeFuture.setClosed();
+                });
+        return closeFuture;
+    }
+
+    @Override
+    protected void doCloseImmediately() {
+        context.close();
+        super.doCloseImmediately();
+    }
+
+    protected void channelActive(ChannelHandlerContext ctx) throws Exception {
+        context = ctx;
+        service.channelGroup.add(ctx.channel());
+        service.sessions.put(id, NettyIoSession.this);
+        prev = context.newPromise().setSuccess();
+        remoteAddr = context.channel().remoteAddress();
+        handler.sessionCreated(NettyIoSession.this);
+        IoConnectFuture future = ctx.channel().attr(NettyIoService.CONNECT_FUTURE_KEY).get();
+        if (future != null) {
+            future.setSession(NettyIoSession.this);
+        }
+    }
+
+    protected void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        service.sessions.remove(id);
+        handler.sessionClosed(NettyIoSession.this);
+        context = null;
+    }
+
+    protected void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        ByteBuf buf = (ByteBuf) msg;
+        handler.messageReceived(NettyIoSession.this, NettySupport.asReadable(buf));
+    }
+
+    protected void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        handler.exceptionCaught(NettyIoSession.this, cause);
+    }
+
+    protected static class DefaultIoWriteFuture extends AbstractIoWriteFuture {
+
+        public DefaultIoWriteFuture(Object id, Object lock) {
+            super(id, lock);
+        }
+    }
+
+    /**
+     * Simple netty adapter to use as a bridge.
+     */
+    protected class Adapter extends ChannelInboundHandlerAdapter {
+
+        public Adapter() {
+            super();
+        }
+
+        @Override
+        public void channelActive(ChannelHandlerContext ctx) throws Exception {
+            NettyIoSession.this.channelActive(ctx);
+        }
+
+        @Override
+        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+            NettyIoSession.this.channelInactive(ctx);
+        }
+
+        @Override
+        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+            NettyIoSession.this.channelRead(ctx, msg);
+        }
+
+        @Override
+        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+            NettyIoSession.this.exceptionCaught(ctx, cause);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/java/org/apache/sshd/netty/NettySupport.java
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettySupport.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettySupport.java
new file mode 100644
index 0000000..9f9de89
--- /dev/null
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettySupport.java
@@ -0,0 +1,45 @@
+/*
+ * 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.netty;
+
+import org.apache.sshd.common.util.Readable;
+
+import io.netty.buffer.ByteBuf;
+
+public final class NettySupport {
+
+    private NettySupport() {
+        throw new UnsupportedOperationException("No instance allowed");
+    }
+
+    public static Readable asReadable(final ByteBuf buffer) {
+        return new Readable() {
+            @Override
+            public int available() {
+                return buffer.readableBytes();
+            }
+
+            @Override
+            public void getRawBytes(byte[] data, int offset, int len) {
+                buffer.getBytes(0, data, offset, len);
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/598c991f/sshd-netty/src/main/resources/META-INF/services/org.apache.sshd.common.io.IoServiceFactoryFactory
----------------------------------------------------------------------
diff --git a/sshd-netty/src/main/resources/META-INF/services/org.apache.sshd.common.io.IoServiceFactoryFactory b/sshd-netty/src/main/resources/META-INF/services/org.apache.sshd.common.io.IoServiceFactoryFactory
new file mode 100644
index 0000000..be71649
--- /dev/null
+++ b/sshd-netty/src/main/resources/META-INF/services/org.apache.sshd.common.io.IoServiceFactoryFactory
@@ -0,0 +1,20 @@
+##
+## 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.
+##
+
+org.apache.sshd.netty.NettyIoServiceFactoryFactory