/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.embedded.ssh.internal; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.sshd.server.SshServer; import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; import de.rcenvironment.core.command.api.CommandExecutionService; import de.rcenvironment.core.configuration.CommandLineArguments; import de.rcenvironment.core.configuration.ConfigurationException; import de.rcenvironment.core.configuration.ConfigurationSegment; import de.rcenvironment.core.configuration.ConfigurationService; import de.rcenvironment.core.configuration.ConfigurationService.ConfigurablePathId; import de.rcenvironment.core.embedded.ssh.api.EmbeddedSshServerControl; import de.rcenvironment.core.embedded.ssh.api.ScpContextManager; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; /** * Implementation of an embedded SSH server with OSGi lifecycle methods. * * @author Sebastian Holtappels * @author Robert Mischke * @author Brigitte Boden (added public key authentication) */ public class EmbeddedSshServerImpl implements EmbeddedSshServerControl { private static final String HOST_KEY_STORAGE_FILE_NAME = "ssh_host_key.dat"; private ConfigurationService configurationService; private CommandExecutionService commandExecutionService; private SshAuthenticationManager authenticationManager; private SshConfiguration sshConfiguration; private ScpContextManager scpContextManager; private SshServer sshd; private boolean sshServerActive = false; private final Map<String, String> announcedVersionEntries = new HashMap<>(); private final Log logger = LogFactory.getLog(getClass()); /** * OSGi-DS life-cycle method. */ public void activate() { if (!CommandLineArguments.isConfigurationShellRequested()) { ConfigurationSegment configurationSegment = configurationService.getConfigurationSegment("sshServer"); try { sshConfiguration = new SshConfiguration(configurationSegment); } catch (ConfigurationException | IOException e) { sshConfiguration = new SshConfiguration(); logger.error(e.getMessage()); } ConcurrencyUtils.getAsyncTaskService().execute(new Runnable() { @Override @TaskDescription("Embedded SSH server startup") public void run() { performStartup(); } }); } } /** * OSGi-DS lifecycle method. */ public void deactivate() { performShutdown(); } @Override public synchronized void setAnnouncedVersionOrProperty(String key, String value) { announcedVersionEntries.put(key, value); if (sshServerActive) { updateServerBannerWithAnnouncementData(); } } private synchronized void performStartup() { sshServerActive = getActivationSettingFromConfig(sshConfiguration); if (sshServerActive) { authenticationManager = new SshAuthenticationManager(sshConfiguration); sshd = SshServer.setUpDefaultServer(); // TODO also use this to announce the RCE product version? updateServerBannerWithAnnouncementData(); sshd.setPasswordAuthenticator(authenticationManager); sshd.setPublickeyAuthenticator(authenticationManager); File hostKeyFilePath = new File(configurationService.getConfigurablePath(ConfigurablePathId.PROFILE_INTERNAL_DATA), HOST_KEY_STORAGE_FILE_NAME).getAbsoluteFile(); logger.debug("Using SSH server key storage " + hostKeyFilePath.getPath()); sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyFilePath)); sshd.setHost(sshConfiguration.getHost()); sshd.setPort(sshConfiguration.getPort()); // TODO review: why not use a single factory instance for both? sshd.setShellFactory(new CustomSshCommandFactory(authenticationManager, scpContextManager, commandExecutionService, sshConfiguration)); // don't use ScpCommandFactory. Delegate is not called! sshd.setCommandFactory(new CustomSshCommandFactory(authenticationManager, scpContextManager, commandExecutionService, sshConfiguration)); try { sshd.start(); logger.info(StringUtils.format("SSH server started on port %s (bound to IP %s)", sshConfiguration.getPort(), sshConfiguration.getHost())); } catch (IOException e) { logger.error( StringUtils.format("Failed to start embedded SSH server on port %s (attempted to bind to IP address %s)", sshConfiguration.getPort(), sshConfiguration.getHost()), e); } } else { logger.debug("Not running an SSH server as there is either no SSH configuration at all, " + "or the \"enabled\" property is not \"true\", or the configuration data (including account settings) has errors"); } } private synchronized void performShutdown() { sshServerActive = false; if (sshd != null) { try { sshd.stop(true); logger.debug("Embedded SSH server shut down"); } catch (IOException e) { logger.error("Exception during shutdown of embedded SSH server", e); } } } // note: should only be called from synchronized methods private void updateServerBannerWithAnnouncementData() { StringBuilder buffer = new StringBuilder(); buffer.append("RCE"); for (Entry<String, String> entry : announcedVersionEntries.entrySet()) { buffer.append(" "); buffer.append(entry.getKey()); buffer.append("/"); buffer.append(entry.getValue()); } sshd.getProperties().put(SshServer.SERVER_IDENTIFICATION, buffer.toString()); } private boolean getActivationSettingFromConfig(SshConfiguration currentConfig) { boolean result = false; if (currentConfig != null && currentConfig.isEnabled()) { result = currentConfig.validateConfiguration(logger); } return result; } /** * OSGi-DS injection method. * * @param newInstance the service instance to bind */ protected void bindScpContextManager(ScpContextManager newInstance) { this.scpContextManager = newInstance; } protected void bindConfigurationService(ConfigurationService newConfigurationService) { this.configurationService = newConfigurationService; } protected void bindCommandExecutionService(CommandExecutionService newService) { this.commandExecutionService = newService; } }