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 com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.VisibleForTesting;
14 import java.io.IOException;
15 import java.net.InetSocketAddress;
16 import java.net.SocketAddress;
17 import java.security.PublicKey;
18 import org.opendaylight.netconf.callhome.protocol.CallHomeSessionContext.Factory;
19 import org.opendaylight.netconf.nettyutil.handler.ssh.client.NetconfSessionFactory;
20 import org.opendaylight.netconf.nettyutil.handler.ssh.client.NetconfSshClient;
21 import org.opendaylight.netconf.nettyutil.handler.ssh.client.NettyAwareClientSession;
22 import org.opendaylight.netconf.shaded.sshd.client.future.AuthFuture;
23 import org.opendaylight.netconf.shaded.sshd.client.keyverifier.ServerKeyVerifier;
24 import org.opendaylight.netconf.shaded.sshd.client.session.ClientSession;
25 import org.opendaylight.netconf.shaded.sshd.common.future.SshFutureListener;
26 import org.opendaylight.netconf.shaded.sshd.common.io.IoAcceptor;
27 import org.opendaylight.netconf.shaded.sshd.common.io.IoServiceFactory;
28 import org.opendaylight.netconf.shaded.sshd.common.session.Session;
29 import org.opendaylight.netconf.shaded.sshd.common.session.SessionListener;
30 import org.opendaylight.netconf.shaded.sshd.netty.NettyIoServiceFactory;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 public final class NetconfCallHomeServer implements AutoCloseable, ServerKeyVerifier {
35 private static final Logger LOG = LoggerFactory.getLogger(NetconfCallHomeServer.class);
37 private final CallHomeAuthorizationProvider authProvider;
38 private final IoServiceFactory serviceFactory;
39 private final InetSocketAddress bindAddress;
40 private final StatusRecorder recorder;
41 private final Factory sessionFactory;
42 private final IoAcceptor acceptor;
43 private final NetconfSshClient client;
45 NetconfCallHomeServer(final NetconfSshClient sshClient, final CallHomeAuthorizationProvider authProvider,
46 final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder) {
47 this(sshClient, authProvider, factory, socketAddress, recorder,
48 new NettyIoServiceFactory(sshClient, factory.getNettyGroup()));
52 NetconfCallHomeServer(final NetconfSshClient sshClient, final CallHomeAuthorizationProvider authProvider,
53 final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder,
54 final IoServiceFactory serviceFactory) {
55 client = requireNonNull(sshClient);
56 this.authProvider = requireNonNull(authProvider);
57 sessionFactory = requireNonNull(factory);
58 bindAddress = socketAddress;
59 this.recorder = recorder;
60 this.serviceFactory = requireNonNull(serviceFactory);
62 sshClient.setServerKeyVerifier(this);
63 sshClient.addSessionListener(createSessionListener());
65 acceptor = serviceFactory.createAcceptor(new NetconfSessionFactory(sshClient));
69 NetconfSshClient getClient() {
73 SessionListener createSessionListener() {
74 return new SessionListener() {
76 public void sessionEvent(final Session session, final Event event) {
77 verify(session instanceof NettyAwareClientSession, "Unexpected session %s", session);
78 final NettyAwareClientSession clientSession = (NettyAwareClientSession) session;
79 LOG.debug("SSH session {} event {}", session, event);
82 // Case of key re-exchange - if session is once authenticated, it does not need to be made again
83 if (!clientSession.isAuthenticated()) {
84 doAuth(clientSession);
88 CallHomeSessionContext.getFrom(clientSession).openNetconfChannel();
96 public void sessionCreated(final Session session) {
97 LOG.debug("SSH session {} created", session);
101 public void sessionClosed(final Session session) {
102 verify(session instanceof NettyAwareClientSession, "Unexpected session %s", session);
103 final CallHomeSessionContext ctx = CallHomeSessionContext.getFrom((NettyAwareClientSession) session);
107 LOG.debug("SSH Session {} closed", session);
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);
121 private SshFutureListener<AuthFuture> newAuthSshFutureListener(final ClientSession session) {
122 final PublicKey serverKey = session.getServerKey();
124 return new SshFutureListener<>() {
126 public void operationComplete(final AuthFuture authFuture) {
127 if (authFuture.isSuccess()) {
129 } else if (authFuture.isFailure()) {
130 onFailure(authFuture.getException());
131 } else if (authFuture.isCanceled()) {
134 authFuture.removeListener(this);
137 private void onSuccess() {
138 LOG.debug("Authorize success");
141 private void onFailure(final Throwable throwable) {
142 LOG.error("Authorize failed for session {}", session, throwable);
143 recorder.reportFailedAuth(serverKey);
147 private void onCanceled() {
148 LOG.warn("Authorize cancelled");
155 public boolean verifyServerKey(final ClientSession sshClientSession, final SocketAddress remoteAddress,
156 final PublicKey serverKey) {
157 final CallHomeAuthorization authorization = authProvider.provideAuth(remoteAddress, serverKey);
158 if (!authorization.isServerAllowed()) {
159 // server is not authorized
160 LOG.info("Incoming session {} was rejected by Authorization Provider.", sshClientSession);
164 if (sessionFactory.createIfNotExists(sshClientSession, authorization) == null) {
165 // Session was not created, session with same name exists
166 LOG.info("Incoming session {} was rejected. Session with same name {} is already active.", sshClientSession,
167 authorization.getSessionName());
171 // Session was created, session with same name does not exist
172 LOG.debug("Incoming session {} was successfully verified.", sshClientSession);
176 public void bind() throws IOException {
179 acceptor.bind(bindAddress);
180 } catch (IOException e) {
181 LOG.error("Unable to start NETCONF CallHome Service on {}", bindAddress, e);
187 public void close() {
188 acceptor.close(true);
189 serviceFactory.close(true);