package org.tmatesoft.svn.core.internal.io.svn.ssh; import com.trilead.ssh2.Connection; import com.trilead.ssh2.InteractiveCallback; import com.trilead.ssh2.ServerHostKeyVerifier; import com.trilead.ssh2.auth.AgentProxy; import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class SshHost { private static final int CONNECTION_INACTIVITY_TIMEOUT = Integer.parseInt(System.getProperty("svnkit.ssh.connection.inactivity.timeout.secs", "600")) * 1000; // 10 minutes private static final int MAX_CONCURRENT_OPENERS = Integer.parseInt(System.getProperty("svnkit.ssh.max.concurrent.connection.openers", "3")); private static final int MAX_SESSIONS_PER_CONNECTION = Integer.parseInt(System.getProperty("svnkit.ssh.max.sessions.per.connection", "8")); private String myHost; private int myPort; private ServerHostKeyVerifier myHostVerifier; private char[] myPrivateKey; private char[] myPassphrase; private char[] myPassword; private String myUserName; private AgentProxy myAgentProxy; private int myConnectTimeout; private boolean myIsLocked; private boolean myIsDisposed; private List<SshConnection> myConnections; private Object myOpenerLock = new Object(); private int myOpenersCount; private int myReadTimeout; public SshHost(String host, int port) { myConnections = new LinkedList<SshConnection>(); myHost = host; myPort = port; } public void setHostVerifier(ServerHostKeyVerifier verifier) { myHostVerifier = verifier; } public void setConnectionTimeout(int timeout) { myConnectTimeout = timeout; } public void setReadTimeout(int readTimeout) { myReadTimeout = readTimeout; } public void setCredentials(String userName, char[] key, char[] passphrase, char[] password, AgentProxy agentProxy) { myUserName = userName; myPrivateKey = key; myPassphrase = passphrase; myPassword = password; myAgentProxy = agentProxy; } public boolean purge() { try { lock(); int size = myConnections.size(); long time = System.currentTimeMillis(); for (Iterator<SshConnection> connections = myConnections.iterator(); connections.hasNext();) { SshConnection connection = connections.next(); if (connection.getSessionsCount() == 0) { if (myConnections.size() == 1) { long timeout = time - connection.lastAcccessTime(); if (timeout >= CONNECTION_INACTIVITY_TIMEOUT) { connection.close(); connections.remove(); } } else { connection.close(); connections.remove(); } } } if (myConnections.size() == 0 && size > 0) { setDisposed(true); } return isDisposed(); } finally { unlock(); } } public boolean isDisposed() { return myIsDisposed; } public void setDisposed(boolean disposed) { myIsDisposed = disposed; if (disposed) { for (SshConnection connection : myConnections) { connection.close(); } myConnections.clear(); } } public String getKey() { String key = myUserName + ":" + myHost + ":" + myPort; if (myPrivateKey != null) { key += ":" + new String(myPrivateKey); } if (myPassphrase != null) { key += ":" + new String(myPassphrase); } if (myPassword != null) { key += ":" + new String(myPassword); } return key; } void lock() { synchronized (myConnections) { while(myIsLocked) { try { myConnections.wait(); } catch (InterruptedException e) { } } myIsLocked = true; } } void unlock() { synchronized (myConnections) { myIsLocked = false; myConnections.notifyAll(); } } public SshSession openSession() throws IOException { SshSession session = useExistingConnection(); if (session != null) { return session; } SshConnection newConnection = null; addOpener(); try { session = useExistingConnection(); if (session != null) { return session; } newConnection = openConnection(); } finally { removeOpener(); } if (newConnection != null) { lock(); try { if (isDisposed()) { newConnection.close(); throw new SshHostDisposedException(); } myConnections.add(newConnection); return newConnection.openSession(); } finally { unlock(); } } throw new IOException("Cannot establish SSH connection with " + myHost + ":" + myPort); } private SshSession useExistingConnection() throws IOException { lock(); try { if (isDisposed()) { throw new SshHostDisposedException(); } for (Iterator<SshConnection> connections = myConnections.iterator(); connections.hasNext();) { final SshConnection connection = connections.next(); if (connection.getSessionsCount() < MAX_SESSIONS_PER_CONNECTION) { try { return connection.openSession(); } catch (IOException e) { // this connection has been closed by server. if (e.getMessage() != null && e.getMessage().contains("connection is closed")) { connection.close(); connections.remove(); } else { throw e; } } } } } finally { unlock(); } return null; } private void removeOpener() { synchronized (myOpenerLock) { myOpenersCount--; myOpenerLock.notifyAll(); } } private void addOpener() { synchronized(myOpenerLock) { while(myOpenersCount >= MAX_CONCURRENT_OPENERS) { try { myOpenerLock.wait(); } catch (InterruptedException e) { } } myOpenersCount++; } } private SshConnection openConnection() throws IOException { Connection connection = new Connection(myHost, myPort); connection.connect(new ServerHostKeyVerifier() { public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) throws Exception { if (myHostVerifier != null) { myHostVerifier.verifyServerHostKey(hostname, port, serverHostKeyAlgorithm, serverHostKey); } return true; } }, myConnectTimeout, myReadTimeout, myConnectTimeout); boolean authenticated = false; final String password = myPassword != null ? new String(myPassword) : null; final String passphrase = myPassphrase != null ? new String(myPassphrase) : null; if(myAgentProxy != null) { authenticated = connection.authenticateWithAgent(myUserName, myAgentProxy); } if (!authenticated && myPrivateKey != null) { authenticated = connection.authenticateWithPublicKey(myUserName, myPrivateKey, passphrase); } if (!authenticated && myPassword != null) { String[] methods = connection.getRemainingAuthMethods(myUserName); for (int i = 0; !authenticated && i < methods.length; i++) { if ("password".equals(methods[i])) { authenticated = connection.authenticateWithPassword(myUserName, password); } else if ("keyboard-interactive".equals(methods[i])) { authenticated = connection.authenticateWithKeyboardInteractive(myUserName, new InteractiveCallback() { public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) throws Exception { String[] reply = new String[numPrompts]; for (int i = 0; i < reply.length; i++) { reply[i] = password; } return reply; } }); } } } if (!authenticated) { connection.close(); throw new SshAuthenticationException("Credentials rejected by SSH server."); } return new SshConnection(this, connection); } public String toString() { return myUserName + "@" + myHost + ":" + myPort + ":" + myConnections.size(); } }