2 * Copyright (c) 2016 Brocade Communication Systems 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
9 package org.opendaylight.netconf.callhome.protocol;
11 import com.google.common.base.Preconditions;
12 import java.io.IOException;
13 import java.net.InetSocketAddress;
14 import java.net.SocketAddress;
15 import java.security.PublicKey;
16 import org.apache.sshd.client.SshClient;
17 import org.apache.sshd.client.future.AuthFuture;
18 import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
19 import org.apache.sshd.client.session.ClientSession;
20 import org.apache.sshd.client.session.ClientSessionImpl;
21 import org.apache.sshd.client.session.SessionFactory;
22 import org.apache.sshd.common.future.SshFutureListener;
23 import org.apache.sshd.common.io.IoAcceptor;
24 import org.apache.sshd.common.io.IoServiceFactory;
25 import org.apache.sshd.common.io.mina.MinaServiceFactory;
26 import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
27 import org.apache.sshd.common.kex.KeyExchange;
28 import org.apache.sshd.common.session.Session;
29 import org.apache.sshd.common.session.SessionListener;
30 import org.opendaylight.netconf.callhome.protocol.CallHomeSessionContext.Factory;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 public class NetconfCallHomeServer implements AutoCloseable, ServerKeyVerifier {
36 private static final Logger LOG = LoggerFactory.getLogger(NetconfCallHomeServer.class);
38 private final IoAcceptor acceptor;
39 private final SshClient client;
40 private final CallHomeAuthorizationProvider authProvider;
41 private final CallHomeSessionContext.Factory sessionFactory;
42 private final InetSocketAddress bindAddress;
43 private final StatusRecorder recorder;
45 NetconfCallHomeServer(final SshClient sshClient, final CallHomeAuthorizationProvider authProvider,
46 final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder) {
47 this.client = Preconditions.checkNotNull(sshClient);
48 this.authProvider = Preconditions.checkNotNull(authProvider);
49 this.sessionFactory = Preconditions.checkNotNull(factory);
50 this.bindAddress = socketAddress;
51 this.recorder = recorder;
53 sshClient.setServerKeyVerifier(this);
54 sshClient.addSessionListener(createSessionListener());
56 this.acceptor = createServiceFactory(sshClient).createAcceptor(new SessionFactory(sshClient));
59 private IoServiceFactory createServiceFactory(final SshClient sshClient) {
61 return createMinaServiceFactory(sshClient);
62 } catch (NoClassDefFoundError e) {
63 LOG.warn("Mina is not available, defaulting to NIO.");
64 return new Nio2ServiceFactory(sshClient, sshClient.getScheduledExecutorService(), false);
68 protected IoServiceFactory createMinaServiceFactory(final SshClient sshClient) {
69 return new MinaServiceFactory(sshClient, sshClient.getScheduledExecutorService(), false);
72 SessionListener createSessionListener() {
73 return new SessionListener() {
75 public void sessionEvent(final Session session, final Event event) {
76 ClientSession clientSession = (ClientSession) session;
77 LOG.debug("SSH session {} event {}", session, event);
80 doAuth(clientSession);
83 doPostAuth(clientSession);
91 public void sessionCreated(final Session session) {
92 LOG.debug("SSH session {} created", session);
96 public void sessionClosed(final Session session) {
97 CallHomeSessionContext ctx = CallHomeSessionContext.getFrom((ClientSession) session);
101 LOG.debug("SSH Session {} closed", session);
106 private static void doPostAuth(final ClientSession session) {
107 CallHomeSessionContext.getFrom(session).openNetconfChannel();
110 private void doAuth(final ClientSession session) {
112 final AuthFuture authFuture = CallHomeSessionContext.getFrom(session).authorize();
113 authFuture.addListener(newAuthSshFutureListener(session));
114 } catch (IOException e) {
115 LOG.error("Failed to authorize session {}", session, e);
119 private SshFutureListener<AuthFuture> newAuthSshFutureListener(final ClientSession session) {
120 return new SshFutureListener<AuthFuture>() {
122 public void operationComplete(final AuthFuture authFuture) {
123 if (authFuture.isSuccess()) {
125 } else if (authFuture.isFailure()) {
126 onFailure(authFuture.getException());
127 } else if (authFuture.isCanceled()) {
130 authFuture.removeListener(this);
133 private void onSuccess() {
134 LOG.debug("Authorize success");
137 private void onFailure(final Throwable throwable) {
138 ClientSessionImpl impl = (ClientSessionImpl) session;
139 LOG.error("Authorize failed for session {}", session, throwable);
141 KeyExchange kex = impl.getKex();
142 PublicKey key = kex.getServerKey();
143 recorder.reportFailedAuth(key);
148 private void onCanceled() {
149 LOG.warn("Authorize cancelled");
156 public boolean verifyServerKey(final ClientSession sshClientSession, final SocketAddress remoteAddress,
157 final PublicKey serverKey) {
158 final CallHomeAuthorization authorization = authProvider.provideAuth(remoteAddress, serverKey);
159 // server is not authorized
160 if (!authorization.isServerAllowed()) {
161 LOG.info("Incoming session {} was rejected by Authorization Provider.", sshClientSession);
164 CallHomeSessionContext session = sessionFactory.createIfNotExists(
165 sshClientSession, authorization, remoteAddress);
166 // Session was created, session with same name does not exists
167 if (session != null) {
170 // Session was not created, session with same name exists
171 LOG.info("Incoming session {} was rejected. Session with same name {} is already active.",
172 sshClientSession, authorization.getSessionName());
176 public void bind() throws IOException {
179 acceptor.bind(bindAddress);
180 } catch (IOException e) {
181 LOG.error("Unable to start NETCONF CallHome Service", e);
187 public void close() throws Exception {
188 acceptor.close(true);