mina-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lgoldst...@apache.org
Subject [4/4] mina-sshd git commit: [SSHD-559] Add support for reading specific host configuration from config file
Date Wed, 02 Sep 2015 05:40:03 GMT
[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 <lgoldstein@vmware.com>
Authored: Wed Sep 2 08:39:46 2015 +0300
Committer: Lyor Goldstein <lgoldstein@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);
+    }
+}


Mime
View raw message