/* * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.netconf.ssh; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import io.netty.channel.EventLoopGroup; import java.io.IOException; import java.nio.channels.AsynchronousChannelGroup; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.sshd.common.FactoryManager; import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.RuntimeSshException; import org.apache.sshd.common.cipher.BuiltinCiphers; import org.apache.sshd.common.cipher.Cipher; import org.apache.sshd.common.io.IoAcceptor; import org.apache.sshd.common.io.IoConnector; import org.apache.sshd.common.io.IoHandler; import org.apache.sshd.common.io.IoServiceFactory; import org.apache.sshd.common.io.IoServiceFactoryFactory; import org.apache.sshd.common.io.nio2.Nio2Acceptor; import org.apache.sshd.common.io.nio2.Nio2Connector; import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory; import org.apache.sshd.common.util.closeable.AbstractCloseable; import org.apache.sshd.server.ServerFactoryManager; import org.apache.sshd.server.SshServer; /** * Proxy SSH server that just delegates decrypted content to a delegate server within same VM. * Implemented using Apache Mina SSH lib. */ public class SshProxyServer implements AutoCloseable { private final SshServer sshServer; private final ScheduledExecutorService minaTimerExecutor; private final EventLoopGroup clientGroup; private final IoServiceFactoryFactory nioServiceWithPoolFactoryFactory; private SshProxyServer(final ScheduledExecutorService minaTimerExecutor, final EventLoopGroup clientGroup, final IoServiceFactoryFactory serviceFactory) { this.minaTimerExecutor = minaTimerExecutor; this.clientGroup = clientGroup; this.nioServiceWithPoolFactoryFactory = serviceFactory; this.sshServer = SshServer.setUpDefaultServer(); } public SshProxyServer(final ScheduledExecutorService minaTimerExecutor, final EventLoopGroup clientGroup, final ExecutorService nioExecutor) { this(minaTimerExecutor, clientGroup, new NioServiceWithPoolFactoryFactory(nioExecutor)); } /** * Create a server with a shared {@link AsynchronousChannelGroup}. Unlike the other constructor, this does * not create a dedicated thread group, which is useful when you need to start a large number of servers and do * not want to have a thread group (and hence an anonyous thread) for each of them. */ @VisibleForTesting public SshProxyServer(final ScheduledExecutorService minaTimerExecutor, final EventLoopGroup clientGroup, final AsynchronousChannelGroup group) { this(minaTimerExecutor, clientGroup, new SharedNioServiceFactoryFactory(group)); } public void bind(final SshProxyServerConfiguration sshProxyServerConfiguration) throws IOException { sshServer.setHost(sshProxyServerConfiguration.getBindingAddress().getHostString()); sshServer.setPort(sshProxyServerConfiguration.getBindingAddress().getPort()); //remove rc4 ciphers final List> cipherFactories = sshServer.getCipherFactories(); cipherFactories.removeIf(factory -> factory.getName().contains(BuiltinCiphers.arcfour128.getName()) || factory.getName().contains(BuiltinCiphers.arcfour256.getName())); sshServer.setPasswordAuthenticator( (username, password, session) -> sshProxyServerConfiguration.getAuthenticator().authenticated(username, password)); sshProxyServerConfiguration.getPublickeyAuthenticator().ifPresent(sshServer::setPublickeyAuthenticator); sshServer.setKeyPairProvider(sshProxyServerConfiguration.getKeyPairProvider()); sshServer.setIoServiceFactoryFactory(nioServiceWithPoolFactoryFactory); sshServer.setScheduledExecutorService(minaTimerExecutor); sshServer.getProperties().put(ServerFactoryManager.IDLE_TIMEOUT, String.valueOf(sshProxyServerConfiguration.getIdleTimeout())); sshServer.getProperties().put(ServerFactoryManager.AUTH_TIMEOUT, String.valueOf(sshProxyServerConfiguration.getIdleTimeout())); final RemoteNetconfCommand.NetconfCommandFactory netconfCommandFactory = new RemoteNetconfCommand.NetconfCommandFactory(clientGroup, sshProxyServerConfiguration.getLocalAddress()); sshServer.setSubsystemFactories(ImmutableList.of(netconfCommandFactory)); sshServer.start(); } @Override public void close() throws IOException { try { sshServer.stop(true); } finally { sshServer.close(true); } } private abstract static class AbstractNioServiceFactory extends AbstractCloseable implements IoServiceFactory { private final FactoryManager manager; private final AsynchronousChannelGroup group; AbstractNioServiceFactory(final FactoryManager manager, final AsynchronousChannelGroup group) { this.manager = requireNonNull(manager); this.group = requireNonNull(group); } final AsynchronousChannelGroup group() { return group; } @Override public final IoConnector createConnector(final IoHandler handler) { return new Nio2Connector(manager, handler, group); } @Override public final IoAcceptor createAcceptor(final IoHandler handler) { return new Nio2Acceptor(manager, handler, group); } } /** * Based on Nio2ServiceFactory with one addition: injectable executor. */ private static final class NioServiceWithPoolFactory extends AbstractNioServiceFactory { NioServiceWithPoolFactory(final FactoryManager manager, final AsynchronousChannelGroup group) { super(manager, group); } @SuppressWarnings("checkstyle:IllegalCatch") @Override protected void doCloseImmediately() { try { group().shutdownNow(); group().awaitTermination(5, TimeUnit.SECONDS); } catch (final Exception e) { log.debug("Exception caught while closing channel group", e); } finally { super.doCloseImmediately(); } } } private static final class NioServiceWithPoolFactoryFactory extends Nio2ServiceFactoryFactory { private final ExecutorService nioExecutor; NioServiceWithPoolFactoryFactory(final ExecutorService nioExecutor) { this.nioExecutor = nioExecutor; } @Override public IoServiceFactory create(final FactoryManager manager) { try { return new NioServiceWithPoolFactory(manager, AsynchronousChannelGroup.withThreadPool(nioExecutor)); } catch (final IOException e) { throw new RuntimeSshException("Failed to create channel group", e); } } } private static final class SharedNioServiceFactory extends AbstractNioServiceFactory { SharedNioServiceFactory(final FactoryManager manager, final AsynchronousChannelGroup group) { super(manager, group); } } private static final class SharedNioServiceFactoryFactory extends Nio2ServiceFactoryFactory { private final AsynchronousChannelGroup group; SharedNioServiceFactoryFactory(final AsynchronousChannelGroup group) { this.group = requireNonNull(group); } @Override public IoServiceFactory create(final FactoryManager manager) { return new SharedNioServiceFactory(manager, group); } } }