package com.pastdev.jsch; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jcraft.jsch.Identity; import com.jcraft.jsch.IdentityRepository; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Proxy; import com.jcraft.jsch.Session; import com.jcraft.jsch.UserInfo; import com.jcraft.jsch.agentproxy.AgentProxyException; import com.jcraft.jsch.agentproxy.Connector; import com.jcraft.jsch.agentproxy.ConnectorFactory; import com.jcraft.jsch.agentproxy.RemoteIdentityRepository; /** * The default implementation of {@link com.pastdev.jsch.SessionFactory * SessionFactory}. This class provides sane defaults for all * <i>conventional</i> configuration including * * <p style="margin-left: 20px;"> * <b>username</b>: System property <code>user.name</code> <br> * <b>hostname</b>: localhost <br> * <b>port</b>: 22 <br> * <b>.ssh directory:</b> System property <code>jsch.dotSsh</code>, or system * property <code>user.home</code> concatenated with <code>"/.ssh"</code> <br> * <b>known hosts:</b> System property <code>jsch.knownHosts.file</code> or, * .ssh directory concatenated with <code>"/known_hosts"</code>. <br> * <b>private keys:</b> First checks for an agent proxy using * {@link ConnectorFactory#createConnector()}, then system property * <code>jsch.privateKey.files</code> split on <code>","</code>, otherwise, .ssh * directory concatenated with all 3 of <code>"/id_rsa"</code>, * <code>"/id_dsa"</code>, and <code>"/id_ecdsa"</code> if they exist. * </p> */ public class DefaultSessionFactory implements SessionFactory { private static Logger logger = LoggerFactory.getLogger( DefaultSessionFactory.class ); public static final String PROPERTY_JSCH_DOT_SSH = "jsch.dotSsh"; public static final String PROPERTY_JSCH_KNOWN_HOSTS_FILE = "jsch.knownHosts.file"; public static final String PROPERTY_JSCH_PRIVATE_KEY_FILES = "jsch.privateKey.files"; private Map<String, String> config; private File dotSshDir; private String hostname; private JSch jsch; private String password; private int port = SSH_PORT; private Proxy proxy; private UserInfo userInfo; private String username; /** * Creates a default DefaultSessionFactory. */ public DefaultSessionFactory() { this( null, null, null ); } /** * Constructs a DefaultSessionFactory with the supplied properties. * * @param username * The username * @param hostname * The hostname * @param port * The port */ public DefaultSessionFactory( String username, String hostname, Integer port ) { JSch.setLogger( new Slf4jBridge() ); jsch = new JSch(); try { setDefaultIdentities(); } catch ( JSchException e ) { logger.warn( "Unable to set default identities: ", e ); } try { setDefaultKnownHosts(); } catch ( JSchException e ) { logger.warn( "Unable to set default known_hosts: ", e ); } if ( username == null ) { this.username = System.getProperty( "user.name" ).toLowerCase(); } else { this.username = username; } if ( hostname == null ) { this.hostname = "localhost"; } else { this.hostname = hostname; } if ( port == null ) { this.port = 22; } else { this.port = port; } } private DefaultSessionFactory( JSch jsch, String username, String hostname, int port, Proxy proxy ) { this.jsch = jsch; this.username = username; this.hostname = hostname; this.port = port; this.proxy = proxy; } private void clearIdentityRepository() throws JSchException { jsch.setIdentityRepository( null ); // revert to default identity repo jsch.removeAllIdentity(); } private File dotSshDir() { if ( dotSshDir == null ) { String dotSshString = System.getProperty( PROPERTY_JSCH_DOT_SSH ); if ( dotSshString != null ) { dotSshDir = new File( dotSshString ); } else { dotSshDir = new File( new File( System.getProperty( "user.home" ) ), ".ssh" ); } } return dotSshDir; } @Override public String getHostname() { return hostname; } @Override public int getPort() { return port; } @Override public Proxy getProxy() { return proxy; } @Override public String getUsername() { return username; } @Override public UserInfo getUserInfo() { return userInfo; } @Override public Session newSession() throws JSchException { Session session = jsch.getSession( username, hostname, port ); if ( config != null ) { for ( String key : config.keySet() ) { session.setConfig( key, config.get( key ) ); } } if ( proxy != null ) { session.setProxy( proxy ); } if ( password != null ) { session.setPassword( password ); } if ( userInfo != null ) { session.setUserInfo( userInfo ); } return session; } @Override public SessionFactoryBuilder newSessionFactoryBuilder() { return new SessionFactoryBuilder( jsch, username, hostname, port, proxy, config, userInfo ) { @Override public SessionFactory build() { DefaultSessionFactory sessionFactory = new DefaultSessionFactory( jsch, username, hostname, port, proxy ); sessionFactory.config = config; sessionFactory.password = password; sessionFactory.userInfo = userInfo; return sessionFactory; } }; } /** * Sets the configuration options for the sessions created by this factory. * This method will replace the current SessionFactory <code>config</code> * map. If you want to add, rather than replace, see * {@link #setConfig(String, String)}. All of these options will be added * one at a time using * {@link com.jcraft.jsch.Session#setConfig(String, String) * Session.setConfig(String, String)}. Details on the supported options can * be found in the source for {@link com.jcraft.jsch.Session#applyConfig()}. * * @param config * The configuration options * * @see com.jcraft.jsch.Session#setConfig(java.util.Hashtable) * @see com.jcraft.jsch.Session#applyConfig() */ public void setConfig( Map<String, String> config ) { this.config = config; } /** * Adds a single configuration options for the sessions created by this * factory. Details on the supported options can be found in the source for * {@link com.jcraft.jsch.Session#applyConfig()}. * * @param key * The name of the option * @param value * The value of the option * * @see #setConfig(Map) * @see com.jcraft.jsch.Session#setConfig(java.util.Hashtable) * @see com.jcraft.jsch.Session#applyConfig() */ public void setConfig( String key, String value ) { if ( config == null ) { config = new HashMap<String, String>(); } config.put( key, value ); } private void setDefaultKnownHosts() throws JSchException { String knownHosts = System.getProperty( PROPERTY_JSCH_KNOWN_HOSTS_FILE ); if ( knownHosts != null && !knownHosts.isEmpty() ) { setKnownHosts( knownHosts ); } else { File knownHostsFile = new File( dotSshDir(), "known_hosts" ); if ( knownHostsFile.exists() ) { setKnownHosts( knownHostsFile.getAbsolutePath() ); } } } private void setDefaultIdentities() throws JSchException { boolean identitiesSet = false; try { Connector connector = ConnectorFactory.getDefault() .createConnector(); if ( connector != null ) { logger.info( "An AgentProxy Connector was found, check for identities" ); RemoteIdentityRepository repository = new RemoteIdentityRepository( connector ); Vector<Identity> identities = repository.getIdentities(); if ( identities.size() > 0 ) { logger.info( "Using AgentProxy identities: {}", identities ); setIdentityRepository( repository ); identitiesSet = true; } } } catch ( AgentProxyException e ) { logger.debug( "Failed to load any keys from AgentProxy:", e ); } if ( !identitiesSet ) { String privateKeyFilesString = System.getProperty( PROPERTY_JSCH_PRIVATE_KEY_FILES ); if ( privateKeyFilesString != null && !privateKeyFilesString.isEmpty() ) { logger.info( "Using local identities from {}: {}", PROPERTY_JSCH_PRIVATE_KEY_FILES, privateKeyFilesString ); setIdentitiesFromPrivateKeys( Arrays.asList( privateKeyFilesString.split( "," ) ) ); identitiesSet = true; } } if ( !identitiesSet ) { List<String> privateKeyFiles = new ArrayList<String>(); for ( File file : new File[] { new File( dotSshDir(), "id_rsa" ), new File( dotSshDir(), "id_dsa" ), new File( dotSshDir(), "id_ecdsa" ) } ) { if ( file.exists() ) { privateKeyFiles.add( file.getAbsolutePath() ); } } logger.info( "Using local identities: {}", privateKeyFiles ); setIdentitiesFromPrivateKeys( privateKeyFiles ); } } /** * Sets the hostname. * * @param hostname * The hostname. */ public void setHostname( String hostname ) { this.hostname = hostname; } /** * Configures this factory to use a single identity authenticated by the * supplied private key. The private key should be the path to a private key * file in OpenSSH format. Clears out the current {@link IdentityRepository} * before adding this key. * * @param privateKey * Path to a private key file * @throws JSchException * If the key is invalid */ public void setIdentityFromPrivateKey( String privateKey ) throws JSchException { clearIdentityRepository(); jsch.addIdentity( privateKey ); } /** * Configures this factory to use a single identity authenticated by the * supplied private key and pass phrase. The private key should be the path * to a private key file in OpenSSH format. Clears out the current * {@link IdentityRepository} before adding this key. * * @param privateKey * Path to a private key file * @param passPhrase * Pass phrase for private key * @throws JSchException * If the key is invalid */ public void setIdentityFromPrivateKey( String privateKey, String passPhrase ) throws JSchException { clearIdentityRepository(); jsch.addIdentity( privateKey, passPhrase ); } /** * Configures this factory to use a list of identities authenticated by the * supplied private keys. The private keys should be the paths to a private * key files in OpenSSH format. Clears out the current * {@link IdentityRepository} before adding these keys. * * @param privateKeys * A list of paths to private key files * @throws JSchException * If one (or more) of the keys are invalid */ public void setIdentitiesFromPrivateKeys( List<String> privateKeys ) throws JSchException { clearIdentityRepository(); for ( String privateKey : privateKeys ) { jsch.addIdentity( privateKey ); } } /** * Sets the {@link IdentityRepository} for this factory. This will replace * any current IdentityRepository, so you should be sure to call this before * any of the <code>setIdentit(y|ies)Xxx</code> if you plan on using both. * * @param identityRepository * The identity repository * * @see JSch#setIdentityRepository(IdentityRepository) */ public void setIdentityRepository( IdentityRepository identityRepository ) { jsch.setIdentityRepository( identityRepository ); } /** * Sets the known hosts from the stream. Mostly useful if you distribute * your known_hosts in the jar for your application rather than allowing * users to manage their own known hosts. * * @param knownHosts * A stream of known hosts * @throws JSchException * If an I/O error occurs * * @see JSch#setKnownHosts(InputStream) */ public void setKnownHosts( InputStream knownHosts ) throws JSchException { jsch.setKnownHosts( knownHosts ); } /** * Sets the known hosts from a file at path <code>knownHosts</code>. * * @param knownHosts * The path to a known hosts file * @throws JSchException * If an I/O error occurs * * @see JSch#setKnownHosts(String) */ public void setKnownHosts( String knownHosts ) throws JSchException { jsch.setKnownHosts( knownHosts ); } /** * Sets the {@code password} used to authenticate {@code username}. This * mode of authentication is not recommended as it would keep the password * in memory and if the application dies and writes a heap dump, it would be * available. Using {@link Identity} would be better, or even using ssh * agent support. * * @param password * the password for {@code username} */ public void setPassword( String password ) { this.password = password; } /** * Sets the port. * * @param port * The port */ public void setPort( int port ) { this.port = port; } /** * Sets the proxy through which all connections will be piped. * * @param proxy * The proxy */ public void setProxy( Proxy proxy ) { this.proxy = proxy; } /** * Sets the {@code UserInfo} for use with {@code keyboard-interactive} * authentication. This may be useful, however, setting the password * with {@link #setPassword(String)} is likely sufficient. * * @param userInfo * * @see <a * href="http://www.jcraft.com/jsch/examples/UserAuthKI.java.html">Keyboard * Interactive Authentication Example</a> */ public void setUserInfo( UserInfo userInfo ) { this.userInfo = userInfo; } /** * Sets the username. * * @param username * The username */ public void setUsername( String username ) { this.username = username; } @Override public String toString() { return (proxy == null ? "" : proxy.toString() + " ") + "ssh://" + username + "@" + hostname + ":" + port; } }