/* * 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.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.equinox.security.storage.ISecurePreferences; import org.eclipse.equinox.security.storage.StorageException; import com.jcraft.jsch.Session; import de.rcenvironment.core.communication.configuration.NodeConfigurationService; import de.rcenvironment.core.communication.sshconnection.InitialSshConnectionConfig; import de.rcenvironment.core.communication.sshconnection.SshConnectionConstants; import de.rcenvironment.core.communication.sshconnection.SshConnectionContext; import de.rcenvironment.core.communication.sshconnection.SshConnectionService; import de.rcenvironment.core.communication.sshconnection.api.SshConnectionListener; import de.rcenvironment.core.communication.sshconnection.api.SshConnectionListenerAdapter; import de.rcenvironment.core.communication.sshconnection.api.SshConnectionSetup; import de.rcenvironment.core.configuration.SecurePreferencesFactory; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncCallback; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncCallbackExceptionPolicy; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncOrderedCallbackManager; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; /** * Default implementation of {@link SshConnectionService}. * * @author Brigitte Boden */ public class SshConnectionServiceImpl implements SshConnectionService { private static final String NO_SSH_CONNECTION_WITH_ID_S_CONFIGURED = "No SSH connection with id %s configured."; private final Map<String, SshConnectionSetup> connectionSetups; private final Log log = LogFactory.getLog(getClass()); private final AsyncOrderedCallbackManager<SshConnectionListener> callbackManager = ConcurrencyUtils.getFactory().createAsyncOrderedCallbackManager(AsyncCallbackExceptionPolicy.LOG_AND_CANCEL_LISTENER); private NodeConfigurationService configurationService; public SshConnectionServiceImpl() { connectionSetups = new HashMap<String, SshConnectionSetup>(); } @Override public Session getAvtiveSshSession(String connectionId) { final SshConnectionSetup setup = connectionSetups.get(connectionId); if (setup == null) { log.warn(StringUtils.format(NO_SSH_CONNECTION_WITH_ID_S_CONFIGURED, connectionId)); return null; } return setup.getSession(); } @Override public String addSshConnection(String displayName, String destinationHost, int port, String sshAuthUser, String keyfileLocation, boolean usePassphrase, boolean connectImmediately) { String connectionId = UUID.randomUUID().toString(); SshConnectionListener listenerAdapter = defineListenerForSSHConnectionSetup(); final SshConnectionSetupImpl newSetup; newSetup = new SshConnectionSetupImpl(connectionId, displayName, destinationHost, port, sshAuthUser, keyfileLocation, usePassphrase, false, connectImmediately, listenerAdapter); if (newSetup != null) { synchronized (connectionSetups) { connectionSetups.put(connectionId, newSetup); final Collection<SshConnectionSetup> snapshot = Collections.unmodifiableCollection(connectionSetups.values()); callbackManager.enqueueCallback(new AsyncCallback<SshConnectionListener>() { @Override public void performCallback(SshConnectionListener listener) { listener.onCollectionChanged(snapshot); } }); } } return connectionId; } private SshConnectionListener defineListenerForSSHConnectionSetup() { SshConnectionListener listenerAdapter = new SshConnectionListenerAdapter() { @Override public void onConnectionAttemptFailed(final SshConnectionSetup setup, final String reason, final boolean firstConsecutiveFailure, final boolean willAutoRetry) { callbackManager.enqueueCallback(new AsyncCallback<SshConnectionListener>() { @Override public void performCallback(SshConnectionListener listener) { listener.onConnectionAttemptFailed(setup, reason, firstConsecutiveFailure, willAutoRetry); } }); } @Override public void onConnectionClosed(final SshConnectionSetup setup, final boolean willAutoRetry) { callbackManager.enqueueCallback(new AsyncCallback<SshConnectionListener>() { @Override public void performCallback(SshConnectionListener listener) { listener.onConnectionClosed(setup, willAutoRetry); } }); } @Override public void onConnected(final SshConnectionSetup setup) { callbackManager.enqueueCallback(new AsyncCallback<SshConnectionListener>() { @Override public void performCallback(SshConnectionListener listener) { listener.onConnected(setup); } }); } @Override public void onCreated(final SshConnectionSetup setup) { callbackManager.enqueueCallback(new AsyncCallback<SshConnectionListener>() { @Override public void performCallback(SshConnectionListener listener) { listener.onCreated(setup); } }); } }; return listenerAdapter; } @Override public boolean isConnected(String connectionId) { return connectionSetups.get(connectionId).isConnected(); } @Override public Session connectSession(String connectionId) { String passphrase = ""; if (connectionSetups.get(connectionId).getUsePassphrase()) { // Retreive passphrase from secure store. passphrase = retreiveSshConnectionPassword(connectionId); } return connectSession(connectionId, passphrase); } @Override public Session connectSession(String connectionId, String passphrase) { final SshConnectionSetup sshConnectionSetup = connectionSetups.get(connectionId); if (sshConnectionSetup == null) { log.warn(StringUtils.format(NO_SSH_CONNECTION_WITH_ID_S_CONFIGURED, connectionId)); return null; } return sshConnectionSetup.connect(passphrase); } @Override public void disconnectSession(String connectionId) { final SshConnectionSetup sshConnectionSetup = connectionSetups.get(connectionId); if (sshConnectionSetup == null) { log.warn(StringUtils.format(NO_SSH_CONNECTION_WITH_ID_S_CONFIGURED, connectionId)); return; } sshConnectionSetup.disconnect(); } @Override public void disposeConnection(String connectionId) { final SshConnectionSetup setup = connectionSetups.get(connectionId); if (setup == null) { log.warn(StringUtils.format(NO_SSH_CONNECTION_WITH_ID_S_CONFIGURED, connectionId)); return; } if (setup.isConnected()) { setup.disconnect(); } synchronized (connectionSetups) { connectionSetups.remove(connectionId); final Collection<SshConnectionSetup> snapshot = Collections.unmodifiableCollection(connectionSetups.values()); callbackManager.enqueueCallback(new AsyncCallback<SshConnectionListener>() { @Override public void performCallback(SshConnectionListener listener) { listener.onDisposed(setup); listener.onCollectionChanged(snapshot); } }); } } @Override public SshConnectionSetup getConnectionSetup(String connnectionId) { return connectionSetups.get(connnectionId); } @Override public Collection<SshConnectionSetup> getAllSshConnectionSetups() { return Collections.unmodifiableCollection(connectionSetups.values()); } @Override public Map<String, SshConnectionSetup> getAllActiveSshConnectionSetups() { Map<String, SshConnectionSetup> activeConnections = new HashMap<String, SshConnectionSetup>(); for (SshConnectionSetup connection : connectionSetups.values()) { if (connection.isConnected()) { activeConnections.put(connection.getId(), connection); } } return activeConnections; } /** * Adds a {@link SshConnectionListener}. * * @param listener The listener. */ public void addListener(SshConnectionListener listener) { callbackManager.addListener(listener); } /** * Removes a {@link SshConnectionListener}. * * @param listener The listener. */ public void removeListener(SshConnectionListener listener) { callbackManager.removeListener(listener); } @Override public void editSshConnection(SshConnectionContext context) { SshConnectionListener listenerAdapter = defineListenerForSSHConnectionSetup(); final SshConnectionSetupImpl newSetup; newSetup = new SshConnectionSetupImpl(context.getId(), context.getDisplayName(), context.getDestinationHost(), context.getPort(), context.getSshAuthUser(), context.getKeyfileLocation(), context.isUsePassphrase(), false, context.isConnectImmediately(), listenerAdapter); if (newSetup != null) { synchronized (connectionSetups) { connectionSetups.put(context.getId(), newSetup); final Collection<SshConnectionSetup> snapshot = Collections.unmodifiableCollection(connectionSetups.values()); callbackManager.enqueueCallback(new AsyncCallback<SshConnectionListener>() { @Override public void performCallback(SshConnectionListener listener) { listener.onCollectionChanged(snapshot); } }); } } } @Override public Collection<String> getAllActiveSshConnectionSetupIds() { return getAllActiveSshConnectionSetups().keySet(); } /** * OSGi-DS lifecycle method. */ public void activate() { ConcurrencyUtils.getAsyncTaskService().execute(new Runnable() { @Override @TaskDescription("Client-Side Remote Access: Add pre-configured SSH connections") public void run() { addInitialSshConfigs(configurationService.getInitialSSHConnectionConfigs()); } }); } private void addInitialSshConfigs(List<InitialSshConnectionConfig> configs) { for (InitialSshConnectionConfig config : configs) { SshConnectionSetup setup = new SshConnectionSetupImpl(config.getId(), config.getDisplayName(), config.getHost(), config.getPort(), config.getUser(), config.getKeyFileLocation(), config.getUsePassphrase(), false, false, defineListenerForSSHConnectionSetup()); connectionSetups.put(config.getId(), setup); } } /** * OSGI bind method. * * @param service The service to bind. */ public void bindNodeConfigurationService(NodeConfigurationService service) { this.configurationService = service; } private void storeSshConnectionPassword(String connectionId, String password) { try { ISecurePreferences prefs = SecurePreferencesFactory.getSecurePreferencesStore(); ISecurePreferences node = prefs.node(SshConnectionConstants.SSH_CONNECTIONS_PASSWORDS_NODE); node.put(connectionId, password, true); } catch (IOException | StorageException e) { log.error("Could not store password: " + e); } } private void removeSshConnectionPassword(String connectionId) { try { ISecurePreferences prefs = SecurePreferencesFactory.getSecurePreferencesStore(); ISecurePreferences node = prefs.node(SshConnectionConstants.SSH_CONNECTIONS_PASSWORDS_NODE); node.remove(connectionId); } catch (IOException e) { log.error("Could not remove password: " + e); } } @Override public String retreiveSshConnectionPassword(String connectionId) { String passphrase = null; try { ISecurePreferences prefs = SecurePreferencesFactory.getSecurePreferencesStore(); ISecurePreferences node = prefs.node(SshConnectionConstants.SSH_CONNECTIONS_PASSWORDS_NODE); passphrase = node.get(connectionId, null); } catch (IOException | StorageException e) { log.error("Could not retreive password: " + e); return null; } return passphrase; } @Override public void setAuthPhraseForSshConnection(String id, String sshAuthPassPhrase, boolean storePassphrase) { SshConnectionListener listenerAdapter = defineListenerForSSHConnectionSetup(); final SshConnectionSetup oldSetup = connectionSetups.get(id); final SshConnectionSetupImpl newSetup; newSetup = new SshConnectionSetupImpl(id, oldSetup.getDisplayName(), oldSetup.getHost(), oldSetup.getPort(), oldSetup.getUsername(), oldSetup.getKeyfileLocation(), oldSetup.getUsePassphrase(), storePassphrase, oldSetup.getConnectOnStartUp(), listenerAdapter); if (newSetup != null) { synchronized (connectionSetups) { connectionSetups.put(id, newSetup); final Collection<SshConnectionSetup> snapshot = Collections.unmodifiableCollection(connectionSetups.values()); callbackManager.enqueueCallback(new AsyncCallback<SshConnectionListener>() { @Override public void performCallback(SshConnectionListener listener) { listener.onCollectionChanged(snapshot); } }); } if (storePassphrase) { storeSshConnectionPassword(id, sshAuthPassPhrase); } else if (oldSetup.getStorePassphrase()) { // Remove old stored password, if one exists. removeSshConnectionPassword(id); } } } }