/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.communication.sshconnection.internal;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.UnknownHostException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Logger;
import com.jcraft.jsch.Session;
import de.rcenvironment.core.communication.sshconnection.SshConnectionConstants;
import de.rcenvironment.core.communication.sshconnection.api.SshConnectionListener;
import de.rcenvironment.core.communication.sshconnection.api.SshConnectionSetup;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.ssh.jsch.JschSessionFactory;
import de.rcenvironment.core.utils.ssh.jsch.SshParameterException;
import de.rcenvironment.core.utils.ssh.jsch.SshSessionConfiguration;
import de.rcenvironment.core.utils.ssh.jsch.SshSessionConfigurationFactory;
import de.rcenvironment.core.utils.ssh.jsch.executor.JSchRCECommandLineExecutor;
/**
* Default implementaion of @link {@link SshConnectionSetup}.
*
* @author Brigitte Boden
*/
public class SshConnectionSetupImpl implements SshConnectionSetup {
private SshSessionConfiguration config;
private String id;
private String displayName;
private Session session;
private SshConnectionListener listener;
private boolean connectOnStartup;
private boolean storePassphrase;
private boolean usePassphrase;
private Log log = LogFactory.getLog(getClass());
public SshConnectionSetupImpl(String id, String displayName, String host, int port, String userName, String keyFileLocation,
boolean usePassphrase, boolean storePassphrase, boolean connectOnStartUp, SshConnectionListener listener) {
if (keyFileLocation == null || keyFileLocation.isEmpty()) {
config = SshSessionConfigurationFactory.createSshSessionConfigurationWithAuthPhrase(host, port, userName, null);
} else {
config = SshSessionConfigurationFactory.createSshSessionConfigurationWithKeyFileLocation(host, port, userName, keyFileLocation);
}
this.id = id;
this.connectOnStartup = connectOnStartUp;
this.listener = listener;
this.displayName = displayName;
this.storePassphrase = storePassphrase;
this.usePassphrase = usePassphrase;
listener.onCreated(this);
}
/**
* {@inheritDoc}
*
* @see de.rcenvironment.core.communication.sshconnection.api.SshConnectionSetup#getHost()
*/
@Override
public String getHost() {
return config.getDestinationHost();
}
/**
* {@inheritDoc}
*
* @see de.rcenvironment.core.communication.sshconnection.api.SshConnectionSetup#getPort()
*/
@Override
public int getPort() {
return config.getPort();
}
/**
* {@inheritDoc}
*
* @see de.rcenvironment.core.communication.sshconnection.api.SshConnectionSetup#getUsername()
*/
@Override
public String getUsername() {
return config.getSshAuthUser();
}
@Override
public String getKeyfileLocation() {
return config.getSshKeyFileLocation();
}
/**
* {@inheritDoc}
*
* @see de.rcenvironment.core.communication.sshconnection.api.SshConnectionSetup#getDisplayName()
*/
@Override
public String getDisplayName() {
return displayName;
}
/**
* {@inheritDoc}
*
* @see de.rcenvironment.core.communication.sshconnection.api.SshConnectionSetup#isConnected()
*/
@Override
public boolean isConnected() {
if (session == null) {
return false;
}
boolean result = session.isConnected();
// If the session is not null, but also not connected, it has been lost, which should be communicated by the listener.
if (!result) {
session = null;
listener.onConnectionClosed(this, false);
log.warn(StringUtils.format("SSH session lost: host %s, port %s", config.getDestinationHost(),
config.getPort()));
}
return result;
}
@Override
public Session getSession() {
return session;
}
@Override
public Session connect(String passphrase) {
if (config.getSshKeyFileLocation() == null && passphrase == null) {
log.warn(StringUtils.format("Connecting SSH session failed because no key file and no passphrase is given: host %s, port %s.",
config.getDestinationHost(),
config.getPort()));
return null;
}
Logger logger = JschSessionFactory.createDelegateLogger(LogFactory.getLog(getClass()));
try {
session =
JschSessionFactory.setupSession(config.getDestinationHost(), config.getPort(), config.getSshAuthUser(),
config.getSshKeyFileLocation(), passphrase, logger);
} catch (JSchException | SshParameterException e) {
log.warn(StringUtils.format("Connecting SSH session failed: host %s, port %s: %s", config.getDestinationHost(),
config.getPort(), e.toString()));
// Filter typical reasons to produce better error messages.
String reason = e.getMessage();
Throwable cause = e.getCause();
if (cause != null && cause instanceof ConnectException) {
reason = "The remote instance could not be reached. Probably the hostname or port is wrong.";
} else if (cause != null && cause instanceof UnknownHostException) {
reason = "No host with this name could be found.";
} else if (reason.equals("Auth fail")) {
reason =
"Authentication failed. Probably the username or passphrase is wrong, the wrong key file was used or the account is "
+ "not enabled on the remote host.";
} else if (reason.equals("USERAUTH fail")) {
reason = "Authentication failed. The wrong passphrase for the key file " + config.getSshKeyFileLocation() + " was used.";
} else if (reason.startsWith("invalid privatekey")) {
reason = "Authentication failed. An invalid private key was used.";
}
listener.onConnectionAttemptFailed(this, reason, true, false);
return null;
}
// Check if remote RCE instance has a compatible version
JSchRCECommandLineExecutor rceExecutor = new JSchRCECommandLineExecutor(session);
String remoteRCEVersion;
try {
rceExecutor.start("ra protocol-version");
try (InputStream stdoutStream = rceExecutor.getStdout(); InputStream stderrStream = rceExecutor.getStderr();) {
rceExecutor.waitForTermination();
remoteRCEVersion = IOUtils.toString(stdoutStream).trim();
}
} catch (IOException | InterruptedException e1) {
log.warn(StringUtils.format(
"Connecting SSH session failed: Could not retrieve version of RCE instance on host %s, port %s: %s",
config.getDestinationHost(), config.getPort(),
e1.toString()));
session.disconnect();
session = null;
String reason = "The RCE version of the remote instance could not be retrieved. Possibly it is not an RCE instance.";
listener.onConnectionAttemptFailed(this, reason, true, false);
return null;
}
if (!remoteRCEVersion.contains(SshConnectionConstants.REQUIRED_PROTOCOL_VERSION)) {
log.warn(StringUtils
.format(
"Connecting SSH session failed: Either, the RCE instance on host %s, port %s has an incompatible version, "
+ "or the user %s does not have the required permissions to run remote access tools and workflows. "
+ "(Detected server version information: %s, required version: %s)",
config.getDestinationHost(), config.getPort(), config.getSshAuthUser(),
remoteRCEVersion, SshConnectionConstants.REQUIRED_PROTOCOL_VERSION));
session.disconnect();
session = null;
String reason = StringUtils.format(
"Either, the remote RCE instance has an incompatible version, or the user %s "
+ "does not have the required permissions to run remote access tools and workflows. "
+ "\n\n(Detected server version information: %s, required version: %s)",
config.getSshAuthUser(),
remoteRCEVersion, SshConnectionConstants.REQUIRED_PROTOCOL_VERSION);
listener.onConnectionAttemptFailed(this, reason, true, false);
return null;
}
listener.onConnected(this);
return session;
}
@Override
public void disconnect() {
session.disconnect();
listener.onConnectionClosed(this, false);
session = null;
}
@Override
public String getId() {
return id;
}
@Override
public boolean getConnectOnStartUp() {
return connectOnStartup;
}
@Override
public boolean getStorePassphrase() {
return storePassphrase;
}
@Override
public boolean getUsePassphrase() {
return usePassphrase;
}
}