cf6c310c5a146abec2834d5d0cd406dece6f758a
[netconf.git] / netconf / callhome-protocol / src / main / java / org / opendaylight / netconf / callhome / protocol / NetconfCallHomeServer.java
1 /*
2  * Copyright (c) 2016 Brocade Communication Systems and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.callhome.protocol;
9
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
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;
33
34 public final class NetconfCallHomeServer implements AutoCloseable, ServerKeyVerifier {
35     private static final Logger LOG = LoggerFactory.getLogger(NetconfCallHomeServer.class);
36
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;
44
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()));
49     }
50
51     @VisibleForTesting
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);
61
62         sshClient.setServerKeyVerifier(this);
63         sshClient.addSessionListener(createSessionListener());
64
65         acceptor = serviceFactory.createAcceptor(new NetconfSessionFactory(sshClient));
66     }
67
68     @VisibleForTesting
69     NetconfSshClient getClient() {
70         return client;
71     }
72
73     SessionListener createSessionListener() {
74         return new SessionListener() {
75             @Override
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);
80                 switch (event) {
81                     case KeyEstablished:
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);
85                         }
86                         break;
87                     case Authenticated:
88                         CallHomeSessionContext.getFrom(clientSession).openNetconfChannel();
89                         break;
90                     default:
91                         break;
92                 }
93             }
94
95             @Override
96             public void sessionCreated(final Session session) {
97                 LOG.debug("SSH session {} created", session);
98             }
99
100             @Override
101             public void sessionClosed(final Session session) {
102                 verify(session instanceof NettyAwareClientSession, "Unexpected session %s", session);
103                 final CallHomeSessionContext ctx = CallHomeSessionContext.getFrom((NettyAwareClientSession) session);
104                 if (ctx != null) {
105                     ctx.removeSelf();
106                 }
107                 LOG.debug("SSH Session {} closed", session);
108             }
109
110             private void doAuth(final ClientSession session) {
111                 try {
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);
116                 }
117             }
118         };
119     }
120
121     private SshFutureListener<AuthFuture> newAuthSshFutureListener(final ClientSession session) {
122         final PublicKey serverKey = session.getServerKey();
123
124         return new SshFutureListener<>() {
125             @Override
126             public void operationComplete(final AuthFuture authFuture) {
127                 if (authFuture.isSuccess()) {
128                     onSuccess();
129                 } else if (authFuture.isFailure()) {
130                     onFailure(authFuture.getException());
131                 } else if (authFuture.isCanceled()) {
132                     onCanceled();
133                 }
134                 authFuture.removeListener(this);
135             }
136
137             private void onSuccess() {
138                 LOG.debug("Authorize success");
139             }
140
141             private void onFailure(final Throwable throwable) {
142                 LOG.error("Authorize failed for session {}", session, throwable);
143                 recorder.reportFailedAuth(serverKey);
144                 session.close(true);
145             }
146
147             private void onCanceled() {
148                 LOG.warn("Authorize cancelled");
149                 session.close(true);
150             }
151         };
152     }
153
154     @Override
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);
161             return false;
162         }
163
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());
168             return false;
169         }
170
171         // Session was created, session with same name does not exist
172         LOG.debug("Incoming session {} was successfully verified.", sshClientSession);
173         return true;
174     }
175
176     public void bind() throws IOException {
177         try {
178             client.start();
179             acceptor.bind(bindAddress);
180         } catch (IOException e) {
181             LOG.error("Unable to start NETCONF CallHome Service on {}", bindAddress, e);
182             throw e;
183         }
184     }
185
186     @Override
187     public void close() {
188         acceptor.close(true);
189         serviceFactory.close(true);
190     }
191 }