2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.netconf.ssh.osgi;
10 import static com.google.common.base.Preconditions.checkNotNull;
13 import java.io.FileInputStream;
14 import java.io.IOException;
15 import java.net.InetSocketAddress;
16 import org.apache.commons.io.FilenameUtils;
17 import org.apache.commons.io.IOUtils;
18 import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
19 import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
20 import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
21 import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
22 import org.opendaylight.controller.usermanager.IUserManager;
23 import org.osgi.framework.BundleActivator;
24 import org.osgi.framework.BundleContext;
25 import org.osgi.framework.ServiceReference;
26 import org.osgi.util.tracker.ServiceTracker;
27 import org.osgi.util.tracker.ServiceTrackerCustomizer;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
32 * Activator for netconf SSH bundle which creates SSH bridge between netconf client and netconf server. Activator
33 * starts SSH Server in its own thread. This thread is closed when activator calls stop() method. Server opens socket
34 * and listens for client connections. Each client connection creation is handled in separate
35 * {@link org.opendaylight.controller.netconf.ssh.threads.SocketThread} thread.
36 * This thread creates two additional threads {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}
37 * forwarding data from/to client.IOThread closes servers session and server connection when it gets -1 on input stream.
38 * {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}'s run method waits for -1 on input stream to finish.
39 * All threads are daemons.
41 public class NetconfSSHActivator implements BundleActivator{
43 private NetconfSSHServer server;
44 private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class);
45 private IUserManager iUserManager;
46 private BundleContext context = null;
48 private ServiceTrackerCustomizer<IUserManager, IUserManager> customizer = new ServiceTrackerCustomizer<IUserManager, IUserManager>(){
50 public IUserManager addingService(final ServiceReference<IUserManager> reference) {
51 logger.trace("Service {} added, let there be SSH bridge.", reference);
52 iUserManager = context.getService(reference);
54 onUserManagerFound(iUserManager);
55 } catch (final Exception e) {
56 logger.trace("Can't start SSH server due to {}",e);
61 public void modifiedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
62 logger.trace("Replacing modified service {} in netconf SSH.", reference);
63 server.addUserManagerService(service);
66 public void removedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
67 logger.trace("Removing service {} from netconf SSH. " +
68 "SSH won't authenticate users until IUserManager service will be started.", reference);
69 removeUserManagerService();
75 public void start(final BundleContext context) {
76 this.context = context;
77 listenForManagerService();
81 public void stop(BundleContext context) throws IOException {
84 logger.trace("Netconf SSH bridge is down ...");
87 private void startSSHServer() throws IOException {
88 checkNotNull(this.iUserManager, "No user manager service available.");
89 logger.trace("Starting netconf SSH bridge.");
90 final InetSocketAddress sshSocketAddress = NetconfConfigUtil.extractSSHNetconfAddress(context,
91 NetconfConfigUtil.DEFAULT_NETCONF_SSH_ADDRESS);
92 final InetSocketAddress tcpSocketAddress = NetconfConfigUtil.extractTCPNetconfClientAddress(context,
93 NetconfConfigUtil.DEFAULT_NETCONF_TCP_ADDRESS);
95 String path = FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(context));
98 throw new IllegalStateException("Missing netconf.ssh.pk.path key in configuration file.");
101 final File privateKeyFile = new File(path);
102 final String privateKeyPEMString;
103 if (privateKeyFile.exists() == false) {
104 // generate & save to file
106 privateKeyPEMString = PEMGenerator.generateTo(privateKeyFile);
107 } catch (Exception e) {
108 logger.error("Exception occurred while generating PEM string {}", e);
109 throw new IllegalStateException("Error generating RSA key from file " + path);
113 try (FileInputStream fis = new FileInputStream(path)) {
114 privateKeyPEMString = IOUtils.toString(fis);
115 } catch (final IOException e) {
116 logger.error("Error reading RSA key from file '{}'", path);
117 throw new IOException("Error reading RSA key from file " + path, e);
120 final AuthProvider authProvider = new AuthProvider(iUserManager, privateKeyPEMString);
121 this.server = NetconfSSHServer.start(sshSocketAddress.getPort(), tcpSocketAddress, authProvider);
123 final Thread serverThread = new Thread(server, "netconf SSH server thread");
124 serverThread.setDaemon(true);
125 serverThread.start();
126 logger.trace("Netconf SSH bridge up and running.");
129 private void onUserManagerFound(final IUserManager userManager) throws Exception{
130 if (server!=null && server.isUp()){
131 server.addUserManagerService(userManager);
136 private void removeUserManagerService(){
137 this.server.removeUserManagerService();
139 private void listenForManagerService(){
140 final ServiceTracker<IUserManager, IUserManager> listenerTracker = new ServiceTracker<>(context, IUserManager.class,customizer);
141 listenerTracker.open();