You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2015/09/02 07:40:00 UTC

[1/4] mina-sshd git commit: [SSHD-559] Add support for reading specific host configuration from config file

Repository: mina-sshd
Updated Branches:
  refs/heads/master 3615e4ffb -> 950e896dc


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/common/cipher/BuiltinCiphersTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/cipher/BuiltinCiphersTest.java b/sshd-core/src/test/java/org/apache/sshd/common/cipher/BuiltinCiphersTest.java
index 77c1894..d2a8e87 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/cipher/BuiltinCiphersTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/cipher/BuiltinCiphersTest.java
@@ -39,6 +39,7 @@ import org.apache.sshd.common.cipher.BuiltinCiphers.ParseResult;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.Utils;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runners.MethodSorters;
@@ -156,14 +157,14 @@ public class BuiltinCiphersTest extends BaseTestSupport {
 
     @Test
     public void testSshClientSupportedCiphersConfiguration() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             testSupportedCiphersConfiguration(client);
         }
     }
 
     @Test
     public void testSshSercerSupportedCiphersConfiguration() throws Exception {
-        try (SshServer server = SshServer.setUpDefaultServer()) {
+        try (SshServer server = Utils.setupTestServer()) {
             testSupportedCiphersConfiguration(server);
         }
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java b/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
index 14279d8..41a91df 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
@@ -27,15 +27,12 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-import com.jcraft.jsch.JSch;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.random.Random;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.JSchLogger;
 import org.apache.sshd.util.SimpleUserInfo;
 import org.apache.sshd.util.Utils;
@@ -47,6 +44,8 @@ import org.junit.runners.MethodSorters;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
+import com.jcraft.jsch.JSch;
+
 /**
  * Test Cipher algorithms.
  *
@@ -105,11 +104,8 @@ public class CipherTest extends BaseTestSupport {
     public void testBuiltinCipherSession() throws Exception {
         Assume.assumeTrue("No internal support for " + builtInCipher.getName(), builtInCipher.isSupported() && checkCipher(jschCipher.getName()));
 
-        try (SshServer sshd = SshServer.setUpDefaultServer()) {
-            sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        try (SshServer sshd = Utils.setupTestServer()) {
             sshd.setCipherFactories(Arrays.<NamedFactory<org.apache.sshd.common.cipher.Cipher>>asList(builtInCipher));
-            sshd.setShellFactory(new EchoShellFactory());
-            sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
             sshd.start();
 
             try {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/common/compression/CompressionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/compression/CompressionTest.java b/sshd-core/src/test/java/org/apache/sshd/common/compression/CompressionTest.java
index 035e8c2..ee4f2cb 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/compression/CompressionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/compression/CompressionTest.java
@@ -23,12 +23,9 @@ import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 
-import com.jcraft.jsch.JSch;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.JSchLogger;
 import org.apache.sshd.util.SimpleUserInfo;
 import org.apache.sshd.util.Utils;
@@ -37,6 +34,8 @@ import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runners.MethodSorters;
 
+import com.jcraft.jsch.JSch;
+
 /**
  * Test compression algorithms.
  *
@@ -66,11 +65,8 @@ public class CompressionTest extends BaseTestSupport {
     }
 
     protected void setUp(NamedFactory<org.apache.sshd.common.compression.Compression> compression) throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd = Utils.setupTestServer();
         sshd.setCompressionFactories(Arrays.<NamedFactory<org.apache.sshd.common.compression.Compression>>asList(compression));
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.start();
         JSch.setConfig("compression.s2c", "zlib@openssh.com,zlib,none");
         JSch.setConfig("compression.c2s", "zlib@openssh.com,zlib,none");

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/common/io/nio2/Nio2ServiceTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/io/nio2/Nio2ServiceTest.java b/sshd-core/src/test/java/org/apache/sshd/common/io/nio2/Nio2ServiceTest.java
index 002196c..8dd1e07 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/io/nio2/Nio2ServiceTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/io/nio2/Nio2ServiceTest.java
@@ -25,9 +25,7 @@ import java.util.concurrent.TimeUnit;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.ServerTest.TestEchoShellFactory;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
 import org.apache.sshd.util.Utils;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
@@ -44,7 +42,7 @@ public class Nio2ServiceTest extends BaseTestSupport {
 
     @Test   // see SSHD-554
     public void testSetSocketOptions() throws Exception {
-        try(SshServer sshd = SshServer.setUpDefaultServer()) {
+        try(SshServer sshd = Utils.setupTestServer()) {
             FactoryManagerUtils.updateProperty(sshd, FactoryManager.SOCKET_KEEPALIVE, true);
             FactoryManagerUtils.updateProperty(sshd, FactoryManager.SOCKET_LINGER, 5);
             FactoryManagerUtils.updateProperty(sshd, FactoryManager.SOCKET_RCVBUF, 1024);
@@ -52,9 +50,6 @@ public class Nio2ServiceTest extends BaseTestSupport {
             FactoryManagerUtils.updateProperty(sshd, FactoryManager.SOCKET_SNDBUF, 1024);
             FactoryManagerUtils.updateProperty(sshd, FactoryManager.TCP_NODELAY, true);
 
-            sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-            sshd.setShellFactory(new TestEchoShellFactory());
-            sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
             sshd.start();
 
             int port = sshd.getPort();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/common/mac/MacTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/mac/MacTest.java b/sshd-core/src/test/java/org/apache/sshd/common/mac/MacTest.java
index 48402e9..11b544c 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/mac/MacTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/mac/MacTest.java
@@ -23,15 +23,12 @@ import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 
-import com.jcraft.jsch.JSch;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.cipher.BuiltinCiphers;
 import org.apache.sshd.common.cipher.Cipher;
 import org.apache.sshd.common.random.Random;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.JSchLogger;
 import org.apache.sshd.util.SimpleUserInfo;
 import org.apache.sshd.util.Utils;
@@ -41,6 +38,8 @@ import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runners.MethodSorters;
 
+import com.jcraft.jsch.JSch;
+
 /**
  * Test Cipher algorithms.
  *
@@ -117,11 +116,9 @@ public class MacTest extends BaseTestSupport {
 
 
     protected void setUp(NamedFactory<Mac> mac) throws Exception {
-        sshd = SshServer.setUpDefaultServer();
+        sshd = Utils.setupTestServer();
         sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
         sshd.setMacFactories(Arrays.<NamedFactory<Mac>>asList(mac));
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.start();
         port = sshd.getPort();
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java b/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
index 8899b8d..14906db 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
@@ -27,8 +27,8 @@ import org.apache.sshd.common.kex.KexProposalOption;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.Utils;
 import org.junit.Before;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
@@ -114,7 +114,7 @@ public class AbstractSessionTest extends BaseTestSupport {
 
     public static class MySession extends AbstractSession {
         public MySession() {
-            super(true, SshServer.setUpDefaultServer(), null);
+            super(true, Utils.setupTestServer(), null);
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/common/signature/AbstractSignatureFactoryTestSupport.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/signature/AbstractSignatureFactoryTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/common/signature/AbstractSignatureFactoryTestSupport.java
index b958ceb..cca6f1f 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/signature/AbstractSignatureFactoryTestSupport.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/signature/AbstractSignatureFactoryTestSupport.java
@@ -35,7 +35,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.Utils;
 import org.junit.After;
 import org.junit.Before;
 
@@ -66,8 +66,7 @@ public abstract class AbstractSignatureFactoryTestSupport extends BaseTestSuppor
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+        sshd = Utils.setupTestServer();
     }
 
     @After
@@ -118,7 +117,7 @@ public abstract class AbstractSignatureFactoryTestSupport extends BaseTestSuppor
         sshd.start();
         port = sshd.getPort();
 
-        client = SshClient.setUpDefaultClient();
+        client = Utils.setupTestClient();
         client.setSignatureFactories(signatures);
         client.start();
         try (ClientSession s = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
index 4c2c4ba..95a3577 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
@@ -63,7 +63,7 @@ import org.apache.sshd.deprecated.ClientUserAuthServiceOld;
 import org.apache.sshd.server.command.ScpCommandFactory;
 import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShell;
 import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
@@ -92,14 +92,12 @@ public class ServerTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd = Utils.setupTestServer();
         sshd.setShellFactory(new TestEchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.start();
         port = sshd.getPort();
 
-        client = SshClient.setUpDefaultClient();
+        client = Utils.setupTestClient();
     }
 
     @After
@@ -193,7 +191,7 @@ public class ServerTest extends BaseTestSupport {
     @Test
     public void testIdleTimeout() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
-        TestEchoShellFactory.TestEchoShell.latch = new CountDownLatch(1);
+        TestEchoShell.latch = new CountDownLatch(1);
         final long IDLE_TIMEOUT = 2500;
         FactoryManagerUtils.updateProperty(sshd, FactoryManager.IDLE_TIMEOUT, IDLE_TIMEOUT);
 
@@ -236,8 +234,8 @@ public class ServerTest extends BaseTestSupport {
             client.stop();
         }
 
-        assertTrue("Session latch not signalled in time", latch.await(1, TimeUnit.SECONDS));
-        assertTrue("Shell latch not signalled in time", TestEchoShellFactory.TestEchoShell.latch.await(1, TimeUnit.SECONDS));
+        assertTrue("Session latch not signalled in time", latch.await(1L, TimeUnit.SECONDS));
+        assertTrue("Shell latch not signalled in time", TestEchoShell.latch.await(1L, TimeUnit.SECONDS));
     }
 
     /**
@@ -585,22 +583,22 @@ public class ServerTest extends BaseTestSupport {
         public Command create() {
             return new TestEchoShell();
         }
+    }
 
-        public static class TestEchoShell extends EchoShell {
+    public static class TestEchoShell extends EchoShell {
 
-            public static CountDownLatch latch = new CountDownLatch(1);
+        public static CountDownLatch latch;
 
-            public TestEchoShell() {
-                super();
-            }
+        public TestEchoShell() {
+            super();
+        }
 
-            @Override
-            public void destroy() {
-                if (latch != null) {
-                    latch.countDown();
-                }
-                super.destroy();
+        @Override
+        public void destroy() {
+            if (latch != null) {
+                latch.countDown();
             }
+            super.destroy();
         }
     }
 
@@ -663,10 +661,11 @@ public class ServerTest extends BaseTestSupport {
         @Override
         public void run() {
             try {
-                Thread.sleep(5000);
+                Thread.sleep(TimeUnit.SECONDS.toMillis(5L));
                 while (true) {
+                    byte[] data = "0123456789\n".getBytes(StandardCharsets.UTF_8);
                     for (int i = 0; i < 100; i++) {
-                        out.write("0123456789\n".getBytes(StandardCharsets.UTF_8));
+                        out.write(data);
                     }
                     out.flush();
                 }
@@ -683,14 +682,11 @@ public class ServerTest extends BaseTestSupport {
     }
 
     public static void main(String[] args) throws Exception {
-        SshServer sshd = SshServer.setUpDefaultServer();
+        SshServer sshd = Utils.setupTestServer();
         FactoryManagerUtils.updateProperty(sshd, FactoryManager.IDLE_TIMEOUT, TimeUnit.SECONDS.toMillis(10L));
         sshd.setPort(8001);
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
         sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
-        sshd.setShellFactory(new EchoShellFactory());
         sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.start();
         Thread.sleep(100000);
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/server/SshServerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/SshServerTest.java b/sshd-core/src/test/java/org/apache/sshd/server/SshServerTest.java
index 4d63003..2cd8734 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/SshServerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/SshServerTest.java
@@ -22,8 +22,6 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.Utils;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
@@ -49,7 +47,7 @@ public class SshServerTest extends BaseTestSupport {
     public void testExecutorShutdownFalse() throws Exception {
         ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
 
-        try (SshServer sshd = createTestServer()) {
+        try (SshServer sshd = Utils.setupTestServer()) {
             sshd.setScheduledExecutorService(executorService);
 
             sshd.start();
@@ -64,7 +62,7 @@ public class SshServerTest extends BaseTestSupport {
     public void testExecutorShutdownTrue() throws Exception {
         ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
 
-        try (SshServer sshd = createTestServer()) {
+        try (SshServer sshd = Utils.setupTestServer()) {
             sshd.setScheduledExecutorService(executorService, true);
 
             sshd.start();
@@ -76,7 +74,7 @@ public class SshServerTest extends BaseTestSupport {
 
     @Test
     public void testDynamicPort() throws Exception {
-        try (SshServer sshd = createTestServer()) {
+        try (SshServer sshd = Utils.setupTestServer()) {
             sshd.setHost("localhost");
             sshd.start();
 
@@ -85,14 +83,4 @@ public class SshServerTest extends BaseTestSupport {
             sshd.stop();
         }
     }
-
-
-    private SshServer createTestServer() {
-        SshServer sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-
-        return sshd;
-    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
index 52f6ad6..708e677 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
@@ -36,6 +36,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
 import org.apache.sshd.server.session.ServerSession;
 import org.apache.sshd.util.BaseTestSupport;
@@ -89,16 +90,15 @@ public class AuthorizedKeysAuthenticatorTest extends BaseTestSupport {
 
         assertHierarchyTargetFolderExists(file.getParent());
 
-        final String EOL = System.getProperty("line.separator");
         while(keyLines.size() > 0) {
             try (Writer w = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
                 w.append(PublicKeyEntry.COMMENT_CHAR)
                  .append(' ').append(getCurrentTestName())
                  .append(' ').append(String.valueOf(keyLines.size())).append(" remaining keys")
-                 .append(EOL)
+                 .append(IoUtils.EOL)
                  ;
                 for (String l : keyLines) {
-                    w.append(l).append(EOL);
+                    w.append(l).append(IoUtils.EOL);
                 }
             }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java b/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
index 60c47e8..29fadfa 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
@@ -50,7 +50,6 @@ import org.apache.sshd.server.ExitCallback;
 import org.apache.sshd.server.SessionAware;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
-import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
 import org.apache.sshd.server.command.ScpCommandFactory;
 import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
@@ -293,7 +292,7 @@ public class SshFsMounter {
 
         System.err.println("Starting SSHD on port " + port);
 
-        SshServer sshd = SshServer.setUpDefaultServer();
+        SshServer sshd = Utils.setupTestServer();
         Map<String, Object> props = sshd.getProperties();
 //        FactoryManagerUtils.updateProperty(props, ServerFactoryManager.WELCOME_BANNER, "Welcome to SSH-FS Mounter\n");
         props.putAll(options);
@@ -308,7 +307,6 @@ public class SshFsMounter {
 
         sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
         sshd.setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE);
-        sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
         sshd.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
         sshd.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(MounterCommandFactory.INSTANCE).build());
         sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/util/EchoShell.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/EchoShell.java b/sshd-core/src/test/java/org/apache/sshd/util/EchoShell.java
new file mode 100644
index 0000000..95fdbe6
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/util/EchoShell.java
@@ -0,0 +1,121 @@
+/*
+ * 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 java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class EchoShell implements Command, Runnable {
+
+    private InputStream in;
+    private OutputStream out;
+    private OutputStream err;
+    private ExitCallback callback;
+    private Environment environment;
+    private Thread thread;
+
+    public EchoShell() {
+        super();
+    }
+
+    public InputStream getIn() {
+        return in;
+    }
+
+    public OutputStream getOut() {
+        return out;
+    }
+
+    public OutputStream getErr() {
+        return err;
+    }
+
+    public Environment getEnvironment() {
+        return environment;
+    }
+
+    @Override
+    public void setInputStream(InputStream in) {
+        this.in = in;
+    }
+
+    @Override
+    public void setOutputStream(OutputStream out) {
+        this.out = out;
+    }
+
+    @Override
+    public void setErrorStream(OutputStream err) {
+        this.err = err;
+    }
+
+    @Override
+    public void setExitCallback(ExitCallback callback) {
+        this.callback = callback;
+    }
+
+    @Override
+    public void start(Environment env) throws IOException {
+        environment = env;
+        thread = new Thread(this, "EchoShell");
+        thread.setDaemon(true);
+        thread.start();
+    }
+
+    @Override
+    public void destroy() {
+        thread.interrupt();
+    }
+
+    @Override
+    public void run() {
+        BufferedReader r = new BufferedReader(new InputStreamReader(in));
+        try {
+            for (; ; ) {
+                String s = r.readLine();
+                if (s == null) {
+                    return;
+                }
+                out.write((s + "\n").getBytes(StandardCharsets.UTF_8));
+                out.flush();
+                if ("exit".equals(s)) {
+                    return;
+                }
+            }
+        } catch (InterruptedIOException e) {
+            // Ignore
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            callback.onExit(0);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/util/EchoShellFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/EchoShellFactory.java b/sshd-core/src/test/java/org/apache/sshd/util/EchoShellFactory.java
index 1fdacd2..1121fdf 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/EchoShellFactory.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/EchoShellFactory.java
@@ -18,18 +18,8 @@
  */
 package org.apache.sshd.util;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.InterruptedIOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.server.Command;
-import org.apache.sshd.server.Environment;
-import org.apache.sshd.server.ExitCallback;
 
 /**
  * TODO Add javadoc
@@ -37,96 +27,14 @@ import org.apache.sshd.server.ExitCallback;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class EchoShellFactory implements Factory<Command> {
+    public static final EchoShellFactory INSTANCE = new EchoShellFactory();
+
+    public EchoShellFactory() {
+        super();
+    }
 
     @Override
     public Command create() {
         return new EchoShell();
     }
-
-    public static class EchoShell implements Command, Runnable {
-
-        private InputStream in;
-        private OutputStream out;
-        private OutputStream err;
-        private ExitCallback callback;
-        private Environment environment;
-        private Thread thread;
-
-        public EchoShell() {
-            super();
-        }
-
-        public InputStream getIn() {
-            return in;
-        }
-
-        public OutputStream getOut() {
-            return out;
-        }
-
-        public OutputStream getErr() {
-            return err;
-        }
-
-        public Environment getEnvironment() {
-            return environment;
-        }
-
-        @Override
-        public void setInputStream(InputStream in) {
-            this.in = in;
-        }
-
-        @Override
-        public void setOutputStream(OutputStream out) {
-            this.out = out;
-        }
-
-        @Override
-        public void setErrorStream(OutputStream err) {
-            this.err = err;
-        }
-
-        @Override
-        public void setExitCallback(ExitCallback callback) {
-            this.callback = callback;
-        }
-
-        @Override
-        public void start(Environment env) throws IOException {
-            environment = env;
-            thread = new Thread(this, "EchoShell");
-            thread.setDaemon(true);
-            thread.start();
-        }
-
-        @Override
-        public void destroy() {
-            thread.interrupt();
-        }
-
-        @Override
-        public void run() {
-            BufferedReader r = new BufferedReader(new InputStreamReader(in));
-            try {
-                for (; ; ) {
-                    String s = r.readLine();
-                    if (s == null) {
-                        return;
-                    }
-                    out.write((s + "\n").getBytes(StandardCharsets.UTF_8));
-                    out.flush();
-                    if ("exit".equals(s)) {
-                        return;
-                    }
-                }
-            } catch (InterruptedIOException e) {
-                // Ignore
-            } catch (Exception e) {
-                e.printStackTrace();
-            } finally {
-                callback.onExit(0);
-            }
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/util/UnknownCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/UnknownCommand.java b/sshd-core/src/test/java/org/apache/sshd/util/UnknownCommand.java
new file mode 100644
index 0000000..75605d9
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/util/UnknownCommand.java
@@ -0,0 +1,82 @@
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+
+/**
+ * Implementation of an unknown command that can be returned by <code>CommandFactory</code>
+ * when the command is not known, as it is supposed to always
+ * return a valid <code>Command</code> object.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class UnknownCommand implements Command {
+
+    private String command;
+    private InputStream in;
+    private OutputStream out;
+    private OutputStream err;
+    private ExitCallback callback;
+
+    public UnknownCommand(String command) {
+        this.command = command;
+    }
+
+    @Override
+    public void setInputStream(InputStream in) {
+        this.in = in;
+    }
+
+    @Override
+    public void setOutputStream(OutputStream out) {
+        this.out = out;
+    }
+
+    @Override
+    public void setErrorStream(OutputStream err) {
+        this.err = err;
+    }
+
+    @Override
+    public void setExitCallback(ExitCallback callback) {
+        this.callback = callback;
+    }
+
+    @Override
+    public void start(Environment env) throws IOException {
+        err.write(("Unknown command: " + command + "\n").getBytes(StandardCharsets.UTF_8));
+        err.flush();
+        if (callback != null) {
+            callback.onExit(1, "Unknown command: " + command);
+        }
+    }
+
+    @Override
+    public void destroy() {
+        // ignored
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/util/UnknownCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/UnknownCommandFactory.java b/sshd-core/src/test/java/org/apache/sshd/util/UnknownCommandFactory.java
new file mode 100644
index 0000000..69a8e8e
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/util/UnknownCommandFactory.java
@@ -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.Command;
+import org.apache.sshd.server.CommandFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class UnknownCommandFactory implements CommandFactory {
+    public static final UnknownCommandFactory INSTANCE = new UnknownCommandFactory();
+
+    public UnknownCommandFactory() {
+        super();
+    }
+
+    @Override
+    public Command createCommand(String command) {
+        return new UnknownCommand(command);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/util/Utils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/Utils.java b/sshd-core/src/test/java/org/apache/sshd/util/Utils.java
index a347e22..c9db26c 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/Utils.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/Utils.java
@@ -42,6 +42,9 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
+import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.keyprovider.AbstractFileKeyPairProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
@@ -49,6 +52,8 @@ import org.apache.sshd.common.random.Random;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.SecurityUtils;
 import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 
 public class Utils {
@@ -62,7 +67,6 @@ public class Utils {
             return provider;
         }
 
-
         File targetFolder = ValidateUtils.checkNotNull(detectTargetFolder(Utils.class), "Failed to detect target folder");
         File file = new File(targetFolder, "hostkey." + DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM.toLowerCase());
         SimpleGeneratorHostKeyProvider keyProvider = new SimpleGeneratorHostKeyProvider();
@@ -544,4 +548,21 @@ public class Utils {
         Path relPath = root.relativize(file);
         return relPath.toString().replace(File.separatorChar, '/');
     }
+
+    public static final SshClient setupTestClient() {
+        SshClient client = SshClient.setUpDefaultClient();
+        client.setServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE);
+        client.setHostConfigEntryResolver(HostConfigEntryResolver.EMPTY);
+        return client;
+    }
+
+    public static final SshServer setupTestServer() {
+        SshServer sshd = SshServer.setUpDefaultServer();
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+        sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
+        sshd.setShellFactory(EchoShellFactory.INSTANCE);
+        sshd.setCommandFactory(UnknownCommandFactory.INSTANCE);
+        return sshd;
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadGlobalHostsConfigEntries.config.txt
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadGlobalHostsConfigEntries.config.txt b/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadGlobalHostsConfigEntries.config.txt
new file mode 100644
index 0000000..a0be861
--- /dev/null
+++ b/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadGlobalHostsConfigEntries.config.txt
@@ -0,0 +1,23 @@
+# Global section first
+
+    User global
+    Port 7365
+    IdentityFile ~/.ssh/github.key
+    HostName 37.77.34.7
+
+# User override
+Host *.apache.org
+    User mina-sshd
+
+# Port override
+Host *.github.com
+    Port 443
+    
+# Host name override
+Host 10.*.*.*
+    HostName 7.3.6.5
+ 
+# Identity override
+Host 192.168.*.*
+    IdentityFile ~/.ssh/internal.key 
+    
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadMultipleHostPatterns.config.txt
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadMultipleHostPatterns.config.txt b/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadMultipleHostPatterns.config.txt
new file mode 100644
index 0000000..0f5edcb
--- /dev/null
+++ b/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadMultipleHostPatterns.config.txt
@@ -0,0 +1,5 @@
+# Same configuration duplicated in multiple configuration entries
+Host *.github.com *.apache.org 10.*.*.*
+    User mina-sshd
+    Port 443
+    HostName 7.3.6.5

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadSimpleHostsConfigEntries.config.txt
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadSimpleHostsConfigEntries.config.txt b/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadSimpleHostsConfigEntries.config.txt
new file mode 100644
index 0000000..1058777
--- /dev/null
+++ b/sshd-core/src/test/resources/org/apache/sshd/client/config/hosts/testReadSimpleHostsConfigEntries.config.txt
@@ -0,0 +1,8 @@
+# Simple configuration file
+Host *.github.com
+    User mina-sshd
+    Port 443
+    IdentityFile ~/.ssh/github.key
+
+Host *.apache.org
+    User mina-sshd

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommandFactory.java b/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommandFactory.java
index 191ca5d..1f32721 100644
--- a/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommandFactory.java
+++ b/sshd-git/src/main/java/org/apache/sshd/git/pack/GitPackCommandFactory.java
@@ -20,7 +20,7 @@ package org.apache.sshd.git.pack;
 
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.CommandFactory;
-import org.apache.sshd.server.command.UnknownCommand;
+import org.apache.sshd.util.UnknownCommand;
 
 /**
  * TODO Add javadoc

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommandFactory.java b/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommandFactory.java
index c6f767a..ff5ff50 100644
--- a/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommandFactory.java
+++ b/sshd-git/src/main/java/org/apache/sshd/git/pgm/GitPgmCommandFactory.java
@@ -20,7 +20,7 @@ package org.apache.sshd.git.pgm;
 
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.CommandFactory;
-import org.apache.sshd.server.command.UnknownCommand;
+import org.apache.sshd.util.UnknownCommand;
 
 /**
  * TODO Add javadoc


[3/4] mina-sshd git commit: [SSHD-559] Add support for reading specific host configuration from config file

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
new file mode 100644
index 0000000..8ee1d09
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
@@ -0,0 +1,1403 @@
+/*
+ * 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.client.config.hosts;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StreamCorruptedException;
+import java.io.Writer;
+import java.net.InetAddress;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.io.NoCloseReader;
+
+/**
+ * Represents an entry in the client's configuration file as defined by
+ * the <A HREF="http://www.gsp.com/cgi-bin/man.cgi?topic=ssh_config">configuration
+ * file format</A>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class HostConfigEntry implements Cloneable {
+
+    /**
+     * Used in a host pattern to denote zero or more consecutive characters
+     */
+    public static final char WILDCARD_PATTERN = '*';
+    public static final String ALL_HOSTS_PATTERN = String.valueOf(HostConfigEntry.WILDCARD_PATTERN);
+
+    /**
+     * Standard OpenSSH config file name
+     */
+    public static final String STD_CONFIG_FILENAME = "config";
+
+    /**
+     * Used in a host pattern to denote any <U>one</U> character
+     */
+    public static final char SINGLE_CHAR_PATTERN = '?';
+
+    /**
+     * The available pattern characters
+     */
+    public static final String PATTERN_CHARS = new String(new char[]{WILDCARD_PATTERN, SINGLE_CHAR_PATTERN});
+
+    public static final String HOST_CONFIG_PROP = "Host";
+    public static final String HOST_NAME_CONFIG_PROP = "HostName";
+    public static final String PORT_CONFIG_PROP = SshConfigFileReader.PORT_CONFIG_PROP;
+    public static final String USER_CONFIG_PROP = "User";
+    public static final String IDENTITY_FILE_CONFIG_PROP = "IdentityFile";
+
+    /**
+     * A case <U>insensitive</U> {@link Set} of the properties that receive special handling
+     */
+    public static final Set<String> EXPLICIT_PROPERTIES =
+            Collections.unmodifiableSet(
+                    GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER,
+                            HOST_CONFIG_PROP, HOST_NAME_CONFIG_PROP, PORT_CONFIG_PROP,
+                            USER_CONFIG_PROP, IDENTITY_FILE_CONFIG_PROP
+                        ));
+
+    public static final String MULTI_VALUE_SEPARATORS = " ,";
+
+    public static final char HOME_TILDE_CHAR = '~';
+    public static final char PATH_MACRO_CHAR = '%';
+    public static final char LOCAL_HOME_MACRO = 'd';
+    public static final char LOCAL_USER_MACRO = 'u';
+    public static final char LOCAL_HOST_MACRO = 'l';
+    public static final char REMOTE_HOST_MACRO = 'h';
+    public static final char REMOTE_USER_MACRO = 'r';
+    // Extra - not part of the standard
+    public static final char REMOTE_PORT_MACRO = 'p';
+
+    private static final class LazyDefaultConfigFileHolder {
+        private static final File KEYS_FILE = new File(PublicKeyEntry.getDefaultKeysFolder(), STD_CONFIG_FILENAME);
+    }
+
+    private String hostValue;
+    private Pattern hostPattern;
+    private String hostName;
+    private int port;
+    private String username;
+    private Collection<String> identities = Collections.emptyList();
+    private Map<String, String> properties = Collections.emptyMap();
+
+    public HostConfigEntry() {
+        super();
+    }
+
+    public HostConfigEntry(String pattern, String host, int port, String username) {
+        setHost(pattern);
+        setHostName(host);
+        setPort(port);
+        setUsername(username);
+    }
+
+    /**
+     * @return The host <U>pattern</U> represented by this entry
+     * @see <A HREF="http://www.gsp.com/cgi-bin/man.cgi?topic=ssh_config#3">Host Patterns</A>
+     */
+    public String getHost() {
+        return hostValue;
+    }
+
+    public Pattern getHostPattern() {
+        return hostPattern;
+    }
+
+    /**
+     * @param host The host name/address/pattern represented by the entry
+     */
+    public void setHost(String host) {
+        hostValue = host;
+        hostPattern = toPattern(host);
+    }
+
+    /**
+     * Checks if a given host name / address matches the entry's host pattern
+     *
+     * @param host The host name / address - ignored if {@code null}/empty
+     * @return {@code true} if the name / address matches the pattern
+     * @see #getHostPattern()
+     * @see #isHostMatch(String, Pattern)
+     */
+    public boolean isHostMatch(String host) {
+        return isHostMatch(host, getHostPattern());
+    }
+
+    /**
+     * @return The effective host name to connect to if the pattern matches
+     */
+    public String getHostName() {
+        return hostName;
+    }
+
+    public void setHostName(String hostName) {
+        this.hostName = hostName;
+    }
+
+    public String resolveHostName(String originalHost) {
+        return resolveHostName(originalHost, getHostName());
+    }
+
+    /**
+     * @return A port override - if positive
+     */
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    /**
+     * Resolves the effective port to use
+     *
+     * @param originalPort The original requested port
+     * @return If the host entry port is positive, then it is used, otherwise
+     * the original requested port
+     * @see #resolvePort(int, int)
+     */
+    public int resolvePort(int originalPort) {
+        return resolvePort(originalPort, getPort());
+    }
+
+    /**
+     * @return A username override - if not {@code null}/empty
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    /**
+     * Resolves the effective username
+     *
+     * @param originalUser The original requested username
+     * @return If the configured host entry username is not {@code null}/empty
+     * then it is used, otherwise the original one.
+     * @see #resolveUsername(String)
+     */
+    public String resolveUsername(String originalUser) {
+        return resolveUsername(originalUser, getUsername());
+    }
+
+    /**
+     * @return The current identities file paths - may be {@code null}/empty
+     */
+    public Collection<String> getIdentities() {
+        return identities;
+    }
+
+    /**
+     * @param path A {@link File{ that contains an identity key - never {@code null}
+     */
+    public void addIdentity(File file) {
+        addIdentity(ValidateUtils.checkNotNull(file, "No file").toPath());
+    }
+
+    /**
+     * @param path A {@link Path} to a file that contains an identity key
+     * - never {@code null}
+     */
+    public void addIdentity(Path path) {
+        addIdentity(ValidateUtils.checkNotNull(path, "No path").toAbsolutePath().normalize().toString());
+    }
+
+    /**
+     * Adds a path to an identity file
+     *
+     * @param id The identity path to add - never {@code null{
+     */
+    public void addIdentity(String id) {
+        String path = ValidateUtils.checkNotNullAndNotEmpty(id, "No identity provided");
+        if (GenericUtils.isEmpty(identities)) {
+            identities = new LinkedList<>();
+        }
+        identities.add(path);
+    }
+
+    public void setIdentities(Collection<String> identities) {
+        this.identities = (identities == null) ? Collections.<String>emptyList() : identities;
+    }
+
+    /**
+     * @return A {@link Map} of extra properties that have been read - may be
+     * {@code null}/empty, or even contain some values that have been parsed
+     * and set as members of the entry (e.g., host, port, etc.). <B>Note:</B>
+     * multi-valued keys use a comma-separated list of values
+     */
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    /**
+     * @param name Property name - never {@code null}/empty
+     * @param value Property value - {@code null} if no such property
+     * @see #getProperty(String, String)
+     */
+    public String getProperty(String name) {
+        return getProperty(name, null);
+    }
+
+    /**
+     * @param name Property name - never {@code null}/empty
+     * @param defaultValue Default value to return if no such property
+     * @return The property value or the default one if no such property
+     */
+    public String getProperty(String name, String defaultValue) {
+        String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name");
+        Map<String, String> props = getProperties();
+        if (GenericUtils.isEmpty(props)) {
+            return null;
+        }
+
+        String value = props.get(key);
+        if (GenericUtils.isEmpty(value)) {
+            return defaultValue;
+        } else {
+            return value;
+        }
+    }
+
+    /**
+     * Updates the values that are <U>not</U> already configured with those
+     * from the global entry
+     *
+     * @param globalEntry The global entry - ignored if {@code null} or
+     * same reference as this entry
+     * @return {@code true} if anything updated
+     */
+    public boolean processGlobalValues(HostConfigEntry globalEntry) {
+        if ((globalEntry == null) || (this == globalEntry)) {
+            return false;
+        }
+
+        boolean modified = false;
+        /*
+         * NOTE !!! DO NOT TRY TO CHANGE THE ORDER OF THE OR-ing AS IT
+         * WOULD CAUSE INVALID CODE EXECUTION
+         */
+        modified = updateGlobalPort(globalEntry.getPort()) || modified;
+        modified = updateGlobalHostName(globalEntry.getHostName()) || modified;
+        modified = updateGlobalUserName(globalEntry.getUsername()) || modified;
+        modified = updateGlobalIdentities(globalEntry.getIdentities()) || modified;
+
+        Map<String, String> updated = updateGlobalProperties(globalEntry.getProperties());
+        modified = (GenericUtils.size(updated) > 0) || modified;
+
+        return modified;
+    }
+
+    /**
+     * Sets all the properties for which no current value exists in the entry
+     *
+     * @param props The global properties - ignored if {@code null}/empty
+     * @return A {@link Map} of the <U>updated</U> properties
+     */
+    public Map<String, String> updateGlobalProperties(Map<String, String> props) {
+        if (GenericUtils.isEmpty(props)) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, String> updated = null;
+        for (Map.Entry<String, String> pe : props.entrySet()) {
+            String key = pe.getKey();
+            String curValue = getProperty(key);
+            if (GenericUtils.length(curValue) > 0) {
+                continue;
+            }
+
+            String newValue = pe.getValue();
+            setProperty(key, newValue);
+
+            if (updated == null) {
+                updated = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+            }
+
+            updated.put(key, newValue);
+        }
+
+        if (updated == null) {
+            return Collections.emptyMap();
+        } else {
+            return updated;
+        }
+    }
+
+    /**
+     * @param ids Global identities - ignored if {@code null}/empty or already
+     * have configured identities
+     * @return {@code true} if updated identities
+     */
+    public boolean updateGlobalIdentities(Collection<String> ids) {
+        if (GenericUtils.isEmpty(ids) || (GenericUtils.size(getIdentities()) > 0)) {
+            return false;
+        }
+
+        for (String id : ids) {
+            addIdentity(id);
+        }
+
+        return true;
+    }
+
+    /**
+     * @param name The global user name - ignored if {@code null}/empty or
+     * already have a configured user
+     * @return {@code true} if updated the username
+     */
+    public boolean updateGlobalUserName(String user) {
+        if (GenericUtils.isEmpty(user) || (GenericUtils.length(getUsername()) > 0)) {
+            return false;
+        }
+
+        setUsername(user);
+        return true;
+    }
+
+    /**
+     * @param name The global host name - ignored if {@code null}/empty or
+     * already have a configured target host
+     * @return {@code true} if updated the target host
+     */
+    public boolean updateGlobalHostName(String name) {
+        if (GenericUtils.isEmpty(name) || (GenericUtils.length(getHostName()) > 0)) {
+            return false;
+        }
+
+        setHostName(name);
+        return true;
+    }
+
+    /**
+     * @param portValue The global port value - ignored if not positive
+     * or already have a configured port
+     * @return {@code true} if updated the port value
+     */
+    public boolean updateGlobalPort(int portValue) {
+        if ((portValue <= 0) || (getPort() > 0)) {
+            return false;
+        }
+
+        setPort(portValue);
+        return true;
+    }
+
+    /**
+     * @param name Property name - never {@code null}/empty
+     * @param valsList The available values for the property
+     * @param ignoreAlreadyInitialized If {@code false} and one of the &quot;known&quot;
+     * properties is encountered then throws an exception
+     * @throws IllegalArgumentException If an existing value is overwritten and
+     * <tt>ignoreAlreadyInitialized</tt> is {@code false} (except for {@link #IDENTITY_FILE_CONFIG_PROP}
+     * which is <U>cumulative</U>
+     * @see #HOST_NAME_CONFIG_PROP
+     * @see #PORT_CONFIG_PROP
+     * @see #USER_CONFIG_PROP
+     * @see #IDENTITY_FILE_CONFIG_PROP
+     */
+    public void processProperty(String name, Collection<String> valsList, boolean ignoreAlreadyInitialized) {
+        String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name");
+        String joinedValue = GenericUtils.join(valsList, ',');
+        appendPropertyValue(key, joinedValue);
+
+        if (HOST_NAME_CONFIG_PROP.equalsIgnoreCase(key)) {
+            ValidateUtils.checkTrue(GenericUtils.size(valsList) == 1, "Multiple target hosts N/A: %s", joinedValue);
+
+            String curValue = getHostName();
+            ValidateUtils.checkTrue(GenericUtils.isEmpty(curValue) || ignoreAlreadyInitialized, "Already initialized %s: %s", key, curValue);
+            setHostName(joinedValue);
+        } else if (PORT_CONFIG_PROP.equalsIgnoreCase(key)) {
+            ValidateUtils.checkTrue(GenericUtils.size(valsList) == 1, "Multiple target ports N/A: %s", joinedValue);
+
+            int curValue = getPort();
+            ValidateUtils.checkTrue((curValue <= 0) || ignoreAlreadyInitialized, "Already initialized %s: %d", key, curValue);
+
+            int newValue = Integer.parseInt(joinedValue);
+            ValidateUtils.checkTrue(newValue > 0, "Bad new port value: %d", newValue);
+            setPort(newValue);
+        } else if (USER_CONFIG_PROP.equalsIgnoreCase(key)) {
+            ValidateUtils.checkTrue(GenericUtils.size(valsList) == 1, "Multiple target users N/A: %s", joinedValue);
+
+            String curValue = getUsername();
+            ValidateUtils.checkTrue(GenericUtils.isEmpty(curValue) || ignoreAlreadyInitialized, "Already initialized %s: %s", key, curValue);
+            setUsername(joinedValue);
+        } else if (IDENTITY_FILE_CONFIG_PROP.equalsIgnoreCase(key)) {
+            ValidateUtils.checkTrue(GenericUtils.size(valsList) > 0, "No identity files specified");
+            for (String id : valsList) {
+                addIdentity(id);
+            }
+        }
+    }
+
+    /**
+     * Appends a value using a <U>comma</U> to an existing one. If no previous
+     * value then same as calling {@link #setProperty(String, String)}.
+     *
+     * @param name Property name - never {@code null}/empty
+     * @param value The value to be appended - ignored if {@code null}/empty
+     * @return The value <U>before</U> appending - {@code null} if no previous value
+     */
+    public String appendPropertyValue(String name, String value) {
+        String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name");
+        String curVal = getProperty(key);
+        if (GenericUtils.isEmpty(value)) {
+            return curVal;
+        }
+
+        if (GenericUtils.isEmpty(curVal)) {
+            return setProperty(key, value);
+        }
+
+        StringBuilder sb = new StringBuilder(curVal.length() + value.length() + 1);
+        sb.append(curVal).append(',').append(value);
+        return setProperty(key, sb.toString());
+    }
+
+    /**
+     * Sets / Replaces the property value
+     *
+     * @param name Property name - never {@code null}/empty
+     * @param value Property value - if {@code null}/empty then
+     * {@link #removeProperty(String)} is called
+     * @return The previous property value - {@code null} if no such name
+     */
+    public String setProperty(String name, String value) {
+        if (GenericUtils.isEmpty(value)) {
+            return removeProperty(name);
+        }
+
+        String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name");
+        if (GenericUtils.isEmpty(properties)) {
+            properties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        }
+
+        return properties.put(key, value);
+    }
+
+    /**
+     * @param name Property name - never {@code null}/empty
+     * @return The removed property value - {@code null} if no such property name
+     */
+    public String removeProperty(String name) {
+        String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name");
+        Map<String, String> props = getProperties();
+        if (GenericUtils.isEmpty(props)) {
+            return null;
+        } else {
+            return props.remove(key);
+        }
+    }
+
+    /**
+     * @param properties The properties to set - if {@code null} then an empty
+     * map is effectively set. <B>Note:</B> it is highly recommended to use a
+     * <U>case insensitive</U> key mapper.
+     */
+    public void setProperties(Map<String, String> properties) {
+        this.properties = (properties == null) ? Collections.<String, String>emptyMap() : properties;
+    }
+
+    public <A extends Appendable> A append(A sb) throws IOException {
+        sb.append(HOST_CONFIG_PROP).append(' ').append(ValidateUtils.checkNotNullAndNotEmpty(getHost(), "No host pattern")).append(IoUtils.EOL);
+        appendNonEmptyProperty(sb, HOST_NAME_CONFIG_PROP, getHostName());
+        appendNonEmptyPort(sb, PORT_CONFIG_PROP, getPort());
+        appendNonEmptyProperty(sb, USER_CONFIG_PROP, getUsername());
+        appendNonEmptyValues(sb, IDENTITY_FILE_CONFIG_PROP, getIdentities());
+        appendNonEmptyProperties(sb, getProperties());
+        return sb;
+    }
+
+    @Override
+    public HostConfigEntry clone() {
+        try {
+            HostConfigEntry other = getClass().cast(super.clone());
+
+            // avoid shared instances
+            other.setIdentities(null);
+            Collection<String> ids = this.getIdentities();
+            if (GenericUtils.size(ids) > 0) {
+                for (String kp : ids) {
+                    other.addIdentity(kp);
+                }
+            }
+
+            // avoid shared instances
+            other.setProperties(null);
+            Map<String, String> props = this.getProperties();
+            if (GenericUtils.size(props) > 0) {
+                for (Map.Entry<String, String> pe : props.entrySet()) {
+                    other.setProperty(pe.getKey(), pe.getValue());
+                }
+            }
+
+            return other;
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException("Failed (" + e.getClass().getSimpleName() + ") to clone " + toString() + ": " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getHost() + ": " + getUsername() + "@" + getHostName() + ":" + getPort();
+    }
+
+    /**
+     * @param <A> The {@link Appendable} type
+     * @param sb The target appender
+     * @param name The property name - never {@code null}/empty
+     * @param port The port value - ignored if non-positive
+     * @return The target appender after having appended (or not) the value
+     * @throws IOException If failed to append the requested data
+     * @see #appendNonEmptyProperty(Appendable, String, Object)
+     */
+    public static <A extends Appendable> A appendNonEmptyPort(A sb, String name, int port) throws IOException {
+        return appendNonEmptyProperty(sb, name, (port > 0) ? Integer.toString(port) : null);
+    }
+
+    /**
+     * Appends the extra properties - while skipping the {@link #EXPLICIT_PROPERTIES} ones
+     *
+     * @param <A> The {@link Appendable} type
+     * @param sb The target appender
+     * @param props The {@link Map} of properties - ignored if {@code null}/empty
+     * @return The target appender after having appended (or not) the value
+     * @throws IOException If failed to append the requested data
+     * @see #appendNonEmptyProperty(Appendable, String, Object)
+     */
+    public static <A extends Appendable> A appendNonEmptyProperties(A sb, Map<String, ?> props) throws IOException {
+        if (GenericUtils.isEmpty(props)) {
+            return sb;
+        }
+
+        for (Map.Entry<String, ?> pe : props.entrySet()) {
+            String name = pe.getKey();
+            if (EXPLICIT_PROPERTIES.contains(name)) {
+                continue;
+            }
+
+            appendNonEmptyProperty(sb, name, pe.getValue());
+        }
+
+        return sb;
+    }
+
+    /**
+     * @param <A> The {@link Appendable} type
+     * @param sb The target appender
+     * @param name The property name - never {@code null}/empty
+     * @param value The property value - ignored if {@code null}. <B>Note:</B>
+     * if the string representation of the value contains any commas, they are
+     * assumed to indicate a multi-valued property which is broken down to
+     * <U>individual</U> lines - one per value.
+     * @return The target appender after having appended (or not) the value
+     * @throws IOException If failed to append the requested data
+     * @see #appendNonEmptyValues(Appendable, String, Object...)
+     */
+    public static <A extends Appendable> A appendNonEmptyProperty(A sb, String name, Object value) throws IOException {
+        String s = Objects.toString(value, null);
+        String[] vals = GenericUtils.split(s, ',');
+        return appendNonEmptyValues(sb, name, (Object[]) vals);
+    }
+
+    /**
+     * @param <A> The {@link Appendable} type
+     * @param sb The target appender
+     * @param name The property name - never {@code null}/empty
+     * @param values The values to be added - one per line - ignored if {@code null}/empty
+     * @return The target appender after having appended (or not) the value
+     * @throws IOException If failed to append the requested data
+     * @see #appendNonEmptyValues(Appendable, String, Collection)
+     */
+    public static <A extends Appendable> A appendNonEmptyValues(A sb, String name, Object ... values) throws IOException {
+        return appendNonEmptyValues(sb, name, GenericUtils.isEmpty(values) ? Collections.<Object>emptyList() : Arrays.asList(values));
+    }
+
+    /**
+     * @param <A> The {@link Appendable} type
+     * @param sb The target appender
+     * @param name The property name - never {@code null}/empty
+     * @param values The values to be added - one per line - ignored if {@code null}/empty
+     * @return The target appender after having appended (or not) the value
+     * @throws IOException If failed to append the requested data
+     */
+    public static <A extends Appendable> A appendNonEmptyValues(A sb, String name, Collection<?> values) throws IOException {
+        String k = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name");
+        if (GenericUtils.isEmpty(values)) {
+            return sb;
+        }
+
+        for (Object v : values) {
+            sb.append("    ").append(k).append(' ').append(Objects.toString(v)).append(IoUtils.EOL);
+        }
+
+        return sb;
+    }
+
+    /**
+     * @param entries The entries - ignored if {@code null}/empty
+     * @return A {@link HostConfigEntryResolver} wrapper using the entries
+     */
+    public static HostConfigEntryResolver toHostConfigEntryResolver(final Collection<? extends HostConfigEntry> entries) {
+        if (GenericUtils.isEmpty(entries)) {
+            return HostConfigEntryResolver.EMPTY;
+        } else {
+            return new HostConfigEntryResolver() {
+                @Override
+                public HostConfigEntry resolveEffectiveHost(String host, int port, String username) throws IOException {
+                    List<HostConfigEntry> matches = findMatchingEntries(host, entries);
+                    int numMatches = GenericUtils.size(matches);
+                    if (numMatches <= 0) {
+                        return null;
+                    }
+
+                    HostConfigEntry match = (numMatches == 1) ? matches.get(0) : findBestMatch(matches);
+                    if (match == null) {
+                        ValidateUtils.throwIllegalArgumentException("No best match found for %s@%s:%d out of %d matches", username, host, port, numMatches);
+                    }
+
+                    return normalizeEntry(match, host, port, username);
+                }
+            };
+        }
+    }
+
+    /**
+     * @param entry The original entry - ignored if {@code null}
+     * @param host The original host name / address
+     * @param port The original port
+     * @param username The original user name
+     * @return A <U>cloned</U> entry whose values are resolved - including
+     * expanding macros in the identities files
+     * @throws IOException If failed to normalize the entry
+     * @see #resolveHostName(String)
+     * @see #resolvePort(int)
+     * @see #resolveUsername(String)
+     * @see #resolveIdentityFilePath(String, String, int, String)
+     */
+    public static HostConfigEntry normalizeEntry(HostConfigEntry entry, String host, int port, String username) throws IOException {
+        if (entry == null) {
+            return entry;
+        }
+
+        HostConfigEntry normal = entry.clone();
+        normal.setHostName(entry.resolveHostName(host));
+        normal.setPort(entry.resolvePort(port));
+        normal.setUsername(entry.resolveUsername(username));
+
+        Collection<String> ids = entry.getIdentities();
+        if (GenericUtils.isEmpty(ids)) {
+            return normal;
+        }
+
+        normal.setIdentities(Collections.<String>emptyList());  // start fresh
+        for (String id : ids) {
+            String path = resolveIdentityFilePath(id, host, port, username);
+            normal.addIdentity(path);
+        }
+
+        return normal;
+    }
+
+    /**
+     * Finds the best match out of the given ones.
+     *
+     * @param matches The available matches - ignored if {@code null}/empty
+     * @return The best match or {@code null} if no matches or no best match found
+     * @see #findBestMatch(Iterator)
+     */
+    public static HostConfigEntry findBestMatch(Collection<? extends HostConfigEntry> matches) {
+        if (GenericUtils.isEmpty(matches)) {
+            return null;
+        } else {
+            return findBestMatch(matches.iterator());
+        }
+    }
+
+    /**
+     * Finds the best match out of the given ones.
+     *
+     * @param matches The available matches - ignored if {@code null}/empty
+     * @return The best match or {@code null} if no matches or no best match found
+     * @see #findBestMatch(Iterator)
+     */
+    public static HostConfigEntry findBestMatch(Iterable<? extends HostConfigEntry> matches) {
+        if (matches == null) {
+            return null;
+        } else {
+            return findBestMatch(matches.iterator());
+        }
+    }
+
+    /**
+     * Finds the best match out of the given ones. The best match is defined as one whose
+     * pattern is as <U>specific</U> as possible (if more than one match is available).
+     * I.e., a non-global match is preferred over global one, and a match with no wildcards
+     * is preferred over one with such a pattern.
+     *
+     * @param matches The available matches - ignored if {@code null}/empty
+     * @return The best match or {@code null} if no matches or no best match found
+     * @see #isSpecificHostPattern(String)
+     */
+    public static HostConfigEntry findBestMatch(Iterator<? extends HostConfigEntry> matches) {
+        if ((matches == null) || (!matches.hasNext())) {
+            return null;
+        }
+
+        HostConfigEntry candidate = matches.next();
+        int wildcardMatches = 0;
+        while (matches.hasNext()) {
+            HostConfigEntry entry = matches.next();
+            String entryPattern = entry.getHost();
+            String candidatePattern = candidate.getHost();
+            // prefer non-global entry over global entry
+            if (ALL_HOSTS_PATTERN.equalsIgnoreCase(candidatePattern)) {
+                // unlikely, but handle it
+                if (ALL_HOSTS_PATTERN.equalsIgnoreCase(entryPattern)) {
+                    wildcardMatches++;
+                } else {
+                    candidate = entry;
+                    wildcardMatches = 0;
+                }
+                continue;
+            }
+
+            if (isSpecificHostPattern(entryPattern)) {
+                // if both are specific then no best match
+                if (isSpecificHostPattern(candidatePattern)) {
+                    return null;
+                }
+
+                candidate = entry;
+                wildcardMatches = 0;
+                continue;
+            }
+
+            wildcardMatches++;
+        }
+
+        String candidatePattern = candidate.getHost();
+        // best match either has specific host or no wildcard matches
+        if ((wildcardMatches <= 0) || (isSpecificHostPattern(candidatePattern))) {
+            return candidate;
+        }
+
+        return null;
+    }
+
+    /**
+     * @param pattern The pattern to check - ignored if {@code null}/empty
+     * @return {@code true} if the pattern is not empty and contains no wildcard characters
+     * @see #WILDCARD_PATTERN
+     * @see #SINGLE_CHAR_PATTERN
+     */
+    public static boolean isSpecificHostPattern(String pattern) {
+        if (GenericUtils.isEmpty(pattern)) {
+            return false;
+        }
+
+        for (int index = 0; index < PATTERN_CHARS.length(); index++) {
+            char ch = PATTERN_CHARS.charAt(index);
+            if (pattern.indexOf(ch) >= 0) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Locates all the matching entries for a give host name / address
+     *
+     * @param host The host name / address - ignored if {@code null}/empty
+     * @param entries The {@link HostConfigEntry}-ies to scan - ignored if {@code null}/empty
+     * @return A {@link List} of all the matching entries
+     * @see #isHostMatch(String)
+     */
+    public static List<HostConfigEntry> findMatchingEntries(String host, HostConfigEntry ... entries) {
+        // TODO in Java-8 use Stream(s) + predicate
+        if (GenericUtils.isEmpty(host) || GenericUtils.isEmpty(entries)) {
+            return Collections.emptyList();
+        } else {
+            return findMatchingEntries(host, Arrays.asList(entries));
+        }
+    }
+
+    /**
+     * Locates all the matching entries for a give host name / address
+     *
+     * @param host The host name / address - ignored if {@code null}/empty
+     * @param entries The {@link HostConfigEntry}-ies to scan - ignored if {@code null}/empty
+     * @return A {@link List} of all the matching entries
+     * @see #isHostMatch(String)
+     */
+    public static List<HostConfigEntry> findMatchingEntries(String host, Collection<? extends HostConfigEntry> entries) {
+        // TODO in Java-8 use Stream(s) + predicate
+        if (GenericUtils.isEmpty(host) || GenericUtils.isEmpty(entries)) {
+            return Collections.emptyList();
+        }
+
+        List<HostConfigEntry> matches = null;
+        for (HostConfigEntry entry : entries) {
+            if (!entry.isHostMatch(host)) {
+                continue;   // debug breakpoint
+            }
+
+            if (matches == null) {
+                matches = new ArrayList<>(entries.size());  // in case ALL of them match
+            }
+
+            matches.add(entry);
+        }
+
+        if (matches == null) {
+            return Collections.emptyList();
+        } else {
+            return matches;
+        }
+    }
+
+    /**
+     * Resolves the effective target host
+     *
+     * @param originalName The original requested host
+     * @param entryName The configured host
+     * @return If the configured host entry is not {@code null}/empty
+     * then it is used, otherwise the original one.
+     */
+    public static String resolveHostName(String originalName, String entryName) {
+        if (GenericUtils.isEmpty(entryName)) {
+            return originalName;
+        } else {
+            return entryName;
+        }
+    }
+
+    /**
+     * Resolves the effective username
+     *
+     * @param originalUser The original requested username
+     * @param entryUser The configured host entry username
+     * @return If the configured host entry username is not {@code null}/empty
+     * then it is used, otherwise the original one.
+     */
+    public static String resolveUsername(String originalUser, String entryUser) {
+        if (GenericUtils.isEmpty(entryUser)) {
+            return originalUser;
+        } else {
+            return entryUser;
+        }
+    }
+
+    /**
+     * Resolves the effective port to use
+     *
+     * @param originalPort The original requested port
+     * @param entryPort The configured host entry port
+     * @return If the host entry port is positive, then it is used, otherwise
+     * the original requested port
+     */
+    public static int resolvePort(int originalPort, int entryPort) {
+        if (entryPort <= 0) {
+            return originalPort;
+        } else {
+            return entryPort;
+        }
+    }
+
+    /**
+     * Checks if a given host name / address matches a host pattern
+     *
+     * @param host The host name / address - ignored if {@code null}/empty
+     * @param pattern The host pattern string - ignored if {@code null}/empty
+     * @return {@code true} if the name / address matches the pattern
+     * @see #toPattern(String)
+     * @see #isHostMatch(String, Pattern)
+     */
+    public static boolean isHostMatch(String host, CharSequence pattern) {
+        return isHostMatch(host, toPattern(pattern));
+    }
+
+    /**
+     * Checks if a given host name / address matches a host pattern
+     *
+     * @param host The host name / address - ignored if {@code null}/empty
+     * @param pattern The host {@link Pattern} - ignored if {@code null}
+     * @return {@code true} if the name / address matches the pattern
+     */
+    public static boolean isHostMatch(String host, Pattern pattern) {
+        if (GenericUtils.isEmpty(host) || (pattern == null)) {
+            return false;
+        }
+
+        Matcher m = pattern.matcher(host);
+        return m.matches();
+    }
+
+    /**
+     * Converts a host pattern string to a regular expression matcher.
+     * <B>Note:</B> pattern matching is <U>case insensitive</U>
+     *
+     * @param pattern The original pattern string - ignored if {@code null}/empty
+     * @return The regular expression matcher {@link Pattern} - {@code null}
+     * if empty original string
+     * @see #WILDCARD_PATTERN
+     * @see #SINGLE_CHAR_PATTERN
+     */
+    public static Pattern toPattern(CharSequence pattern) {
+        if (GenericUtils.isEmpty(pattern)) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder(pattern.length());
+        for (int curPos = 0; curPos < pattern.length(); curPos++) {
+            char ch = pattern.charAt(curPos);
+            ValidateUtils.checkTrue(isValidPatternChar(ch), "Invalid host pattern char in %s", pattern);
+
+            switch(ch) {
+                case '.':   // need to escape it
+                    sb.append('\\').append(ch);
+                    break;
+                case SINGLE_CHAR_PATTERN:
+                    sb.append('.');
+                    break;
+                case WILDCARD_PATTERN:
+                    sb.append(".*");
+                    break;
+                default:
+                    sb.append(ch);
+            }
+        }
+
+        return Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE);
+    }
+
+    /**
+     * Checks if the given character is valid for a host pattern. Valid
+     * characters are:
+     * <UL>
+     *      <LI>A-Z</LI>
+     *      <LI>a-z</LI>
+     *      <LI>0-9</LI>
+     *      <LI>Underscore (_)</LI>
+     *      <LI>Hyphen (-)</LI>
+     *      <LI>Dot (.)</LI>
+     *      <LI>The {@link #WILDCARD_PATTERN}</LI>
+     *      <LI>The {@link #SINGLE_CHAR_PATTERN}</LI>
+     * </UL>
+     *
+     * @param ch The character to validate
+     * @return {@code true} if valid pattern character
+     */
+    public static boolean isValidPatternChar(char ch) {
+        if ((ch <= ' ') || (ch >= 0x7E)) {
+            return false;
+        }
+        if ((ch >= 'a') && (ch <= 'z')) {
+            return true;
+        }
+        if ((ch >= 'A') && (ch <= 'Z')) {
+            return true;
+        }
+        if ((ch >= '0') && (ch <= '9')) {
+            return true;
+        }
+        if ("-_.".indexOf(ch) >= 0) {
+            return true;
+        }
+        if ((ch == SINGLE_CHAR_PATTERN) || (ch == WILDCARD_PATTERN)) {
+            return true;
+        }
+        return false;
+    }
+
+    public static List<HostConfigEntry> readHostConfigEntries(File file) throws IOException {
+        return readHostConfigEntries(file.toPath(), IoUtils.EMPTY_OPEN_OPTIONS);
+    }
+
+    public static List<HostConfigEntry> readHostConfigEntries(Path path, OpenOption... options) throws IOException {
+        try (InputStream input = Files.newInputStream(path, options)) {
+            return readHostConfigEntries(input, true);
+        }
+    }
+
+    public static List<HostConfigEntry> readHostConfigEntries(URL url) throws IOException {
+        try (InputStream input = url.openStream()) {
+            return readHostConfigEntries(input, true);
+        }
+    }
+
+    public static List<HostConfigEntry> readHostConfigEntries(String filePath) throws IOException {
+        try (InputStream inStream = new FileInputStream(filePath)) {
+            return readHostConfigEntries(inStream, true);
+        }
+    }
+
+    public static List<HostConfigEntry> readHostConfigEntries(InputStream inStream, boolean okToClose) throws IOException {
+        try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(inStream, okToClose), StandardCharsets.UTF_8)) {
+            return readHostConfigEntries(reader, true);
+        }
+    }
+
+    public static List<HostConfigEntry> readHostConfigEntries(Reader rdr, boolean okToClose) throws IOException {
+        try (BufferedReader buf = new BufferedReader(NoCloseReader.resolveReader(rdr, okToClose))) {
+            return readHostConfigEntries(buf);
+        }
+    }
+
+    /**
+     * Reads configuration entries
+     *
+     * @param rdr The {@link BufferedReader} to use
+     * @return The {@link List} of read {@link HostConfigEntry}-ies
+     * @throws IOException If failed to parse the read configuration
+     */
+    public static List<HostConfigEntry> readHostConfigEntries(BufferedReader rdr) throws IOException {
+        HostConfigEntry curEntry = null;
+        HostConfigEntry globalEntry = null;
+        List<HostConfigEntry> entries = null;
+        List<String> multiHosts = Collections.emptyList();
+
+        int lineNumber = 1;
+        for (String line = rdr.readLine(); line != null; line = rdr.readLine(), lineNumber++) {
+            line = GenericUtils.trimToEmpty(line);
+            if (GenericUtils.isEmpty(line)) {
+                continue;
+            }
+
+            int pos = line.indexOf(SshConfigFileReader.COMMENT_CHAR);
+            if (pos == 0) {
+                continue;
+            }
+
+            if (pos > 0) {
+                line = line.substring(0, pos);
+                line = line.trim();
+            }
+
+            /*
+             * Some options use '=', others use ' ' - try both
+             * NOTE: we do not validate the format for each option separately
+             */
+            pos = line.indexOf(' ');
+            if (pos < 0) {
+                pos = line.indexOf('=');
+            }
+
+            if (pos < 0) {
+                throw new StreamCorruptedException("No configuration value delimiter at line " + lineNumber + ": " + line);
+            }
+
+            String key = line.substring(0, pos);
+            String value = line.substring(pos + 1);
+            List<String> valsList = parseConfigValue(value);
+
+            if (HOST_CONFIG_PROP.equalsIgnoreCase(key)) {
+                if (GenericUtils.isEmpty(valsList)) {
+                    throw new StreamCorruptedException("Missing host pattern(s) at line " + lineNumber + ": " + line);
+                }
+
+                // If the all-hosts pattern is used, make sure no global section already active
+                for (String name : valsList) {
+                    if (ALL_HOSTS_PATTERN.equalsIgnoreCase(name) && (globalEntry != null)) {
+                        throw new StreamCorruptedException("Overriding the global section with a specific one at line " + lineNumber + ": " + line);
+                    }
+                }
+
+                if (curEntry != null) {
+                    curEntry.processGlobalValues(globalEntry);
+                }
+
+                entries = updateEntriesList(entries, curEntry, multiHosts);
+
+                curEntry = new HostConfigEntry();
+                curEntry.setHost(valsList.get(0));
+                // allow for multiple hosts patterns
+                if (valsList.size() > 1) {
+                    multiHosts = valsList.subList(1, valsList.size());
+                } else {
+                    multiHosts = Collections.emptyList();
+                }
+            } else if (curEntry == null) {
+                // if 1st encountered property is NOT for a specific host, then configuration applies to ALL
+                curEntry = new HostConfigEntry();
+                curEntry.setHost(ALL_HOSTS_PATTERN);
+                multiHosts = Collections.emptyList();
+                globalEntry = curEntry;
+            }
+
+            try {
+                curEntry.processProperty(key, valsList, false);
+            } catch (RuntimeException e) {
+                throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ")"
+                                                 + " to process line #" + lineNumber + " (" + line + ")"
+                                                 + ": " + e.getMessage());
+            }
+        }
+
+        if (curEntry != null) {
+            curEntry.processGlobalValues(globalEntry);
+        }
+
+        entries = updateEntriesList(entries, curEntry, multiHosts);
+        if (entries == null) {
+            return Collections.emptyList();
+        } else {
+            return entries;
+        }
+    }
+
+    public static void writeHostConfigEntries(File file, Collection<? extends HostConfigEntry> entries) throws IOException {
+        writeHostConfigEntries(ValidateUtils.checkNotNull(file, "No file").toPath(), entries, IoUtils.EMPTY_OPEN_OPTIONS);
+    }
+
+    public static void writeHostConfigEntries(Path path, Collection<? extends HostConfigEntry> entries, OpenOption ... options) throws IOException {
+        try (OutputStream outputStream = Files.newOutputStream(path, options)) {
+            writeHostConfigEntries(outputStream, true, entries);
+        }
+    }
+
+    public static void writeHostConfigEntries(OutputStream outputStream, boolean okToClose, Collection<? extends HostConfigEntry> entries) throws IOException {
+        if (GenericUtils.isEmpty(entries)) {
+            return;
+        }
+
+        try (Writer w = new OutputStreamWriter(NoCloseOutputStream.resolveOutputStream(outputStream, okToClose), StandardCharsets.UTF_8)) {
+            appendHostConfigEntries(w, entries);
+        }
+    }
+
+    public static <A extends Appendable> A appendHostConfigEntries(A sb, Collection<? extends HostConfigEntry> entries) throws IOException {
+        if (GenericUtils.isEmpty(entries)) {
+            return sb;
+        }
+
+        for (HostConfigEntry entry : entries) {
+            entry.append(sb);
+        }
+
+        return sb;
+    }
+
+    /**
+     * @param entries The original list - if {@code null} one will be allocated if necessary
+     * @param curEntry The current entry - ignored if {@code null}
+     * @param hosts The extra host patterns to be attached to the entry - ignored
+     * if {@code null}/empty
+     * @return The updated list of entries - same as input if nothing added
+     * @see #duplicateConfiguration(HostConfigEntry, Collection)
+     */
+    public static List<HostConfigEntry> updateEntriesList(List<HostConfigEntry> entries, HostConfigEntry curEntry, Collection<String> hosts) {
+        if (curEntry == null) {
+            return entries;
+        }
+
+        // TODO consider allowing multiple patterns for same entry instead of duplicating the entries
+        Collection<HostConfigEntry> dups = duplicateConfiguration(curEntry, hosts);
+        int extraHosts = Math.max(0, GenericUtils.size(dups));
+        if (entries == null) {
+            entries = new ArrayList<>(Math.max(1 + extraHosts, Byte.SIZE));
+        }
+
+        entries.add(curEntry);
+
+        if (extraHosts > 0) {
+            entries.addAll(dups);
+        }
+
+        return entries;
+    }
+
+    /**
+     * @param entry The original entry to duplicate - ignored if {@code null}
+     * @param hosts The hosts patterns to attach to each duplicated entry -
+     * ignored if {@code null}/empty
+     * @return A {@link List} of duplicated entries where each new entry host
+     * pattern is updated from the given one
+     */
+    public static List<HostConfigEntry> duplicateConfiguration(HostConfigEntry entry, Collection<String> hosts) {
+        if ((entry == null) || GenericUtils.isEmpty(hosts)) {
+            return Collections.emptyList();
+        }
+
+        List<HostConfigEntry> dups = new ArrayList<>(hosts.size());
+        for (String pattern : hosts) {
+            HostConfigEntry cloned = entry.clone();
+            cloned.setHost(pattern);
+            dups.add(cloned);
+        }
+
+        return dups;
+    }
+
+    /**
+     * Checks if this is a multi-value - allow space and comma
+     *
+     * @param value The value - ignored if {@code null}/empty (after trimming)
+     * @return A {@link List} of the encountered values
+     */
+    public static List<String> parseConfigValue(String value) {
+        String s = GenericUtils.trimToEmpty(value);
+        if (GenericUtils.isEmpty(s)) {
+            return Collections.emptyList();
+        }
+
+        for (int index = 0; index < MULTI_VALUE_SEPARATORS.length(); index++) {
+            char sep = MULTI_VALUE_SEPARATORS.charAt(index);
+            int pos = s.indexOf(sep);
+            if (pos >= 0) {
+                String[] vals = GenericUtils.split(s, sep);
+                if (GenericUtils.isEmpty(vals)) {
+                    return Collections.emptyList();
+                } else {
+                    return Arrays.asList(vals);
+                }
+            }
+        }
+
+        // this point is reached if no separators found
+        return Collections.singletonList(s);
+    }
+
+    // The file name may use the tilde syntax to refer to a user’s home directory or one of the following escape characters:
+    // '%d' (local user's home directory), '%u' (local user name), '%l' (local host name), '%h' (remote host name) or '%r' (remote user name).
+    public static String resolveIdentityFilePath(String id, String host, int port, String username) throws IOException {
+        if (GenericUtils.isEmpty(id)) {
+            return id;
+        }
+
+        String path = id.replace('/', File.separatorChar);  // make sure all separators are local
+        String[] elements = GenericUtils.split(path, File.separatorChar);
+        StringBuilder sb = new StringBuilder(path.length() + Long.SIZE);
+        for (int index = 0; index < elements.length; index++) {
+            String elem = elements[index];
+            if (index > 0) {
+                sb.append(File.separatorChar);
+            }
+
+            for (int curPos = 0; curPos < elem.length(); curPos++) {
+                char ch = elem.charAt(curPos);
+                if (ch == HOME_TILDE_CHAR) {
+                    ValidateUtils.checkTrue((curPos == 0) && (index == 0), "Home tilde must be first: %s", id);
+                    appendUserHome(sb);
+                } else if (ch == PATH_MACRO_CHAR) {
+                    curPos++;
+                    ValidateUtils.checkTrue(curPos < elem.length(), "Missing macro modifier in %s", id);
+                    ch = elem.charAt(curPos);
+                    switch(ch) {
+                        case PATH_MACRO_CHAR:
+                            sb.append(ch);
+                            break;
+                        case LOCAL_HOME_MACRO:
+                            ValidateUtils.checkTrue((curPos == 1) && (index == 0), "Home macro must be first: %s", id);
+                            appendUserHome(sb);
+                            break;
+                        case LOCAL_USER_MACRO:
+                            sb.append(ValidateUtils.checkNotNullAndNotEmpty(System.getProperty("user.name"), "No local user name value"));
+                            break;
+                        case LOCAL_HOST_MACRO:
+                        {
+                            InetAddress address = ValidateUtils.checkNotNull(InetAddress.getLocalHost(), "No local address");
+                            sb.append(ValidateUtils.checkNotNullAndNotEmpty(address.getHostName(), "No local name"));
+                            break;
+                        }
+                        case REMOTE_HOST_MACRO:
+                            sb.append(ValidateUtils.checkNotNullAndNotEmpty(host, "No remote host provided"));
+                            break;
+                        case REMOTE_USER_MACRO:
+                            sb.append(ValidateUtils.checkNotNullAndNotEmpty(username, "No remote user provided"));
+                            break;
+                        case REMOTE_PORT_MACRO:
+                            ValidateUtils.checkTrue(port > 0, "Bad remote port value: %d", port);
+                            sb.append(port);
+                            break;
+                        default:
+                            ValidateUtils.throwIllegalArgumentException("Bad modifier '%s' in %s", String.valueOf(ch), id);
+                    }
+                } else {
+                    sb.append(ch);
+                }
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public static StringBuilder appendUserHome(StringBuilder sb) {
+        return appendUserHome(sb, ValidateUtils.checkNotNullAndNotEmpty(System.getProperty("user.home"), "No user home property"));
+    }
+
+    public static StringBuilder appendUserHome(StringBuilder sb, String userHome) {
+        if (GenericUtils.isEmpty(userHome)) {
+            return sb;
+        }
+
+        sb.append(userHome);
+        // strip any ending separator since we add our own
+        int len = sb.length();
+        if (sb.charAt(len - 1) == File.separatorChar) {
+            sb.setLength(len - 1);
+        }
+
+        return sb;
+    }
+
+    /**
+     * @return The default {@link File} location of the OpenSSH authorized keys file
+     */
+    @SuppressWarnings("synthetic-access")
+    public static File getDefaultHostConfigFile() {
+        return LazyDefaultConfigFileHolder.KEYS_FILE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java
new file mode 100644
index 0000000..596c59d
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java
@@ -0,0 +1,59 @@
+/*
+ * 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.client.config.hosts;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface HostConfigEntryResolver {
+
+    /**
+     * An &quot;empty&quot; implementation that does not resolve any entry
+     */
+    HostConfigEntryResolver EMPTY = new HostConfigEntryResolver() {
+        @Override
+        public HostConfigEntry resolveEffectiveHost(String host, int port, String username) throws IOException {
+            return null;
+        }
+
+        @Override
+        public String toString() {
+            return "EMPTY";
+        }
+    };
+
+    /**
+     * Invoked when creating a new client session in order to allow for overriding
+     * of the original parameters
+     *
+     * @param host The requested host - never {@code null}/empty
+     * @param port The requested port
+     * @param username The requested username
+     * @return A {@link HostConfigEntry} for the actual target - {@code null} if use
+     * original parameters. <B>Note:</B> if any identity files are attached to the
+     * configuration then they must point to <U>existing</U> locations. This means
+     * that any macros such as <code>~, %d, %h</code>, etc. must be resolved <U>prior</U>
+     * to returning the value
+     * @throws IOException If failed to resolve the configuration
+     */
+    HostConfigEntry resolveEffectiveHost(String host, int port, String username) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java b/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
new file mode 100644
index 0000000..cb11e10
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
@@ -0,0 +1,99 @@
+/*
+ * 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.client.config.keys;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ClientIdentityLoader {
+    /**
+     * <P>A default implementation that assumes a file location that <U>must</U> exist.</P>
+     *
+     * <P>
+     * <B>Note:</B> It calls {@link SecurityUtils#loadKeyPairIdentity(String, InputStream, FilePasswordProvider)}
+     * which fails if the {@code Bouncycastle} provider is not registered, therefore the
+     * default {@link #isValidLocation(String)} implementation also checks if
+     * {@link SecurityUtils#isBouncyCastleRegistered()}
+     * </P>
+     */
+    ClientIdentityLoader DEFAULT = new ClientIdentityLoader() {
+        @Override
+        public boolean isValidLocation(String location) throws IOException {
+            if (!SecurityUtils.isBouncyCastleRegistered()) {
+                return false;
+            }
+            Path path = toPath(location);
+            return Files.exists(path, IoUtils.EMPTY_LINK_OPTIONS);
+        }
+
+        @Override
+        public KeyPair loadClientIdentity(String location, FilePasswordProvider provider) throws IOException, GeneralSecurityException {
+            Path path = toPath(location);
+            try (InputStream inputStream = Files.newInputStream(path, IoUtils.EMPTY_OPEN_OPTIONS)) {
+                return SecurityUtils.loadKeyPairIdentity(path.toString(), inputStream, provider);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "DEFAULT";
+        }
+
+        private Path toPath(String location) {
+            Path path = new File(ValidateUtils.checkNotNullAndNotEmpty(location, "No location")).toPath();
+            return path.toAbsolutePath().normalize();
+        }
+    };
+
+    /**
+     * @param location The identity key-pair location - the actual meaning (file, URL, etc.)
+     * depends on the implementation.
+     * @return {@code true} if it represents a valid location - the actual meaning of
+     * the validity depends on the implementation
+     * @throws IOException If failed to validate the location
+     */
+    boolean isValidLocation(String location) throws IOException;
+
+    /**
+     * @param location The identity key-pair location - the actual meaning (file, URL, etc.)
+     * depends on the implementation.
+     * @param provider The {@link FilePasswordProvider} to consult if the location contains
+     * an encrypted identity
+     * @return The loaded {@link KeyPair} - {@code null} if location is empty
+     * and it is OK that it does not exist
+     * @throws IOException If failed to access / process the remote location
+     * @throws GeneralSecurityException If failed to convert the contents into
+     * a valid identity
+     */
+    KeyPair loadClientIdentity(String location, FilePasswordProvider provider) throws IOException, GeneralSecurityException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java
index 95a1496..1af1fd8 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java
@@ -26,6 +26,21 @@ import java.io.IOException;
  */
 public interface FilePasswordProvider {
     /**
+     * An &quot;empty&quot; provider that returns {@code null} - i.e., unprotected key file
+     */
+    FilePasswordProvider EMPTY = new FilePasswordProvider() {
+        @Override
+        public String getPassword(String resourceKey) throws IOException {
+            return null;
+        }
+
+        @Override
+        public String toString() {
+            return "EMPTY";
+        }
+    };
+
+    /**
      * @param resourceKey The resource key representing the <U>private</U>
      *                    file
      * @return The password - if {@code null}/empty then no password is required

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index 87631d7..c82dabf 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -286,6 +286,18 @@ public final class GenericUtils {
      * @return A {@link SortedSet} containing the values (if any) sorted
      * using the provided comparator
      */
+    @SafeVarargs
+    public static <V> SortedSet<V> asSortedSet(Comparator<? super V> comp, V ... values) {
+        return asSortedSet(comp, isEmpty(values) ? Collections.<V>emptyList() : Arrays.asList(values));
+    }
+
+    /**
+     * @param <V>    The element type
+     * @param comp   The (non-{@code null}) {@link Comparator} to use
+     * @param values The values to be added (ignored if {@code null}/empty)
+     * @return A {@link SortedSet} containing the values (if any) sorted
+     * using the provided comparator
+     */
     public static <V> SortedSet<V> asSortedSet(Comparator<? super V> comp, Collection<? extends V> values) {
         // TODO for JDK-8 return Collections.emptySortedSet()
         SortedSet<V> set = new TreeSet<V>(ValidateUtils.checkNotNull(comp, "No comparator"));

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
index df9a0a3..d049cad 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
@@ -54,8 +54,16 @@ public final class IoUtils {
 
     public static final List<String> WINDOWS_EXECUTABLE_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(".bat", ".exe", ".cmd"));
 
+    /**
+     * Size of preferred work buffer when reading / writing data to / from streams
+     */
     public static final int DEFAULT_COPY_SIZE = 8192;
 
+    /**
+     * The local O/S line separator
+     */
+    public static final String EOL = System.getProperty("line.separator");
+
     private static final LinkOption[] NO_FOLLOW_OPTIONS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
 
     /**
@@ -378,4 +386,30 @@ public final class IoUtils {
         return true;
     }
 
+    public static String appendPathComponent(String prefix, String component) {
+        if (GenericUtils.isEmpty(prefix)) {
+            return component;
+        }
+
+        if (GenericUtils.isEmpty(component)) {
+            return prefix;
+        }
+
+        StringBuilder sb = new StringBuilder(prefix.length() + component.length() + File.separator.length()).append(prefix);
+
+        if (sb.charAt(prefix.length() - 1) == File.separatorChar) {
+            if (component.charAt(0) == File.separatorChar) {
+                sb.append(component.substring(1));
+            } else {
+                sb.append(component);
+            }
+        } else {
+            if (component.charAt(0) != File.separatorChar) {
+                sb.append(File.separatorChar);
+            }
+            sb.append(component);
+        }
+
+        return sb.toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
index 1b2c0dd..52d00a6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
@@ -158,7 +158,7 @@ public class ModifiableFileWatcher extends AbstractLoggingBean {
             long size = size();
             FileTime modifiedTime = lastModified();
 
-            if (size >= 0L && modifiedTime != null) {
+            if ((size >= 0L) && (modifiedTime != null)) {
                 lastExisted.set(true);
                 lastSize.set(size);
                 lastModified.set(modifiedTime.toMillis());

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/server/command/UnknownCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/UnknownCommand.java b/sshd-core/src/main/java/org/apache/sshd/server/command/UnknownCommand.java
deleted file mode 100644
index 75f4b1f..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/UnknownCommand.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.command;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.Environment;
-import org.apache.sshd.server.ExitCallback;
-
-/**
- * Implementation of an unknown command that can be returned by <code>CommandFactory</code>
- * when the command is not known, as it is supposed to always
- * return a valid <code>Command</code> object.
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class UnknownCommand implements Command {
-
-    private String command;
-    private InputStream in;
-    private OutputStream out;
-    private OutputStream err;
-    private ExitCallback callback;
-
-    public UnknownCommand(String command) {
-        this.command = command;
-    }
-
-    @Override
-    public void setInputStream(InputStream in) {
-        this.in = in;
-    }
-
-    @Override
-    public void setOutputStream(OutputStream out) {
-        this.out = out;
-    }
-
-    @Override
-    public void setErrorStream(OutputStream err) {
-        this.err = err;
-    }
-
-    @Override
-    public void setExitCallback(ExitCallback callback) {
-        this.callback = callback;
-    }
-
-    @Override
-    public void start(Environment env) throws IOException {
-        err.write(("Unknown command: " + command + "\n").getBytes(StandardCharsets.UTF_8));
-        err.flush();
-        if (callback != null) {
-            callback.onExit(1, "Unknown command: " + command);
-        }
-    }
-
-    @Override
-    public void destroy() {
-        // ignored
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
index 052b05c..e6347ee 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
@@ -68,6 +68,10 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
 
     private static final long serialVersionUID = -9007505285002809156L;
 
+    private static final class LazyDefaultAuthorizedKeysFileHolder {
+        private static final File KEYS_FILE = new File(PublicKeyEntry.getDefaultKeysFolder(), STD_AUTHORIZED_KEYS_FILENAME);
+    }
+
     private String comment;
     // for options that have no value, "true" is used
     private Map<String, String> loginOptions = Collections.emptyMap();
@@ -163,10 +167,6 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
         return keys;
     }
 
-    private static final class LazyDefaultAuthorizedKeysFileHolder {
-        private static final File KEYS_FILE = new File(PublicKeyEntry.getDefaultKeysFolder(), STD_AUTHORIZED_KEYS_FILENAME);
-    }
-
     /**
      * @return The default {@link File} location of the OpenSSH authorized keys file
      */

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
index d4e485b..6e6ac22 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
@@ -103,7 +103,7 @@ public class DefaultAuthorizedKeysAuthenticator extends AuthorizedKeysAuthentica
     protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(Path path, String username, ServerSession session) throws IOException {
         if (isStrict()) {
             if (log.isDebugEnabled()) {
-                log.info("reloadAuthorizedKeys(" + username + ")[" + session + "] check permissions of " + path);
+                log.debug("reloadAuthorizedKeys(" + username + ")[" + session + "] check permissions of " + path);
             }
 
             KeyUtils.validateStrictKeyFilePermissions(path);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index 7e15fd9..d47fc74 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -2095,7 +2095,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
 
     protected void appendExtensions(Buffer buffer, String supportedVersions) {
         appendVersionsExtension(buffer, supportedVersions);
-        appendNewlineExtension(buffer, System.getProperty("line.separator"));
+        appendNewlineExtension(buffer, IoUtils.EOL);
         appendVendorIdExtension(buffer, VersionProperties.getVersionProperties());
         appendOpenSSHExtensions(buffer);
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
index 25a2de8..a292909 100644
--- a/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
@@ -37,11 +37,9 @@ import org.apache.sshd.deprecated.UserAuthPassword;
 import org.apache.sshd.deprecated.UserAuthPublicKey;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
 import org.apache.sshd.server.session.ServerSessionImpl;
 import org.apache.sshd.server.session.SessionFactory;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
 import org.junit.Before;
@@ -63,10 +61,7 @@ public class AuthenticationTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-        sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
+        sshd = Utils.setupTestServer();
         FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.WELCOME_BANNER, WELCOME);
         FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, "publickey,password publickey,keyboard-interactive");
         sshd.setSessionFactory(new SessionFactory(sshd) {
@@ -88,7 +83,7 @@ public class AuthenticationTest extends BaseTestSupport {
 
     @Test
     public void testWrongPassword() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
             try (ClientSession s = client.connect("user", "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
                 s.addPasswordIdentity("bad password");
@@ -100,7 +95,7 @@ public class AuthenticationTest extends BaseTestSupport {
 
     @Test
     public void testChangeUser() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.setServiceFactories(Arrays.asList(
                     new ClientUserAuthServiceOld.Factory(),
                     ClientConnectionServiceFactory.INSTANCE
@@ -125,7 +120,7 @@ public class AuthenticationTest extends BaseTestSupport {
 
     @Test
     public void testAuthPasswordOnly() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.setServiceFactories(Arrays.asList(
                     new ClientUserAuthServiceOld.Factory(),
                     ClientConnectionServiceFactory.INSTANCE
@@ -146,7 +141,7 @@ public class AuthenticationTest extends BaseTestSupport {
 
     @Test
     public void testAuthKeyPassword() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.setServiceFactories(Arrays.asList(
                     new ClientUserAuthServiceOld.Factory(),
                     ClientConnectionServiceFactory.INSTANCE
@@ -168,7 +163,7 @@ public class AuthenticationTest extends BaseTestSupport {
 
     @Test
     public void testAuthKeyInteractive() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.setServiceFactories(Arrays.asList(
                     new ClientUserAuthServiceOld.Factory(),
                     ClientConnectionServiceFactory.INSTANCE

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/KeepAliveTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/KeepAliveTest.java b/sshd-core/src/test/java/org/apache/sshd/KeepAliveTest.java
index 18203fb..1cab46b 100644
--- a/sshd-core/src/test/java/org/apache/sshd/KeepAliveTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/KeepAliveTest.java
@@ -30,9 +30,8 @@ import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShell;
 import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
@@ -58,12 +57,9 @@ public class KeepAliveTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
+        sshd = Utils.setupTestServer();
         FactoryManagerUtils.updateProperty(sshd, FactoryManager.IDLE_TIMEOUT, TIMEOUT);
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
         sshd.setShellFactory(new TestEchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-        sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
         sshd.start();
         port = sshd.getPort();
     }
@@ -77,7 +73,7 @@ public class KeepAliveTest extends BaseTestSupport {
 
     @Test
     public void testIdleClient() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
+        SshClient client = Utils.setupTestClient();
         client.start();
 
         try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -97,7 +93,7 @@ public class KeepAliveTest extends BaseTestSupport {
 
     @Test
     public void testClientWithHeartBeat() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
+        SshClient client = Utils.setupTestClient();
         FactoryManagerUtils.updateProperty(client, ClientFactoryManager.HEARTBEAT_INTERVAL, HEARTBEAT);
         client.start();
 
@@ -118,9 +114,9 @@ public class KeepAliveTest extends BaseTestSupport {
 
     @Test
     public void testShellClosedOnClientTimeout() throws Exception {
-        TestEchoShellFactory.TestEchoShell.latch = new CountDownLatch(1);
+        TestEchoShell.latch = new CountDownLatch(1);
 
-        SshClient client = SshClient.setUpDefaultClient();
+        SshClient client = Utils.setupTestClient();
         client.start();
 
         try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -135,13 +131,14 @@ public class KeepAliveTest extends BaseTestSupport {
                 channel.setErr(err);
                 channel.open().verify(9L, TimeUnit.SECONDS);
 
-                assertTrue("Latch time out", TestEchoShellFactory.TestEchoShell.latch.await(10L, TimeUnit.SECONDS));
+                assertTrue("Latch time out", TestEchoShell.latch.await(10L, TimeUnit.SECONDS));
                 int state = channel.waitFor(ClientChannel.CLOSED, WAIT);
                 assertEquals("Wrong channel state", ClientChannel.CLOSED | ClientChannel.EOF | ClientChannel.OPENED, state);
 
                 channel.close(false);
             }
         } finally {
+            TestEchoShell.latch = null;
             client.stop();
         }
     }
@@ -151,18 +148,18 @@ public class KeepAliveTest extends BaseTestSupport {
         public Command create() {
             return new TestEchoShell();
         }
+    }
 
-        public static class TestEchoShell extends EchoShell {
+    public static class TestEchoShell extends EchoShell {
 
-            public static CountDownLatch latch;
+        public static CountDownLatch latch;
 
-            @Override
-            public void destroy() {
-                if (latch != null) {
-                    latch.countDown();
-                }
-                super.destroy();
+        @Override
+        public void destroy() {
+            if (latch != null) {
+                latch.countDown();
             }
+            super.destroy();
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
index 571357f..5f541e9 100644
--- a/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
@@ -49,8 +49,6 @@ import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.JSchLogger;
 import org.apache.sshd.util.SimpleUserInfo;
 import org.apache.sshd.util.TeeOutputStream;
@@ -83,7 +81,7 @@ public class KeyReExchangeTest extends BaseTestSupport {
     }
 
     protected void setUp(long bytesLimit, long timeLimit) throws Exception {
-        sshd = SshServer.setUpDefaultServer();
+        sshd = Utils.setupTestServer();
         if (bytesLimit > 0L) {
             FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.REKEY_BYTES_LIMIT, bytesLimit);
         }
@@ -91,9 +89,6 @@ public class KeyReExchangeTest extends BaseTestSupport {
             FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.REKEY_TIME_LIMIT, timeLimit);
         }
 
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.start();
         port = sshd.getPort();
     }
@@ -103,7 +98,7 @@ public class KeyReExchangeTest extends BaseTestSupport {
         setUp(0, 0);
 
         sshd.getCipherFactories().add(BuiltinCiphers.none);
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.getCipherFactories().add(BuiltinCiphers.none);
             client.start();
 
@@ -127,7 +122,7 @@ public class KeyReExchangeTest extends BaseTestSupport {
         setUp(0, 0);
         sshd.getCipherFactories().add(BuiltinCiphers.none);
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.getCipherFactories().add(BuiltinCiphers.none);
             // replace the original KEX factories with wrapped ones that we can fail intentionally
             List<NamedFactory<KeyExchange>> kexFactories = new ArrayList<>();
@@ -235,7 +230,7 @@ public class KeyReExchangeTest extends BaseTestSupport {
     public void testReExchangeFromSshdClient() throws Exception {
         setUp(0, 0);
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -292,7 +287,7 @@ public class KeyReExchangeTest extends BaseTestSupport {
     public void testReExchangeFromServer() throws Exception {
         setUp(8192, 0);
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/LoadTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/LoadTest.java b/sshd-core/src/test/java/org/apache/sshd/LoadTest.java
index 96b0ce1..187c204 100644
--- a/sshd-core/src/test/java/org/apache/sshd/LoadTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/LoadTest.java
@@ -40,8 +40,6 @@ import org.apache.sshd.common.cipher.Cipher;
 import org.apache.sshd.common.kex.BuiltinDHFactories;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
 import org.junit.Before;
@@ -57,10 +55,7 @@ public class LoadTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+        sshd = Utils.setupTestServer();
         sshd.start();
         port = sshd.getPort();
     }
@@ -120,7 +115,7 @@ public class LoadTest extends BaseTestSupport {
     }
 
     protected void runClient(String msg) throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             Map<String, Object> props = client.getProperties();
             FactoryManagerUtils.updateProperty(props, FactoryManager.MAX_PACKET_SIZE, 1024 * 16);
             FactoryManagerUtils.updateProperty(props, FactoryManager.WINDOW_SIZE, 1024 * 8);


[4/4] mina-sshd git commit: [SSHD-559] Add support for reading specific host configuration from config file

Posted by lg...@apache.org.
[SSHD-559] Add support for reading specific host configuration from config file


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

Branch: refs/heads/master
Commit: 950e896dc766b2d48a8946a197d7e0b4bf233549
Parents: 3615e4f
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Wed Sep 2 08:39:46 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Wed Sep 2 08:39:46 2015 +0300

----------------------------------------------------------------------
 .../org/apache/sshd/client/ClientBuilder.java   |   40 +
 .../sshd/client/ClientFactoryManager.java       |   32 +
 .../java/org/apache/sshd/client/SshClient.java  |  194 ++-
 .../java/org/apache/sshd/client/SshKeyScan.java |    4 +-
 .../hosts/ConfigFileHostEntryResolver.java      |  104 ++
 .../DefaultConfigFileHostEntryResolver.java     |   82 +
 .../client/config/hosts/HostConfigEntry.java    | 1403 ++++++++++++++++++
 .../config/hosts/HostConfigEntryResolver.java   |   59 +
 .../config/keys/ClientIdentityLoader.java       |   99 ++
 .../config/keys/FilePasswordProvider.java       |   15 +
 .../apache/sshd/common/util/GenericUtils.java   |   12 +
 .../org/apache/sshd/common/util/io/IoUtils.java |   34 +
 .../common/util/io/ModifiableFileWatcher.java   |    2 +-
 .../sshd/server/command/UnknownCommand.java     |   82 -
 .../server/config/keys/AuthorizedKeyEntry.java  |    8 +-
 .../DefaultAuthorizedKeysAuthenticator.java     |    2 +-
 .../server/subsystem/sftp/SftpSubsystem.java    |    2 +-
 .../org/apache/sshd/AuthenticationTest.java     |   17 +-
 .../java/org/apache/sshd/KeepAliveTest.java     |   35 +-
 .../java/org/apache/sshd/KeyReExchangeTest.java |   15 +-
 .../src/test/java/org/apache/sshd/LoadTest.java |    9 +-
 .../org/apache/sshd/PortForwardingLoadTest.java |    7 +-
 .../org/apache/sshd/PortForwardingTest.java     |    9 +-
 .../test/java/org/apache/sshd/ProxyTest.java    |    9 +-
 .../apache/sshd/SinglePublicKeyAuthTest.java    |   16 +-
 .../java/org/apache/sshd/WelcomeBannerTest.java |    9 +-
 .../java/org/apache/sshd/WindowAdjustTest.java  |    7 +-
 .../java/org/apache/sshd/agent/AgentTest.java   |   37 +-
 .../java/org/apache/sshd/client/ClientTest.java |   41 +-
 .../hosts/ConfigFileHostEntryResolverTest.java  |  113 ++
 .../hosts/HostConfigEntryResolverTest.java      |  188 +++
 .../config/hosts/HostConfigEntryTest.java       |  301 ++++
 .../org/apache/sshd/client/kex/KexTest.java     |    9 +-
 .../org/apache/sshd/client/scp/ScpTest.java     |   52 +-
 .../sftp/AbstractSftpClientTestSupport.java     |    7 +-
 .../subsystem/sftp/SftpFileSystemTest.java      |   11 +-
 .../sshd/client/subsystem/sftp/SftpTest.java    |   22 +-
 .../impl/AbstractCheckFileExtensionTest.java    |    4 +-
 .../impl/AbstractMD5HashExtensionTest.java      |    4 +-
 .../impl/CopyDataExtensionImplTest.java         |    4 +-
 .../impl/CopyFileExtensionImplTest.java         |    2 +-
 .../impl/SpaceAvailableExtensionImplTest.java   |    2 +-
 .../openssh/impl/OpenSSHExtensionsTest.java     |    4 +-
 .../apache/sshd/common/channel/WindowTest.java  |   34 +-
 .../sshd/common/cipher/BuiltinCiphersTest.java  |    5 +-
 .../apache/sshd/common/cipher/CipherTest.java   |   10 +-
 .../common/compression/CompressionTest.java     |   10 +-
 .../sshd/common/io/nio2/Nio2ServiceTest.java    |    7 +-
 .../org/apache/sshd/common/mac/MacTest.java     |    9 +-
 .../common/session/AbstractSessionTest.java     |    4 +-
 .../AbstractSignatureFactoryTestSupport.java    |    7 +-
 .../java/org/apache/sshd/server/ServerTest.java |   46 +-
 .../org/apache/sshd/server/SshServerTest.java   |   18 +-
 .../keys/AuthorizedKeysAuthenticatorTest.java   |    6 +-
 .../server/subsystem/sftp/SshFsMounter.java     |    4 +-
 .../java/org/apache/sshd/util/EchoShell.java    |  121 ++
 .../org/apache/sshd/util/EchoShellFactory.java  |  102 +-
 .../org/apache/sshd/util/UnknownCommand.java    |   82 +
 .../apache/sshd/util/UnknownCommandFactory.java |   39 +
 .../test/java/org/apache/sshd/util/Utils.java   |   23 +-
 .../testReadGlobalHostsConfigEntries.config.txt |   23 +
 .../testReadMultipleHostPatterns.config.txt     |    5 +
 .../testReadSimpleHostsConfigEntries.config.txt |    8 +
 .../sshd/git/pack/GitPackCommandFactory.java    |    2 +-
 .../sshd/git/pgm/GitPgmCommandFactory.java      |    2 +-
 65 files changed, 3165 insertions(+), 510 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java
index 0f9de38..c7f1bb5 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java
@@ -23,12 +23,16 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
+import org.apache.sshd.client.config.hosts.DefaultConfigFileHostEntryResolver;
+import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
 import org.apache.sshd.client.kex.DHGClient;
 import org.apache.sshd.client.kex.DHGEXClient;
 import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
 import org.apache.sshd.common.BaseBuilder;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.kex.DHFactory;
 import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.util.Transformer;
@@ -56,8 +60,14 @@ public class ClientBuilder extends BaseBuilder<SshClient, ClientBuilder> {
             Collections.unmodifiableList(Arrays.<NamedFactory<Channel>>asList(ForwardedTcpipFactory.INSTANCE));
 
     public static final ServerKeyVerifier DEFAULT_SERVER_KEY_VERIFIER = AcceptAllServerKeyVerifier.INSTANCE;
+    public static final HostConfigEntryResolver DEFAULT_HOST_CONFIG_ENTRY_RESOLVER = DefaultConfigFileHostEntryResolver.INSTANCE;
+    public static final ClientIdentityLoader DEFAULT_CLIENT_IDENTITY_LOADER = ClientIdentityLoader.DEFAULT;
+    public static final FilePasswordProvider DEFAULT_FILE_PASSWORD_PROVIDER = FilePasswordProvider.EMPTY;
 
     protected ServerKeyVerifier serverKeyVerifier;
+    protected HostConfigEntryResolver hostConfigEntryResolver;
+    protected ClientIdentityLoader clientIdentityLoader;
+    protected FilePasswordProvider filePasswordProvider;
 
     public ClientBuilder() {
         super();
@@ -68,6 +78,21 @@ public class ClientBuilder extends BaseBuilder<SshClient, ClientBuilder> {
         return me();
     }
 
+    public ClientBuilder hostConfigEntryResolver(HostConfigEntryResolver resolver) {
+        this.hostConfigEntryResolver = resolver;
+        return me();
+    }
+
+    public ClientBuilder clientIdentityLoader(ClientIdentityLoader loader) {
+        this.clientIdentityLoader = loader;
+        return me();
+    }
+
+    public ClientBuilder filePasswordProvider(FilePasswordProvider provider) {
+        this.filePasswordProvider = provider;
+        return me();
+    }
+
     @Override
     protected ClientBuilder fillWithDefaultValues() {
         super.fillWithDefaultValues();
@@ -84,6 +109,18 @@ public class ClientBuilder extends BaseBuilder<SshClient, ClientBuilder> {
             serverKeyVerifier = DEFAULT_SERVER_KEY_VERIFIER;
         }
 
+        if (hostConfigEntryResolver == null) {
+            hostConfigEntryResolver = DEFAULT_HOST_CONFIG_ENTRY_RESOLVER;
+        }
+
+        if (clientIdentityLoader == null) {
+            clientIdentityLoader = DEFAULT_CLIENT_IDENTITY_LOADER;
+        }
+
+        if (filePasswordProvider == null) {
+            filePasswordProvider = DEFAULT_FILE_PASSWORD_PROVIDER;
+        }
+
         if (factory == null) {
             factory = SshClient.DEFAULT_SSH_CLIENT_FACTORY;
         }
@@ -95,6 +132,9 @@ public class ClientBuilder extends BaseBuilder<SshClient, ClientBuilder> {
     public SshClient build(boolean isFillWithDefaultValues) {
         SshClient client = super.build(isFillWithDefaultValues);
         client.setServerKeyVerifier(serverKeyVerifier);
+        client.setHostConfigEntryResolver(hostConfigEntryResolver);
+        client.setClientIdentityLoader(clientIdentityLoader);
+        client.setFilePasswordProvider(filePasswordProvider);
         return client;
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
index 03b7308..ef4bda3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
@@ -22,8 +22,11 @@ import java.util.List;
 
 import org.apache.sshd.client.auth.UserAuth;
 import org.apache.sshd.client.auth.UserInteraction;
+import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
 
 /**
  * The <code>ClientFactoryManager</code> enable the retrieval of additional
@@ -70,6 +73,18 @@ public interface ClientFactoryManager extends FactoryManager {
     int DEFAULT_PASSWORD_PROMPTS = 3;
 
     /**
+     * Whether to ignore invalid identities files when pre-initializing
+     * the client session
+     * @see ClientIdentityLoader#isValidLocation(String)
+     */
+    String IGNORE_INVALID_IDENTITIES = "ignore-invalid-identities";
+
+    /**
+     * Default value of {@link #IGNORE_INVALID_IDENTITIES} if none configured
+     */
+    boolean DEFAULT_IGNORE_INVALID_IDENTITIES = true;
+
+    /**
      * Retrieve the server key verifier to be used to check the key when connecting
      * to an ssh server.
      *
@@ -89,4 +104,21 @@ public interface ClientFactoryManager extends FactoryManager {
      */
     List<NamedFactory<UserAuth>> getUserAuthFactories();
 
+    /**
+     * @return The {@link HostConfigEntryResolver} to use in order to resolve the
+     * effective session parameters
+     */
+    HostConfigEntryResolver getHostConfigEntryResolver();
+
+    /**
+     * @return The {@link ClientIdentityLoader} to use in order to load client
+     * key pair identities
+     */
+    ClientIdentityLoader getClientIdentityLoader();
+
+    /**
+     * @return The {@link FilePasswordProvider} to use if need to load encrypted
+     * identities keys
+     */
+    FilePasswordProvider getFilePasswordProvider();
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 6e21329..aa0db19 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -20,15 +20,21 @@ package org.apache.sshd.client;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
 import java.io.PrintWriter;
+import java.io.StreamCorruptedException;
 import java.io.StringWriter;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.nio.file.LinkOption;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedHashMap;
@@ -49,7 +55,10 @@ import org.apache.sshd.client.auth.UserAuthPublicKeyFactory;
 import org.apache.sshd.client.auth.UserInteraction;
 import org.apache.sshd.client.channel.ChannelShell;
 import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
 import org.apache.sshd.client.config.keys.ClientIdentity;
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
 import org.apache.sshd.client.future.ConnectFuture;
 import org.apache.sshd.client.future.DefaultConnectFuture;
 import org.apache.sshd.client.session.ClientConnectionServiceFactory;
@@ -59,12 +68,14 @@ import org.apache.sshd.client.session.SessionFactory;
 import org.apache.sshd.common.AbstractFactoryManager;
 import org.apache.sshd.common.Closeable;
 import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.ServiceFactory;
 import org.apache.sshd.common.SshdSocketAddress;
 import org.apache.sshd.common.channel.Channel;
 import org.apache.sshd.common.config.SshConfigFileReader;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoConnectFuture;
 import org.apache.sshd.common.io.IoConnector;
@@ -73,6 +84,7 @@ import org.apache.sshd.common.session.AbstractSession;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.SecurityUtils;
 import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.io.NoCloseInputStream;
 import org.apache.sshd.common.util.io.NoCloseOutputStream;
 
@@ -153,6 +165,9 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
     protected List<NamedFactory<UserAuth>> userAuthFactories;
 
     private ServerKeyVerifier serverKeyVerifier;
+    private HostConfigEntryResolver hostConfigEntryResolver;
+    private ClientIdentityLoader clientIdentityLoader;
+    private FilePasswordProvider filePasswordProvider;
 
     public SshClient() {
         super();
@@ -176,6 +191,33 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
     }
 
     @Override
+    public HostConfigEntryResolver getHostConfigEntryResolver() {
+        return hostConfigEntryResolver;
+    }
+
+    public void setHostConfigEntryResolver(HostConfigEntryResolver resolver) {
+        this.hostConfigEntryResolver = resolver;
+    }
+
+    @Override
+    public FilePasswordProvider getFilePasswordProvider() {
+        return filePasswordProvider;
+    }
+
+    public void setFilePasswordProvider(FilePasswordProvider provider) {
+        this.filePasswordProvider = provider;
+    }
+
+    @Override
+    public ClientIdentityLoader getClientIdentityLoader() {
+        return clientIdentityLoader;
+    }
+
+    public void setClientIdentityLoader(ClientIdentityLoader loader) {
+        this.clientIdentityLoader = loader;
+    }
+
+    @Override
     public UserInteraction getUserInteraction() {
         return userInteraction;
     }
@@ -199,6 +241,9 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
 
         ValidateUtils.checkNotNull(getTcpipForwarderFactory(), "TcpipForwarderFactory not set");
         ValidateUtils.checkNotNull(getServerKeyVerifier(), "ServerKeyVerifier not set");
+        ValidateUtils.checkNotNull(getHostConfigEntryResolver(), "HostConfigEntryResolver not set");
+        ValidateUtils.checkNotNull(getClientIdentityLoader(), "ClientIdentityLoader not set");
+        ValidateUtils.checkNotNull(getFilePasswordProvider(), "FilePasswordProvider not set");
 
         // Register the additional agent forwarding channel if needed
         SshAgentFactory agentFactory = getAgentFactory();
@@ -275,18 +320,144 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
                 .build();
     }
 
+    /**
+     * Resolves the <U>effective</U> {@link HostConfigEntry} and connects to it
+     *
+     * @param username The intended username
+     * @param host The target host name/address - never {@code null}/empty
+     * @param port The target port
+     * @return A {@link ConnectFuture}
+     * @throws IOException If failed to resolve the effective target or
+     * connect to it
+     * @see #getHostConfigEntryResolver()
+     * @see #connect(HostConfigEntry)
+     */
     public ConnectFuture connect(String username, String host, int port) throws IOException {
-        ValidateUtils.checkTrue(port >= 0, "Invalid port: %d", port);
-        SocketAddress address = new InetSocketAddress(ValidateUtils.checkNotNullAndNotEmpty(host, "No host"), port);
-        return connect(username, address);
+        HostConfigEntryResolver resolver = getHostConfigEntryResolver();
+        HostConfigEntry entry = resolver.resolveEffectiveHost(host, port, username);
+        if (entry == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("connect({}@{}:{}) no overrides", username, host, port);
+            }
+
+            // generate a synthetic entry
+            entry = new HostConfigEntry(host, host, port, username);
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("connect({}@{}:{}) effective: {}", username, host, port, entry);
+            }
+        }
+
+        return connect(entry);
+    }
+
+    /**
+     * Resolves the <U>effective</U> {@link HostConfigEntry} and connects to it
+     *
+     * @param username The intended username
+     * @param address The intended {@link SocketAddress] - never {@code null}. If
+     * this is an {@link InetSocketAddress} then the <U>effective</U> {@link HostConfigEntry}
+     * is resolved and used.
+     * @return A {@link ConnectFuture}
+     * @throws IOException If failed to resolve the effective target or
+     * connect to it
+     * @see #getHostConfigEntryResolver()
+     * @see #connect(HostConfigEntry)
+     * @see #doConnect(String, SocketAddress)
+     */
+    public ConnectFuture connect(String username, SocketAddress address) throws IOException {
+        ValidateUtils.checkNotNull(address, "No target address");
+        if (address instanceof InetSocketAddress) {
+            InetSocketAddress inetAddress = (InetSocketAddress) address;
+            String host = ValidateUtils.checkNotNullAndNotEmpty(inetAddress.getHostString(), "No host");
+            int port = inetAddress.getPort();
+            ValidateUtils.checkTrue(port > 0, "Invalid port: %d", port);
+
+            HostConfigEntryResolver resolver = getHostConfigEntryResolver();
+            HostConfigEntry entry = resolver.resolveEffectiveHost(host, port, username);
+            if (entry == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("connect({}@{}:{}) no overrides", username, host, port);
+                }
+
+                return doConnect(username, address, Collections.<KeyPair>emptyList());
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug("connect({}@{}:{}) effective: {}", username, host, port, entry);
+                }
+
+                return connect(entry);
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("connect({}@{}) not an InetSocketAddress: {}", username, address, address.getClass().getName());
+            }
+            return doConnect(username, address, Collections.<KeyPair>emptyList());
+        }
+    }
+
+    /**
+     * @param hostConfig The effective {@link HostConfigEntry} to connect to - never {@code null}
+     * @return A {@link ConnectFuture}
+     * @throws IOException If failed to create the connector
+     * @see #doCloseGracefully()
+     */
+    public ConnectFuture connect(HostConfigEntry hostConfig) throws IOException {
+        ValidateUtils.checkNotNull(hostConfig, "No host configuration");
+        String host = ValidateUtils.checkNotNullAndNotEmpty(hostConfig.getHostName(), "No target host");
+        int port = hostConfig.getPort();
+        ValidateUtils.checkTrue(port > 0, "Invalid port: %d", port);
+
+        Collection<KeyPair> keys = loadClientIdentities(hostConfig.getIdentities(), IoUtils.EMPTY_LINK_OPTIONS);
+        return doConnect(hostConfig.getUsername(), new InetSocketAddress(host, port), keys);
+    }
+
+    protected List<KeyPair> loadClientIdentities(Collection<String> locations, LinkOption ... options) throws IOException {
+        if (GenericUtils.isEmpty(locations)) {
+            return Collections.emptyList();
+        }
+
+        List<KeyPair> ids = new ArrayList<>(locations.size());
+        boolean ignoreNonExisting = FactoryManagerUtils.getBooleanProperty(this, IGNORE_INVALID_IDENTITIES, DEFAULT_IGNORE_INVALID_IDENTITIES);
+        ClientIdentityLoader loader = ValidateUtils.checkNotNull(getClientIdentityLoader(), "No ClientIdentityLoader");
+        FilePasswordProvider provider = ValidateUtils.checkNotNull(getFilePasswordProvider(), "No FilePasswordProvider");
+        for (String l : locations) {
+            if (!loader.isValidLocation(l)) {
+                if (ignoreNonExisting) {
+                    log.debug("loadClientIdentities - skip non-existing identity location: {}", l);
+                    continue;
+                }
+
+                throw new FileNotFoundException("Invalid identity location: " + l);
+            }
+
+            try {
+                KeyPair kp = loader.loadClientIdentity(l, provider);
+                if (kp == null) {
+                    throw new IOException("No identity loaded from " + l);
+                }
+
+                if (log.isDebugEnabled()) {
+                    log.debug("loadClientIdentities({}) type={}, fingerprint={}",
+                              l, KeyUtils.getKeyType(kp), KeyUtils.getFingerPrint(kp.getPublic()));
+                }
+
+                ids.add(kp);
+            } catch (GeneralSecurityException e) {
+                throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ") to load identity from " + l + ": " + e.getMessage());
+            }
+        }
+
+        return ids;
     }
 
-    public ConnectFuture connect(final String username, SocketAddress address) {
+    protected ConnectFuture doConnect(final String username, final SocketAddress address, final Collection<? extends KeyPair> identities) throws IOException {
         if (connector == null) {
             throw new IllegalStateException("SshClient not started. Please call start() method before connecting to a server");
         }
         final ConnectFuture connectFuture = new DefaultConnectFuture(null);
         connector.connect(address).addListener(new SshFutureListener<IoConnectFuture>() {
+            @SuppressWarnings("synthetic-access")
             @Override
             public void operationComplete(IoConnectFuture future) {
                 if (future.isCanceled()) {
@@ -296,13 +467,26 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
                 } else {
                     ClientSession session = (ClientSession) AbstractSession.getSession(future.getSession());
                     session.setUsername(username);
+
+                    int numIds = GenericUtils.size(identities);
+                    if (numIds > 0) {
+                        if (log.isDebugEnabled()) {
+                            log.debug("doConnect({}@{}) adding {} identities", username, address, numIds);
+                        }
+                        for (KeyPair kp : identities) {
+                            if (log.isTraceEnabled()) {
+                                log.trace("doConnect({}@{}) add identity type={}, fingerprint={}",
+                                          username, address, KeyUtils.getKeyType(kp), KeyUtils.getFingerPrint(kp.getPublic()));
+                            }
+                            session.addPublicKeyIdentity(kp);
+                        }
+                    }
                     connectFuture.setSession(session);
                 }
             }
         });
         return connectFuture;
     }
-
     protected IoConnector createConnector() {
         return getIoServiceFactory().createConnector(getSessionFactory());
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java b/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
index 8e1b49a..d602958 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
@@ -73,6 +73,7 @@ import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.SecurityUtils;
 import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.io.NoCloseInputStream;
 import org.apache.sshd.common.util.logging.AbstractSimplifiedLog;
 import org.apache.sshd.common.util.logging.LoggingUtils;
@@ -608,9 +609,8 @@ public class SshKeyScan extends AbstractSimplifiedLog
             // convert the hosts from the arguments into a "file" - one host per line
             try (ByteArrayOutputStream baos = new ByteArrayOutputStream(hosts.size() * 32)) {
                 try (Writer w = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) {
-                    String eol = System.getProperty("line.separator");
                     for (String h : hosts) {
-                        w.append(h).append(eol);
+                        w.append(h).append(IoUtils.EOL);
                     }
                 }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolver.java
new file mode 100644
index 0000000..2a694c6
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolver.java
@@ -0,0 +1,104 @@
+/*
+ * 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.client.config.hosts;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.ModifiableFileWatcher;
+
+/**
+ * Watches for changes in a configuration file and automatically reloads any changes
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ConfigFileHostEntryResolver extends ModifiableFileWatcher implements HostConfigEntryResolver {
+    private final AtomicReference<HostConfigEntryResolver> delegateHolder = // assumes initially empty
+            new AtomicReference<HostConfigEntryResolver>(HostConfigEntryResolver.EMPTY);
+
+    public ConfigFileHostEntryResolver(File file) {
+        this(ValidateUtils.checkNotNull(file, "No file to watch").toPath());
+    }
+
+    public ConfigFileHostEntryResolver(Path file) {
+        this(file, IoUtils.EMPTY_LINK_OPTIONS);
+    }
+
+    public ConfigFileHostEntryResolver(Path file, LinkOption... options) {
+        super(file, options);
+    }
+
+    @Override
+    public HostConfigEntry resolveEffectiveHost(String host, int port, String username) throws IOException {
+        try {
+            HostConfigEntryResolver delegate = ValidateUtils.checkNotNull(resolveEffectiveResolver(host, port, username), "No delegate");
+            HostConfigEntry entry = delegate.resolveEffectiveHost(host, port, username);
+            if (log.isDebugEnabled()) {
+                log.debug("resolveEffectiveHost({}@{}:{}) => {}", username, host, port, entry);
+            }
+
+            return entry;
+        } catch (Exception e) {
+            if (log.isDebugEnabled()) {
+                log.debug("resolveEffectiveHost({}@{}:{}) failed ({}) to resolve: {}",
+                          username, host, port, e.getClass().getSimpleName(), e.getMessage());
+            }
+
+            if (e instanceof IOException) {
+                throw (IOException) e;
+            } else {
+                throw new IOException(e);
+            }
+        }
+    }
+
+    protected HostConfigEntryResolver resolveEffectiveResolver(String host, int port, String username) throws IOException {
+        if (checkReloadRequired()) {
+            delegateHolder.set(HostConfigEntryResolver.EMPTY);  // start fresh
+
+            Path path = getPath();
+            if (exists()) {
+                Collection<HostConfigEntry> entries = reloadHostConfigEntries(path, host, port, username);
+                if (GenericUtils.size(entries) > 0) {
+                    delegateHolder.set(HostConfigEntry.toHostConfigEntryResolver(entries));
+                }
+            } else {
+                log.info("resolveEffectiveResolver({}@{}:{}) no configuration file at {}", username, host, port, path);
+            }
+        }
+
+        return delegateHolder.get();
+    }
+
+    protected List<HostConfigEntry> reloadHostConfigEntries(Path path, String host, int port, String username) throws IOException {
+        List<HostConfigEntry> entries = HostConfigEntry.readHostConfigEntries(path);
+        log.info("resolveEffectiveResolver({}@{}:{}) loaded {} entries from {}", username, host, port, GenericUtils.size(entries), path);
+        updateReloadAttributes();
+        return entries;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/DefaultConfigFileHostEntryResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/DefaultConfigFileHostEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/DefaultConfigFileHostEntryResolver.java
new file mode 100644
index 0000000..86c4fea
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/DefaultConfigFileHostEntryResolver.java
@@ -0,0 +1,82 @@
+/*
+ * 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.client.config.hosts;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * Monitors the {@code ~/.ssh/config} file of the user currently running
+ * the server, re-loading it if necessary. It also (optionally) enforces the same
+ * permissions regime as {@code OpenSSH} does for the file permissions.
+
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultConfigFileHostEntryResolver extends ConfigFileHostEntryResolver {
+
+    /**
+     * The default instance that enforces the same permissions regime as {@code OpenSSH}
+     */
+    public static final DefaultConfigFileHostEntryResolver INSTANCE = new DefaultConfigFileHostEntryResolver(true);
+
+    private final boolean strict;
+
+    /**
+     * @param strict If {@code true} then makes sure that the containing folder
+     *               has 0700 access and the file 0600. <B>Note:</B> for <I>Windows</I> it
+     *               does not check these permissions
+     */
+    public DefaultConfigFileHostEntryResolver(boolean strict) {
+        this(HostConfigEntry.getDefaultHostConfigFile(), strict);
+    }
+
+    public DefaultConfigFileHostEntryResolver(File file, boolean strict) {
+        this(ValidateUtils.checkNotNull(file, "No file provided").toPath(), strict, IoUtils.getLinkOptions(false));
+    }
+
+    public DefaultConfigFileHostEntryResolver(Path path, boolean strict, LinkOption ... options) {
+        super(path, options);
+        this.strict = strict;
+    }
+
+    public final boolean isStrict() {
+        return strict;
+    }
+
+    @Override
+    protected List<HostConfigEntry> reloadHostConfigEntries(Path path, String host, int port, String username) throws IOException {
+        if (isStrict()) {
+            if (log.isDebugEnabled()) {
+                log.debug("reloadHostConfigEntries({}@{}:{}) check permissions of {}", username, host, port, path);
+            }
+
+            KeyUtils.validateStrictKeyFilePermissions(path);
+        }
+
+        return super.reloadHostConfigEntries(path, host, port, username);
+    }
+}


[2/4] mina-sshd git commit: [SSHD-559] Add support for reading specific host configuration from config file

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/PortForwardingLoadTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/PortForwardingLoadTest.java b/sshd-core/src/test/java/org/apache/sshd/PortForwardingLoadTest.java
index 589bf13..0d845fe 100644
--- a/sshd-core/src/test/java/org/apache/sshd/PortForwardingLoadTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/PortForwardingLoadTest.java
@@ -51,8 +51,6 @@ import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.JSchLogger;
 import org.apache.sshd.util.SimpleUserInfo;
 import org.apache.sshd.util.Utils;
@@ -83,10 +81,7 @@ public class PortForwardingLoadTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+        sshd = Utils.setupTestServer();
         sshd.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
         sshd.start();
         sshPort = sshd.getPort();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java b/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java
index 5dfc8e2..e2b0029 100644
--- a/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java
@@ -60,8 +60,6 @@ import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
 import org.apache.sshd.server.global.CancelTcpipForwardHandler;
 import org.apache.sshd.server.global.TcpipForwardHandler;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.JSchLogger;
 import org.apache.sshd.util.SimpleUserInfo;
 import org.apache.sshd.util.Utils;
@@ -92,12 +90,9 @@ public class PortForwardingTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
+        sshd = Utils.setupTestServer();
         FactoryManagerUtils.updateProperty(sshd, FactoryManager.WINDOW_SIZE, 2048);
         FactoryManagerUtils.updateProperty(sshd, FactoryManager.MAX_PACKET_SIZE, 256);
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
         sshd.start();
 
@@ -598,7 +593,7 @@ public class PortForwardingTest extends BaseTestSupport {
     }
 
     protected ClientSession createNativeSession() throws Exception {
-        client = SshClient.setUpDefaultClient();
+        client = Utils.setupTestClient();
         FactoryManagerUtils.updateProperty(client, FactoryManager.WINDOW_SIZE, 2048);
         FactoryManagerUtils.updateProperty(client, FactoryManager.MAX_PACKET_SIZE, 256);
         client.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java b/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
index 8636bd2..1e84b29 100644
--- a/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
@@ -40,8 +40,6 @@ import org.apache.sshd.common.SshdSocketAddress;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
 import org.junit.Before;
@@ -62,12 +60,9 @@ public class ProxyTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
+        sshd = Utils.setupTestServer();
         FactoryManagerUtils.updateProperty(sshd, FactoryManager.WINDOW_SIZE, 2048);
         FactoryManagerUtils.updateProperty(sshd, FactoryManager.MAX_PACKET_SIZE, "256");
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
         sshd.start();
         sshPort = sshd.getPort();
@@ -145,7 +140,7 @@ public class ProxyTest extends BaseTestSupport {
     }
 
     protected ClientSession createNativeSession() throws Exception {
-        client = SshClient.setUpDefaultClient();
+        client = Utils.setupTestClient();
         FactoryManagerUtils.updateProperty(client, FactoryManager.WINDOW_SIZE, 2048);
         FactoryManagerUtils.updateProperty(client, FactoryManager.MAX_PACKET_SIZE, 256);
         client.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java b/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
index 99445fe..eacdc6c 100644
--- a/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
@@ -30,14 +30,11 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.CommandFactory;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.auth.CachingPublicKeyAuthenticator;
 import org.apache.sshd.server.auth.UserAuthPublicKeyFactory;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
-import org.apache.sshd.server.command.UnknownCommand;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 import org.apache.sshd.server.session.ServerSession;
 import org.apache.sshd.util.BaseTestSupport;
@@ -70,14 +67,7 @@ public class SinglePublicKeyAuthTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setCommandFactory(new CommandFactory() {
-            @Override
-            public Command createCommand(String command) {
-                return new UnknownCommand(command);
-            }
-        });
+        sshd = Utils.setupTestServer();
         FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, UserAuthPublicKeyFactory.NAME);
         sshd.setPublickeyAuthenticator(new PublickeyAuthenticator() {
             @SuppressWarnings("synthetic-access")
@@ -111,7 +101,7 @@ public class SinglePublicKeyAuthTest extends BaseTestSupport {
         });
         delegate = auth;
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -147,7 +137,7 @@ public class SinglePublicKeyAuthTest extends BaseTestSupport {
             }
         };
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java b/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
index a71cd1a..e3c1d13 100644
--- a/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
@@ -27,9 +27,7 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
 import org.junit.Before;
@@ -47,10 +45,7 @@ public class WelcomeBannerTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-        sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
+        sshd = Utils.setupTestServer();
         FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.WELCOME_BANNER, WELCOME);
         sshd.start();
         port = sshd.getPort();
@@ -67,7 +62,7 @@ public class WelcomeBannerTest extends BaseTestSupport {
     public void testBanner() throws Exception {
         final AtomicReference<String> welcome = new AtomicReference<String>();
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.setUserInteraction(new UserInteraction() {
                 @Override
                 public void welcome(String banner) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/WindowAdjustTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/WindowAdjustTest.java b/sshd-core/src/test/java/org/apache/sshd/WindowAdjustTest.java
index 4e02001..1b66d6f 100644
--- a/sshd-core/src/test/java/org/apache/sshd/WindowAdjustTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/WindowAdjustTest.java
@@ -46,7 +46,7 @@ import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.Utils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.FixMethodOrder;
@@ -71,7 +71,7 @@ public class WindowAdjustTest {
 
     @Before
     public void setUp() throws Exception {
-        sshServer = SshServer.setUpDefaultServer();
+        sshServer = Utils.setupTestServer();
         final byte[] msg = Files.readAllBytes(
                 Paths.get(getClass().getResource("/big-msg.txt").toURI()));
 
@@ -82,7 +82,6 @@ public class WindowAdjustTest {
             }
         });
 
-        sshServer.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
         sshServer.start();
         port = sshServer.getPort();
@@ -99,7 +98,7 @@ public class WindowAdjustTest {
     @Test(timeout = 2L * 60L * 1000L)
     public void testTrafficHeavyLoad() throws Exception {
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (final ClientSession session = client.connect("admin", "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/agent/AgentTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/agent/AgentTest.java b/sshd-core/src/test/java/org/apache/sshd/agent/AgentTest.java
index 397f162..2917837 100644
--- a/sshd-core/src/test/java/org/apache/sshd/agent/AgentTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/agent/AgentTest.java
@@ -40,10 +40,9 @@ import org.apache.sshd.common.util.SecurityUtils;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
 import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShell;
 import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.Utils;
 import org.junit.Assume;
@@ -107,27 +106,21 @@ public class AgentTest extends BaseTestSupport {
         KeyPair pair = createTestKeyPairProvider("dsaprivkey.pem").loadKey(KeyPairProvider.SSH_DSS);
         localAgentFactory.getAgent().addIdentity(pair, username);
 
-        try (SshServer sshd1 = SshServer.setUpDefaultServer()) {
-            sshd1.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        try (SshServer sshd1 = Utils.setupTestServer()) {
             sshd1.setShellFactory(shellFactory);
-            sshd1.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-            sshd1.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
             sshd1.setAgentFactory(agentFactory);
             sshd1.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
             sshd1.start();
 
             final int port1 = sshd1.getPort();
-            try (SshServer sshd2 = SshServer.setUpDefaultServer()) {
-                sshd2.setKeyPairProvider(Utils.createTestHostKeyProvider());
+            try (SshServer sshd2 = Utils.setupTestServer()) {
                 sshd2.setShellFactory(new TestEchoShellFactory());
-                sshd2.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-                sshd2.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
                 sshd1.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
                 sshd2.setAgentFactory(new ProxyAgentFactory());
                 sshd2.start();
 
                 final int port2 = sshd2.getPort();
-                try (SshClient client1 = SshClient.setUpDefaultClient()) {
+                try (SshClient client1 = Utils.setupTestClient()) {
                     client1.setAgentFactory(localAgentFactory);
                     client1.start();
 
@@ -151,7 +144,7 @@ public class AgentTest extends BaseTestSupport {
                                     }
                                 }
 
-                                try (SshClient client2 = SshClient.setUpDefaultClient()) {
+                                try (SshClient client2 = Utils.setupTestClient()) {
                                     client2.setAgentFactory(agentFactory);
                                     client2.getProperties().putAll(shellFactory.shell.getEnvironment().getEnv());
                                     client2.start();
@@ -190,24 +183,22 @@ public class AgentTest extends BaseTestSupport {
     }
 
     public static class TestEchoShellFactory extends EchoShellFactory {
-
-        TestEchoShell shell = new TestEchoShell();
+        public final TestEchoShell shell = new TestEchoShell();
 
         @Override
         public Command create() {
             return shell;
         }
+    }
 
-        public class TestEchoShell extends EchoShell {
-
-            boolean started;
+    public static class TestEchoShell extends EchoShell {
+        public boolean started;
 
-            @Override
-            public synchronized void start(Environment env) throws IOException {
-                super.start(env);
-                started = true;
-                notifyAll();
-            }
+        @Override
+        public synchronized void start(Environment env) throws IOException {
+            super.start(env);
+            started = true;
+            notifyAll();
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
index 7713b2d..604b39c 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
@@ -94,13 +94,10 @@ import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.io.NoCloseOutputStream;
 import org.apache.sshd.server.Command;
-import org.apache.sshd.server.CommandFactory;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
 import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.channel.ChannelSessionFactory;
-import org.apache.sshd.server.command.UnknownCommand;
 import org.apache.sshd.server.forward.DirectTcpipFactory;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 import org.apache.sshd.server.session.ServerConnectionServiceFactory;
@@ -110,7 +107,7 @@ import org.apache.sshd.server.session.ServerUserAuthServiceFactory;
 import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
 import org.apache.sshd.util.AsyncEchoShellFactory;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShell;
 import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.TeeOutputStream;
 import org.apache.sshd.util.Utils;
@@ -167,17 +164,8 @@ public class ClientTest extends BaseTestSupport {
         authLatch = new CountDownLatch(0);
         channelLatch = new CountDownLatch(0);
 
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd = Utils.setupTestServer();
         sshd.setShellFactory(new TestEchoShellFactory());
-        sshd.setCommandFactory(new CommandFactory() {
-            @Override
-            public Command createCommand(String command) {
-                return new UnknownCommand(command);
-            }
-        });
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-        sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
         sshd.setServiceFactories(Arrays.asList(
                 new ServerUserAuthServiceFactory() {
                     @Override
@@ -221,7 +209,7 @@ public class ClientTest extends BaseTestSupport {
         sshd.start();
         port = sshd.getPort();
 
-        client = SshClient.setUpDefaultClient();
+        client = Utils.setupTestClient();
         clientSessionHolder.set(null);  // just making sure
         client.addSessionListener(clientSessionListener);
     }
@@ -1164,7 +1152,7 @@ public class ClientTest extends BaseTestSupport {
 
     @Test
     public void testClientDisconnect() throws Exception {
-        TestEchoShellFactory.TestEchoShell.latch = new CountDownLatch(1);
+        TestEchoShell.latch = new CountDownLatch(1);
         try {
             client.start();
 
@@ -1191,14 +1179,14 @@ public class ClientTest extends BaseTestSupport {
                 assertTrue("Packet writing not completed in time", f.await(11L, TimeUnit.SECONDS));
                 suspend(cs.getIoSession());
 
-                TestEchoShellFactory.TestEchoShell.latch.await();
+                TestEchoShell.latch.await();
             } finally {
                 client.stop();
             }
 
             assertNull("Session closure not signalled", clientSessionHolder.get());
         } finally {
-            TestEchoShellFactory.TestEchoShell.latch = null;
+            TestEchoShell.latch = null;
         }
     }
 
@@ -1352,18 +1340,17 @@ public class ClientTest extends BaseTestSupport {
         public Command create() {
             return new TestEchoShell();
         }
+    }
 
-        public static class TestEchoShell extends EchoShell {
-
-            public static CountDownLatch latch = new CountDownLatch(1);
+    public static class TestEchoShell extends EchoShell {
+        public static CountDownLatch latch;
 
-            @Override
-            public void destroy() {
-                if (latch != null) {
-                    latch.countDown();
-                }
-                super.destroy();
+        @Override
+        public void destroy() {
+            if (latch != null) {
+                latch.countDown();
             }
+            super.destroy();
         }
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolverTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolverTest.java b/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolverTest.java
new file mode 100644
index 0000000..2405cbd
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolverTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.client.config.hosts;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ConfigFileHostEntryResolverTest extends BaseTestSupport {
+    public ConfigFileHostEntryResolverTest() {
+        super();
+    }
+
+    @Test
+    public void testConfigFileReload() throws IOException {
+        File dir = new File(new File(detectTargetFolder(), TEMP_SUBFOLDER_NAME), getClass().getSimpleName());
+        final AtomicInteger reloadCount = new AtomicInteger();
+        ConfigFileHostEntryResolver resolver = new ConfigFileHostEntryResolver(new File(assertHierarchyTargetFolderExists(dir), getCurrentTestName() + ".config.txt")) {
+            @Override
+            protected List<HostConfigEntry> reloadHostConfigEntries(Path path, String host, int port, String username)
+                    throws IOException {
+                reloadCount.incrementAndGet();
+                return super.reloadHostConfigEntries(path, host, port, username);
+            }
+        };
+        Path path = resolver.getPath();
+
+        HostConfigEntry expected = new HostConfigEntry(getCurrentTestName(), getCurrentTestName(), 7365, getCurrentTestName());
+        testConfigFileReload("Non-existing", path, reloadCount, null, resolver, expected, null);
+        testConfigFileReload("Empty", path, reloadCount, Collections.<HostConfigEntry>emptyList(), resolver, expected, null);
+        testConfigFileReload("Global", path, reloadCount,
+                Collections.singletonList(new HostConfigEntry(HostConfigEntry.ALL_HOSTS_PATTERN, expected.getHost(), expected.getPort(), expected.getUsername())),
+                resolver, expected, expected);
+        testConfigFileReload("Wildcard", path, reloadCount,
+                Arrays.asList(
+                        new HostConfigEntry(HostConfigEntry.ALL_HOSTS_PATTERN, getClass().getSimpleName(), 1234, getClass().getSimpleName()),
+                        new HostConfigEntry(expected.getHost() + String.valueOf(HostConfigEntry.WILDCARD_PATTERN), expected.getHost(), expected.getPort(), expected.getUsername())),
+                resolver, expected, expected);
+        testConfigFileReload("Specific", path, reloadCount,
+                Arrays.asList(
+                        new HostConfigEntry(HostConfigEntry.ALL_HOSTS_PATTERN, getClass().getSimpleName(), 1234, getClass().getSimpleName()),
+                        new HostConfigEntry(getClass().getSimpleName() + String.valueOf(HostConfigEntry.WILDCARD_PATTERN), getClass().getSimpleName(), 1234, getClass().getSimpleName()),
+                        expected),
+                resolver, expected, expected);
+    }
+
+    private static void testConfigFileReload(
+            String phase, Path path, AtomicInteger reloadCount, Collection<? extends HostConfigEntry> entries, HostConfigEntryResolver resolver, HostConfigEntry query, HostConfigEntry expected)
+                    throws IOException {
+        if (entries == null) {
+            if (Files.exists(path)) {
+                Files.delete(path);
+            }
+        } else {
+            HostConfigEntry.writeHostConfigEntries(path, entries, IoUtils.EMPTY_OPEN_OPTIONS);
+        }
+
+        reloadCount.set(0);
+
+        for (int index = 1; index < Byte.SIZE; index++) {
+            HostConfigEntry actual = resolver.resolveEffectiveHost(query.getHostName(), query.getPort(), query.getUsername());
+
+            if (entries == null) {
+                assertEquals(phase + "[" + index + "]: mismatched reload count", 0, reloadCount.get());
+            } else {
+                assertEquals(phase + "[" + index + "]: mismatched reload count", 1, reloadCount.get());
+            }
+
+            if (expected == null) {
+                assertNull(phase + "[" + index + "]: Unexpected success for " + query, actual);
+            } else {
+                assertNotNull(phase + "[" + index + "]: No result for " + query, actual);
+                assertNotSame(phase + "[" + index + "]: No cloned result for " + query, expected, actual);
+                assertEquals(phase + "[" + index + "]: Mismatched host for " + query, expected.getHostName(), actual.getHostName());
+                assertEquals(phase + "[" + index + "]: Mismatched port for " + query, expected.getPort(), actual.getPort());
+                assertEquals(phase + "[" + index + "]: Mismatched user for " + query, expected.getUsername(), actual.getUsername());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolverTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolverTest.java b/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolverTest.java
new file mode 100644
index 0000000..a47d999
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolverTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.client.config.hosts;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.ClientFactoryManager;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.FactoryManagerUtils;
+import org.apache.sshd.common.SshdSocketAddress;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator;
+import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class HostConfigEntryResolverTest extends BaseTestSupport {
+    private SshServer sshd;
+    private SshClient client;
+    private int port;
+
+    public HostConfigEntryResolverTest() {
+        super();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        sshd = Utils.setupTestServer();
+        sshd.start();
+        port = sshd.getPort();
+
+        client = Utils.setupTestClient();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (sshd != null) {
+            sshd.stop(true);
+        }
+        if (client != null) {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testEffectiveHostConfigResolution() throws Exception {
+        final HostConfigEntry entry = new HostConfigEntry(getCurrentTestName(), "localhost", port, getCurrentTestName());
+        client.setHostConfigEntryResolver(new HostConfigEntryResolver() {
+            @Override
+            public HostConfigEntry resolveEffectiveHost(String host, int portValue, String username) throws IOException {
+                return entry;
+            }
+        });
+        client.start();
+
+        try(ClientSession session = client.connect(getClass().getSimpleName(), getClass().getPackage().getName(), getMovedPortNumber(port)).verify(7L, TimeUnit.SECONDS).getSession()) {
+            session.addPasswordIdentity(getCurrentTestName());
+            session.auth().verify(5L, TimeUnit.SECONDS);
+            assertEffectiveRemoteAddress(session, entry);
+        } finally {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testPreloadedIdentities() throws Exception {
+        KeyPairProvider provider = ValidateUtils.checkNotNull(sshd.getKeyPairProvider(), "No key pair provider");
+        Iterable<? extends KeyPair> pairs = ValidateUtils.checkNotNull(provider.loadKeys(), "No loaded keys");
+        Iterator<? extends KeyPair> iter = ValidateUtils.checkNotNull(pairs.iterator(), "No keys iterator");
+        assertTrue("Empty loaded kyes iterator", iter.hasNext());
+
+        final KeyPair identity = iter.next();
+        final String USER = getCurrentTestName();
+        // make sure authentication is achieved only via the identity public key
+        sshd.setPublickeyAuthenticator(new PublickeyAuthenticator() {
+            @Override
+            public boolean authenticate(String username, PublicKey key, ServerSession session) {
+                if (USER.equals(username)) {
+                    return KeyUtils.compareKeys(identity.getPublic(), key);
+                }
+
+                return false;
+            }
+        });
+        sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+
+        final String IDENTITY = getCurrentTestName();
+        client.setClientIdentityLoader(new ClientIdentityLoader() {
+            @Override
+            public boolean isValidLocation(String location) throws IOException {
+                return IDENTITY.equals(location);
+            }
+
+            @Override
+            public KeyPair loadClientIdentity(String location, FilePasswordProvider provider) throws IOException, GeneralSecurityException {
+                if (isValidLocation(location)) {
+                    return identity;
+                }
+
+                throw new FileNotFoundException("Unknown location: " + location);
+            }
+        });
+        FactoryManagerUtils.updateProperty(client, ClientFactoryManager.IGNORE_INVALID_IDENTITIES, false);
+
+        final String HOST = getClass().getSimpleName();
+        final HostConfigEntry entry = new HostConfigEntry(HOST, "localhost", port, USER);
+        entry.addIdentity(IDENTITY);
+        client.setHostConfigEntryResolver(new HostConfigEntryResolver() {
+            @Override
+            public HostConfigEntry resolveEffectiveHost(String host, int portValue, String username) throws IOException {
+                return entry;
+            }
+        });
+
+        client.start();
+        try(ClientSession session = client.connect(USER, HOST, getMovedPortNumber(port)).verify(7L, TimeUnit.SECONDS).getSession()) {
+            session.auth().verify(5L, TimeUnit.SECONDS);
+            assertEffectiveRemoteAddress(session, entry);
+        } finally {
+            client.stop();
+        }
+
+    }
+
+    private static int getMovedPortNumber(int port) {
+        return (port > Short.MAX_VALUE) ? (port - Short.MAX_VALUE) : (1 + Short.MAX_VALUE - port);
+    }
+
+    private static <S extends Session> S assertEffectiveRemoteAddress(S session, HostConfigEntry entry) {
+        IoSession ioSession = session.getIoSession();
+        SocketAddress remoteAddress = ioSession.getRemoteAddress();
+        InetSocketAddress inetAddress;
+        if (remoteAddress instanceof InetSocketAddress) {
+            inetAddress = (InetSocketAddress) remoteAddress;
+        } else if (remoteAddress instanceof SshdSocketAddress) {
+            inetAddress = ((SshdSocketAddress) remoteAddress).toInetSocketAddress();
+        } else {
+            throw new ClassCastException("Unknown remote address type: " + remoteAddress);
+        }
+
+        assertEquals("Mismatched effective port", entry.getPort(), inetAddress.getPort());
+        assertEquals("Mismatched effective user", entry.getUsername(), session.getUsername());
+        return session;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java b/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
new file mode 100644
index 0000000..00e61dd
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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.client.config.hosts;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class HostConfigEntryTest extends BaseTestSupport {
+    public HostConfigEntryTest() {
+        super();
+    }
+
+    @Test
+    public void testHostWildcardPatternMatching() {
+        String pkgName = getClass().getPackage().getName();
+        String[] elements = GenericUtils.split(pkgName, '.');
+        StringBuilder sb = new StringBuilder(pkgName.length() + Long.SIZE + 1).append(HostConfigEntry.WILDCARD_PATTERN);
+        for (int index = elements.length - 1; index >= 0; index--) {
+            sb.append('.').append(elements[index]);
+        }
+
+        String value = sb.toString();
+        Pattern pattern = HostConfigEntry.toPattern(value);
+        String domain = value.substring(1); // chomp the wildcard prefix
+        for (String host : new String[] {
+                getClass().getSimpleName(),
+                getCurrentTestName(),
+                getClass().getSimpleName() + "-" + getCurrentTestName(),
+                getClass().getSimpleName() + "." + getCurrentTestName(),
+            }) {
+            sb.setLength(0); // start from scratch
+            sb.append(host).append(domain);
+
+            testCaseInsensitivePatternMatching(sb.toString(), pattern, true);
+        }
+    }
+
+    @Test
+    public void testIPAddressWildcardPatternMatching() {
+        StringBuilder sb = new StringBuilder().append("10.0.0.");
+        int sbLen = sb.length();
+
+        Pattern pattern = HostConfigEntry.toPattern(sb.append(HostConfigEntry.WILDCARD_PATTERN));
+        for (int v = 0; v <= 255; v++) {
+            sb.setLength(sbLen);    // start from scratch
+            sb.append(v);
+
+            String address = sb.toString();
+            assertTrue("No match for " + address, HostConfigEntry.isHostMatch(address, pattern));
+        }
+    }
+
+    @Test
+    public void testHostSingleCharPatternMatching() {
+        String value = getCurrentTestName();
+        StringBuilder sb = new StringBuilder(value);
+        for (boolean restoreOriginal : new boolean[] { true, false }) {
+            for (int index = 0; index < value.length(); index++) {
+                sb.setCharAt(index, HostConfigEntry.SINGLE_CHAR_PATTERN);
+                testCaseInsensitivePatternMatching(value, HostConfigEntry.toPattern(sb.toString()), true);
+                if (restoreOriginal) {
+                    sb.setCharAt(index, value.charAt(index));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testIPAddressSingleCharPatternMatching() {
+        StringBuilder sb = new StringBuilder().append("10.0.0.");
+        int sbLen = sb.length();
+
+        for (int v = 0; v <= 255; v++) {
+            sb.setLength(sbLen);    // start from scratch
+            sb.append(v);
+
+            String address = sb.toString();
+            // replace the added digits with single char pattern
+            for (int index = sbLen; index < sb.length(); index++) {
+                sb.setCharAt(index, HostConfigEntry.SINGLE_CHAR_PATTERN);
+            }
+            String pattern = sb.toString();
+
+            assertTrue("No match for " + address + " on pattern=" + pattern, HostConfigEntry.isHostMatch(address, pattern));
+        }
+    }
+
+    @Test
+    public void testIsValidPatternChar() {
+        for (char ch = '\0'; ch <= ' '; ch++) {
+            assertFalse("Unexpected valid character (0x" + Integer.toHexString(ch & 0xFF) + ")", HostConfigEntry.isValidPatternChar(ch));
+        }
+
+        for (char ch = 'a'; ch <= 'z'; ch++) {
+            assertTrue("Valid character not recognized: " + String.valueOf(ch), HostConfigEntry.isValidPatternChar(ch));
+        }
+
+        for (char ch = 'A'; ch <= 'Z'; ch++) {
+            assertTrue("Valid character not recognized: " + String.valueOf(ch), HostConfigEntry.isValidPatternChar(ch));
+        }
+
+        for (char ch = '0'; ch <= '9'; ch++) {
+            assertTrue("Valid character not recognized: " + String.valueOf(ch), HostConfigEntry.isValidPatternChar(ch));
+        }
+
+        for (char ch : new char[] { '-', '_', '.', HostConfigEntry.SINGLE_CHAR_PATTERN, HostConfigEntry.WILDCARD_PATTERN }) {
+            assertTrue("Valid character not recognized: " + String.valueOf(ch), HostConfigEntry.isValidPatternChar(ch));
+        }
+
+        for (char ch : new char[] {
+                    '(', ')', '{', '}', '[', ']', '!', '@',
+                    '#', '$', '^', '&', '%', '~', '<', '>',
+                    ',', '/', '\\', '\'', '"', ':', ';' }) {
+            assertFalse("Unexpected valid character: " + String.valueOf(ch), HostConfigEntry.isValidPatternChar(ch));
+        }
+
+        for (char ch = 0x7E; ch <= 0xFF; ch++) {
+            assertFalse("Unexpected valid character (0x" + Integer.toHexString(ch & 0xFF) + ")", HostConfigEntry.isValidPatternChar(ch));
+        }
+    }
+
+    @Test
+    public void testResolvePort() {
+        final int ORIGINAL_PORT = Short.MAX_VALUE;
+        final int ENTRY_PORT = 7365;
+        assertEquals("Mismatched entry port preference", ENTRY_PORT, HostConfigEntry.resolvePort(ORIGINAL_PORT, ENTRY_PORT));
+
+        for (int entryPort : new int[] { -1, 0 }) {
+            assertEquals("Non-preferred original port for entry port=" + entryPort, ORIGINAL_PORT, HostConfigEntry.resolvePort(ORIGINAL_PORT, entryPort));
+        }
+    }
+
+    @Test
+    public void testResolveUsername() {
+        final String ORIGINAL_USER = getCurrentTestName();
+        final String ENTRY_USER = getClass().getSimpleName();
+        assertSame("Mismatched entry user preference", ENTRY_USER, HostConfigEntry.resolveUsername(ORIGINAL_USER, ENTRY_USER));
+
+        for (String entryUser : new String[] { null, "" }) {
+            assertSame("Non-preferred original user for entry user='" + entryUser + "'", ORIGINAL_USER, HostConfigEntry.resolveUsername(ORIGINAL_USER, entryUser));
+        }
+    }
+
+    @Test
+    public void testReadSimpleHostsConfigEntries() throws IOException {
+        validateHostConfigEntries(readHostConfigEntries());
+    }
+
+    @Test
+    public void testReadGlobalHostsConfigEntries() throws IOException {
+        List<HostConfigEntry> entries = validateHostConfigEntries(readHostConfigEntries());
+        assertTrue("Not enough entries read", GenericUtils.size(entries) > 1);
+
+        // global entry MUST be 1st one
+        HostConfigEntry globalEntry = entries.get(0);
+        assertEquals("Mismatched global entry pattern", HostConfigEntry.ALL_HOSTS_PATTERN, globalEntry.getHost());
+
+        for (int index = 1; index < entries.size(); index++) {
+            HostConfigEntry entry = entries.get(index);
+            assertFalse("No target host for " + entry, GenericUtils.isEmpty(entry.getHostName()));
+            assertTrue("No target port for " + entry, entry.getPort() > 0);
+            assertFalse("No username for " + entry, GenericUtils.isEmpty(entry.getUsername()));
+            assertFalse("No identities for " + entry, GenericUtils.isEmpty(entry.getIdentities()));
+            assertFalse("No properties for " + entry, GenericUtils.isEmpty(entry.getProperties()));
+        }
+    }
+
+    @Test
+    public void testReadMultipleHostPatterns() throws IOException {
+        List<HostConfigEntry> entries = validateHostConfigEntries(readHostConfigEntries());
+        assertEquals("Mismatched number of duplicates", 3, GenericUtils.size(entries));
+
+        HostConfigEntry prototype = entries.get(0);
+        String protoIds = GenericUtils.join(prototype.getIdentities(), ',');
+        Collection<Map.Entry<String,String>> protoProps = prototype.getProperties().entrySet();
+        for (int index = 1; index < entries.size(); index++) {
+            HostConfigEntry entry = entries.get(index);
+            assertEquals("Mismatched host name for " + entry, prototype.getHostName(), entry.getHostName());
+            assertEquals("Mismatched port for " + entry, prototype.getPort(), entry.getPort());
+            assertSame("Mismatched user for " + entry, prototype.getUsername(), entry.getUsername());
+            assertEquals("Mismatched identities for " + entry, protoIds, GenericUtils.join(entry.getIdentities(), ','));
+
+            Map<String,String> entryProps = entry.getProperties();
+            assertEquals("Mismatched properties count for " + entry, GenericUtils.size(protoProps), GenericUtils.size(entryProps));
+
+            for (Map.Entry<String,String> ppe : protoProps) {
+                String key = ppe.getKey();
+                String expected = ppe.getValue();
+                String actual = entryProps.get(key);
+                assertEquals("Mismatched value for " + key + " property of " + entry, expected, actual);
+            }
+        }
+    }
+
+    @Test
+    public void testResolveIdentityFilePath() throws Exception {
+        final String HOST = getClass().getSimpleName();
+        final int PORT = 7365;
+        final String USER = getCurrentTestName();
+
+        Exception err = null;
+        for (String pattern : new String[] {
+                "~/.ssh/%h.key",
+                "%d/.ssh/%h.key",
+                "/home/%u/.ssh/id_rsa_%p",
+                "/home/%u/.ssh/id_%r_rsa",
+                "/home/%u/.ssh/%h/%l.key"
+        }) {
+            try {
+                String result = HostConfigEntry.resolveIdentityFilePath(pattern, HOST, PORT, USER);
+                System.out.append('\t').append(pattern).append(" => ").println(result);
+            } catch(Exception e) {
+                System.err.append("Failed (").append(e.getClass().getSimpleName())
+                          .append(") to process pattern=").append(pattern)
+                          .append(": ").println(e.getMessage());
+                err = e;
+            }
+        }
+
+        if (err != null) {
+            throw err;
+        }
+    }
+
+    @Test
+    public void testFindBestMatch() {
+        final String HOST = getCurrentTestName();
+        HostConfigEntry expected = new HostConfigEntry(HOST, HOST, 7365, HOST);
+        List<HostConfigEntry> matches = new ArrayList<>();
+        matches.add(new HostConfigEntry(HostConfigEntry.ALL_HOSTS_PATTERN, getClass().getSimpleName(), Short.MAX_VALUE, getClass().getSimpleName()));
+        matches.add(new HostConfigEntry(HOST + String.valueOf(HostConfigEntry.WILDCARD_PATTERN), getClass().getSimpleName(), Byte.MAX_VALUE, getClass().getSimpleName()));
+        matches.add(expected);
+
+        for (int index = 0; index < matches.size(); index++) {
+            HostConfigEntry actual = HostConfigEntry.findBestMatch(matches);
+            assertSame("Mismatched best match for " + matches, expected, actual);
+            Collections.shuffle(matches);
+        }
+    }
+
+    private static <C extends Collection<HostConfigEntry>> C validateHostConfigEntries(C entries) {
+        assertFalse("No entries", GenericUtils.isEmpty(entries));
+
+        for (HostConfigEntry entry : entries) {
+            assertFalse("No pattern for " + entry, GenericUtils.isEmpty(entry.getHost()));
+            assertFalse("No extra properties for " + entry, GenericUtils.isEmpty(entry.getProperties()));
+        }
+
+        return entries;
+    }
+
+    private List<HostConfigEntry> readHostConfigEntries() throws IOException {
+        return readHostConfigEntries(getCurrentTestName() + ".config.txt");
+    }
+
+    private List<HostConfigEntry> readHostConfigEntries(String resourceName) throws IOException {
+        URL url = getClass().getResource(resourceName);
+        assertNotNull("Missing resource " + resourceName, url);
+        return HostConfigEntry.readHostConfigEntries(url);
+    }
+    private static void testCaseInsensitivePatternMatching(String value, Pattern pattern, boolean expected) {
+        for (int index = 0; index < value.length(); index++) {
+            boolean actual = HostConfigEntry.isHostMatch(value, pattern);
+            assertEquals("Mismatched match result for " + value + " on pattern=" + pattern.pattern(), expected, actual);
+            value = shuffleCase(value);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/kex/KexTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/kex/KexTest.java b/sshd-core/src/test/java/org/apache/sshd/client/kex/KexTest.java
index 5ff1111..3c96be4 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/kex/KexTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/kex/KexTest.java
@@ -37,8 +37,6 @@ import org.apache.sshd.common.kex.BuiltinDHFactories;
 import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.TeeOutputStream;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
@@ -75,10 +73,7 @@ public class KexTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+        sshd = Utils.setupTestServer();
         sshd.start();
         port = sshd.getPort();
     }
@@ -102,7 +97,7 @@ public class KexTest extends BaseTestSupport {
         try (ByteArrayOutputStream sent = new ByteArrayOutputStream();
              ByteArrayOutputStream out = new ByteArrayOutputStream()) {
 
-            try (SshClient client = SshClient.setUpDefaultClient()) {
+            try (SshClient client = Utils.setupTestClient()) {
                 client.setKeyExchangeFactories(Collections.singletonList(kex));
                 client.start();
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
index 9e7ab79..5cf9543 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
@@ -55,11 +55,10 @@ import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.command.ScpCommandFactory;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.JSchLogger;
 import org.apache.sshd.util.SimpleUserInfo;
 import org.apache.sshd.util.Utils;
@@ -99,11 +98,8 @@ public class ScpTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd = Utils.setupTestServer();
         sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.setFileSystemFactory(fileSystemFactory);
         sshd.start();
         port = sshd.getPort();
@@ -145,7 +141,7 @@ public class ScpTest extends BaseTestSupport {
 
         Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
         Path localFile = localDir.resolve("file.txt");
-        byte[] data = writeFile(localFile, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+        byte[] data = writeFile(localFile, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
 
         Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
         Path remoteFile = remoteDir.resolve(localFile.getFileName().toString());
@@ -153,7 +149,7 @@ public class ScpTest extends BaseTestSupport {
         String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
         String[] remoteComps = GenericUtils.split(remotePath, '/');
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             Factory<? extends Random> factory = client.getRandomFactory();
@@ -208,14 +204,14 @@ public class ScpTest extends BaseTestSupport {
 
         Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
         Path localFile = localDir.resolve("file-1.txt");
-        byte[] data = writeFile(localFile, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+        byte[] data = writeFile(localFile, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
 
         Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
         Path remoteFile = remoteDir.resolve(localFile.getFileName().toString());
         String localPath = localFile.toString();
         String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -243,7 +239,7 @@ public class ScpTest extends BaseTestSupport {
 
     @Test
     public void testScpUploadOverwrite() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -251,7 +247,7 @@ public class ScpTest extends BaseTestSupport {
                 session.auth().verify(5L, TimeUnit.SECONDS);
 
                 ScpClient scp = createScpClient(session);
-                String data = getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator");
+                String data = getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL;
 
                 Path targetPath = detectTargetFolder().toPath();
                 Path parentPath = targetPath.getParent();
@@ -295,7 +291,7 @@ public class ScpTest extends BaseTestSupport {
             Files.delete(zeroRemote);
         }
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -331,7 +327,7 @@ public class ScpTest extends BaseTestSupport {
         }
         assertEquals("Non-zero size for remote file=" + zeroRemote, 0L, Files.size(zeroRemote));
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -350,7 +346,7 @@ public class ScpTest extends BaseTestSupport {
 
     @Test
     public void testScpNativeOnSingleFile() throws Exception {
-        String data = getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator");
+        String data = getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL;
 
         Path targetPath = detectTargetFolder().toPath();
         Path parentPath = targetPath.getParent();
@@ -362,7 +358,7 @@ public class ScpTest extends BaseTestSupport {
         Path remoteDir = scpRoot.resolve("remote");
         Path remoteOutFile = remoteDir.resolve(localOutFile.getFileName());
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -402,7 +398,7 @@ public class ScpTest extends BaseTestSupport {
 
     @Test
     public void testScpNativeOnMultipleFiles() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -417,7 +413,7 @@ public class ScpTest extends BaseTestSupport {
 
                 Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
                 Path local1 = localDir.resolve("file-1.txt");
-                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
 
                 Path local2 = localDir.resolve("file-2.txt");
                 Files.write(local2, data);
@@ -483,7 +479,7 @@ public class ScpTest extends BaseTestSupport {
 
     @Test
     public void testScpNativeOnRecursiveDirs() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -499,7 +495,7 @@ public class ScpTest extends BaseTestSupport {
                 Path localDir = scpRoot.resolve("local");
                 Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
                 Path localSub1 = localSubDir.resolve("file-1.txt");
-                byte[] data = writeFile(localSub1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+                byte[] data = writeFile(localSub1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
                 Path localSub2 = localSubDir.resolve("file-2.txt");
                 Files.write(localSub2, data);
 
@@ -523,7 +519,7 @@ public class ScpTest extends BaseTestSupport {
 
     @Test
     public void testScpNativeOnDirWithPattern() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -538,7 +534,7 @@ public class ScpTest extends BaseTestSupport {
 
                 Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
                 Path local1 = localDir.resolve("file-1.txt");
-                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
                 Path local2 = localDir.resolve("file-2.txt");
                 Files.write(local2, data);
 
@@ -561,7 +557,7 @@ public class ScpTest extends BaseTestSupport {
 
     @Test
     public void testScpNativeOnMixedDirAndFiles() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -577,7 +573,7 @@ public class ScpTest extends BaseTestSupport {
                 Path localDir = scpRoot.resolve("local");
                 Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
                 Path local1 = localDir.resolve("file-1.txt");
-                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
                 Path localSub2 = localSubDir.resolve("file-2.txt");
                 Files.write(localSub2, data);
 
@@ -608,7 +604,7 @@ public class ScpTest extends BaseTestSupport {
 
     @Test
     public void testScpNativePreserveAttributes() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -627,7 +623,7 @@ public class ScpTest extends BaseTestSupport {
                 final long lastModMillis = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
                 final long lastModSecs = TimeUnit.MILLISECONDS.toSeconds(lastModMillis);
                 Path local1 = localDir.resolve("file-1.txt");
-                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+                byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
 
                 File lclFile1 = local1.toFile();
                 boolean lcl1ModSet = lclFile1.setLastModified(lastModMillis);
@@ -672,7 +668,7 @@ public class ScpTest extends BaseTestSupport {
 
     @Test
     public void testStreamsUploadAndDownload() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -821,7 +817,7 @@ public class ScpTest extends BaseTestSupport {
             byte[] remoteData = Files.readAllBytes(remoteFile);
             assertArrayEquals("Mismatched remote put data", expected, remoteData);
 
-            Arrays.fill(remoteData, (byte) 0);  // make sure we start with a clean slate 
+            Arrays.fill(remoteData, (byte) 0);  // make sure we start with a clean slate
             try (InputStream input = scp_client.get(remotePath + "/" + fileName)) {
                 int readLen = input.read(remoteData);
                 assertEquals("Mismatched remote get data size", expected.length, readLen);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
index 75ff85d..8033be7 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
@@ -35,8 +35,6 @@ import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.command.ScpCommandFactory;
 import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.Utils;
 
 /**
@@ -61,12 +59,9 @@ public abstract class AbstractSftpClientTestSupport extends BaseTestSupport {
     }
 
     protected void setupServer() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd = Utils.setupTestServer();
         sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
         sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.setFileSystemFactory(fileSystemFactory);
         sshd.start();
         port = sshd.getPort();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
index 6c70d54..9603a87 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
@@ -66,8 +66,6 @@ import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.command.ScpCommandFactory;
 import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
 import org.junit.Before;
@@ -96,12 +94,9 @@ public class SftpFileSystemTest extends BaseTestSupport {
 
     @Before
     public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd = Utils.setupTestServer();
         sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
         sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.setFileSystemFactory(fileSystemFactory);
         sshd.start();
         port = sshd.getPort();
@@ -216,7 +211,7 @@ public class SftpFileSystemTest extends BaseTestSupport {
 
     @Test
     public void testMultipleFileStoresOnSameProvider() throws IOException {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
@@ -286,7 +281,7 @@ public class SftpFileSystemTest extends BaseTestSupport {
                 return selected.get();
             }
         };
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index 3226877..1c039c2 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -131,7 +131,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         rnd.fill(expectedRandom);
 
         byte[] expectedText = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -183,7 +183,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         rnd.fill(expected);
         Files.write(testFile, expected);
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -227,7 +227,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
                 }
             });
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -247,7 +247,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
 
     @Test
     public void testNormalizeRemoteRootValues() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -286,7 +286,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         String file = Utils.resolveRelativeRemotePath(parentPath, testFile);
         String[] comps = GenericUtils.split(file, '/');
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             Factory<? extends Random> factory = client.getRandomFactory();
@@ -347,7 +347,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         File javaFile = testFile.toFile();
         assertHierarchyTargetFolderExists(javaFile.getParentFile());
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -439,7 +439,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
 
     @Test
     public void testClient() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -464,7 +464,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
      */
     @Test
     public void testWriteChunking() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -655,7 +655,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         Path parentPath = targetPath.getParent();
         Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -701,7 +701,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
 
     @Test
     public void testServerExtensionsDeclarations() throws Exception {
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -800,7 +800,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
             }
         };
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java
index c591c1c..89415d2 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java
@@ -138,7 +138,7 @@ public class AbstractCheckFileExtensionTest extends AbstractSftpClientTestSuppor
         byte[] seed = (getClass().getName() + "#" + getCurrentTestName()
                 + "-" + expectedAlgorithm
                 + "-" + inputDataSize + "/" + hashBlockSize
-                + System.getProperty("line.separator"))
+                + IoUtils.EOL)
                 .getBytes(StandardCharsets.UTF_8);
 
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream(inputDataSize + seed.length)) {
@@ -175,7 +175,7 @@ public class AbstractCheckFileExtensionTest extends AbstractSftpClientTestSuppor
         String srcPath = Utils.resolveRelativeRemotePath(parentPath, srcFile);
         String srcFolder = Utils.resolveRelativeRemotePath(parentPath, srcFile.getParent());
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java
index e2013a8..6b39051 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java
@@ -96,7 +96,7 @@ public class AbstractMD5HashExtensionTest extends AbstractSftpClientTestSupport
     }
 
     private void testMD5HashExtension(int dataSize) throws Exception {
-        byte[] seed = (getClass().getName() + "#" + getCurrentTestName() + "-" + dataSize + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8);
+        byte[] seed = (getClass().getName() + "#" + getCurrentTestName() + "-" + dataSize + IoUtils.EOL).getBytes(StandardCharsets.UTF_8);
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream(dataSize + seed.length)) {
             while (baos.size() < dataSize) {
                 baos.write(seed);
@@ -131,7 +131,7 @@ public class AbstractMD5HashExtensionTest extends AbstractSftpClientTestSupport
         String srcPath = Utils.resolveRelativeRemotePath(parentPath, srcFile);
         String srcFolder = Utils.resolveRelativeRemotePath(parentPath, srcFile.getParent());
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImplTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImplTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImplTest.java
index 7c0f989..dda241e 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImplTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImplTest.java
@@ -123,7 +123,7 @@ public class CopyDataExtensionImplTest extends AbstractSftpClientTestSupport {
         byte[] seed = (getClass().getName() + "#" + getCurrentTestName()
                 + "-" + dataSize
                 + "-" + readOffset + "/" + readLength + "/" + writeOffset
-                + System.getProperty("line.separator"))
+                + IoUtils.EOL)
                 .getBytes(StandardCharsets.UTF_8);
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream(dataSize + seed.length)) {
             while (baos.size() < dataSize) {
@@ -150,7 +150,7 @@ public class CopyDataExtensionImplTest extends AbstractSftpClientTestSupport {
         }
         String dstPath = Utils.resolveRelativeRemotePath(parentPath, dstFile);
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             if (writeOffset > 0L) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java
index 1f9ebb1..59c1ef5 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java
@@ -78,7 +78,7 @@ public class CopyFileExtensionImplTest extends AbstractSftpClientTestSupport {
         LinkOption[] options = IoUtils.getLinkOptions(false);
         assertFalse("Destination file unexpectedly exists", Files.exists(dstFile, options));
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/SpaceAvailableExtensionImplTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/SpaceAvailableExtensionImplTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/SpaceAvailableExtensionImplTest.java
index 1c9e0d9..d200267 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/SpaceAvailableExtensionImplTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/SpaceAvailableExtensionImplTest.java
@@ -88,7 +88,7 @@ public class SpaceAvailableExtensionImplTest extends AbstractSftpClientTestSuppo
             }
         }));
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/impl/OpenSSHExtensionsTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/impl/OpenSSHExtensionsTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/impl/OpenSSHExtensionsTest.java
index 20ab5fa..087ae37 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/impl/OpenSSHExtensionsTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/impl/OpenSSHExtensionsTest.java
@@ -89,7 +89,7 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport {
 
         Path parentPath = targetPath.getParent();
         String srcPath = Utils.resolveRelativeRemotePath(parentPath, srcFile);
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -178,7 +178,7 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport {
             }
         }));
 
-        try (SshClient client = SshClient.setUpDefaultClient()) {
+        try (SshClient client = Utils.setupTestClient()) {
             client.start();
 
             try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/950e896d/sshd-core/src/test/java/org/apache/sshd/common/channel/WindowTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/channel/WindowTest.java b/sshd-core/src/test/java/org/apache/sshd/common/channel/WindowTest.java
index f8b23fb..73b64df 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/channel/WindowTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/channel/WindowTest.java
@@ -47,12 +47,9 @@ import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.server.Command;
-import org.apache.sshd.server.CommandFactory;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
 import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.channel.ChannelSessionFactory;
-import org.apache.sshd.server.command.UnknownCommand;
 import org.apache.sshd.server.forward.DirectTcpipFactory;
 import org.apache.sshd.server.session.ServerConnectionService;
 import org.apache.sshd.server.session.ServerConnectionServiceFactory;
@@ -60,7 +57,7 @@ import org.apache.sshd.server.session.ServerUserAuthService;
 import org.apache.sshd.server.session.ServerUserAuthServiceFactory;
 import org.apache.sshd.util.AsyncEchoShellFactory;
 import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShell;
 import org.apache.sshd.util.EchoShellFactory;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
@@ -92,17 +89,8 @@ public class WindowTest extends BaseTestSupport {
         authLatch = new CountDownLatch(0);
         channelLatch = new CountDownLatch(0);
 
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd = Utils.setupTestServer();
         sshd.setShellFactory(new TestEchoShellFactory());
-        sshd.setCommandFactory(new CommandFactory() {
-            @Override
-            public Command createCommand(String command) {
-                return new UnknownCommand(command);
-            }
-        });
-        sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
-        sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
         sshd.setServiceFactories(Arrays.asList(
                 new ServerUserAuthServiceFactory() {
                     @Override
@@ -146,7 +134,7 @@ public class WindowTest extends BaseTestSupport {
         sshd.start();
         port = sshd.getPort();
 
-        client = SshClient.setUpDefaultClient();
+        client = Utils.setupTestClient();
     }
 
     @After
@@ -348,18 +336,18 @@ public class WindowTest extends BaseTestSupport {
         public Command create() {
             return new TestEchoShell();
         }
+    }
 
-        public static class TestEchoShell extends EchoShell {
+    public static class TestEchoShell extends EchoShell {
 
-            public static CountDownLatch latch = new CountDownLatch(1);
+        public static final CountDownLatch latch = new CountDownLatch(1);
 
-            @Override
-            public void destroy() {
-                if (latch != null) {
-                    latch.countDown();
-                }
-                super.destroy();
+        @Override
+        public void destroy() {
+            if (latch != null) {
+                latch.countDown();
             }
+            super.destroy();
         }
     }
 }