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
8 package org.opendaylight.netconf.callhome.protocol;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.annotations.VisibleForTesting;
13 import java.io.IOException;
14 import java.net.InetSocketAddress;
15 import java.net.SocketAddress;
16 import java.security.PublicKey;
17 import org.apache.sshd.client.SshClient;
18 import org.apache.sshd.client.future.AuthFuture;
19 import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
20 import org.apache.sshd.client.session.ClientSession;
21 import org.apache.sshd.client.session.ClientSessionImpl;
22 import org.apache.sshd.client.session.SessionFactory;
23 import org.apache.sshd.common.future.SshFutureListener;
24 import org.apache.sshd.common.io.IoAcceptor;
25 import org.apache.sshd.common.io.IoServiceFactory;
26 import org.apache.sshd.common.kex.KeyExchange;
27 import org.apache.sshd.common.session.Session;
28 import org.apache.sshd.common.session.SessionListener;
29 import org.apache.sshd.netty.NettyIoServiceFactory;
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 CallHomeAuthorizationProvider authProvider;
39 private final IoServiceFactory serviceFactory;
40 private final InetSocketAddress bindAddress;
41 private final StatusRecorder recorder;
42 private final Factory sessionFactory;
43 private final IoAcceptor acceptor;
44 private final SshClient client;
46 NetconfCallHomeServer(final SshClient sshClient, final CallHomeAuthorizationProvider authProvider,
47 final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder) {
48 this(sshClient, authProvider, factory, socketAddress, recorder,
49 new NettyIoServiceFactory(factory.getNettyGroup()));
53 NetconfCallHomeServer(final SshClient sshClient, final CallHomeAuthorizationProvider authProvider,
54 final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder,
55 final IoServiceFactory serviceFactory) {
56 this.client = requireNonNull(sshClient);
57 this.authProvider = requireNonNull(authProvider);
58 this.sessionFactory = requireNonNull(factory);
59 this.bindAddress = socketAddress;
60 this.recorder = recorder;
61 this.serviceFactory = requireNonNull(serviceFactory);
63 sshClient.setServerKeyVerifier(this);
64 sshClient.addSessionListener(createSessionListener());
66 acceptor = serviceFactory.createAcceptor(new SessionFactory(sshClient));
70 SshClient getClient() {
74 SessionListener createSessionListener() {
75 return new SessionListener() {
77 public void sessionEvent(final Session session, final Event event) {
78 ClientSession clientSession = (ClientSession) session;
79 LOG.debug("SSH session {} event {}", session, event);
82 doAuth(clientSession);
85 doPostAuth(clientSession);
93 public void sessionCreated(final Session session) {
94 LOG.debug("SSH session {} created", session);
98 public void sessionClosed(final Session session) {
99 CallHomeSessionContext ctx = CallHomeSessionContext.getFrom((ClientSession) session);
103 LOG.debug("SSH Session {} closed", session);
108 private static void doPostAuth(final ClientSession session) {
109 CallHomeSessionContext.getFrom(session).openNetconfChannel();
112 private void doAuth(final ClientSession session) {
114 final AuthFuture authFuture = CallHomeSessionContext.getFrom(session).authorize();
115 authFuture.addListener(newAuthSshFutureListener(session));
116 } catch (IOException e) {
117 LOG.error("Failed to authorize session {}", session, e);
121 private SshFutureListener<AuthFuture> newAuthSshFutureListener(final ClientSession session) {
122 return new SshFutureListener<AuthFuture>() {
124 public void operationComplete(final AuthFuture authFuture) {
125 if (authFuture.isSuccess()) {
127 } else if (authFuture.isFailure()) {
128 onFailure(authFuture.getException());
129 } else if (authFuture.isCanceled()) {
132 authFuture.removeListener(this);
135 private void onSuccess() {
136 LOG.debug("Authorize success");
139 private void onFailure(final Throwable throwable) {
140 ClientSessionImpl impl = (ClientSessionImpl) session;
141 LOG.error("Authorize failed for session {}", session, throwable);
143 KeyExchange kex = impl.getKex();
144 PublicKey key = kex.getServerKey();
145 recorder.reportFailedAuth(key);
150 private void onCanceled() {
151 LOG.warn("Authorize cancelled");
158 public boolean verifyServerKey(final ClientSession sshClientSession, final SocketAddress remoteAddress,
159 final PublicKey serverKey) {
160 final CallHomeAuthorization authorization = authProvider.provideAuth(remoteAddress, serverKey);
161 // server is not authorized
162 if (!authorization.isServerAllowed()) {
163 LOG.info("Incoming session {} was rejected by Authorization Provider.", sshClientSession);
166 CallHomeSessionContext session = sessionFactory.createIfNotExists(
167 sshClientSession, authorization, remoteAddress);
168 // Session was created, session with same name does not exists
169 if (session != null) {
172 // Session was not created, session with same name exists
173 LOG.info("Incoming session {} was rejected. Session with same name {} is already active.",
174 sshClientSession, authorization.getSessionName());
178 public void bind() throws IOException {
181 acceptor.bind(bindAddress);
182 } catch (IOException e) {
183 LOG.error("Unable to start NETCONF CallHome Service on {}", bindAddress, e);
189 public void close() {
190 acceptor.close(true);
191 serviceFactory.close(true);