Bump odlparent to 6.0.0
[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 java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.io.IOException;
15 import java.net.InetSocketAddress;
16 import java.net.SocketAddress;
17 import java.security.PublicKey;
18 import org.apache.sshd.client.SshClient;
19 import org.apache.sshd.client.future.AuthFuture;
20 import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
21 import org.apache.sshd.client.session.ClientSession;
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.session.Session;
27 import org.apache.sshd.common.session.SessionListener;
28 import org.apache.sshd.netty.NettyIoServiceFactory;
29 import org.opendaylight.netconf.callhome.protocol.CallHomeSessionContext.Factory;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 public class NetconfCallHomeServer implements AutoCloseable, ServerKeyVerifier {
34
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 SshClient client;
44
45     NetconfCallHomeServer(final SshClient sshClient, final CallHomeAuthorizationProvider authProvider,
46             final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder) {
47         this(sshClient, authProvider, factory, socketAddress, recorder,
48             new NettyIoServiceFactory(factory.getNettyGroup()));
49     }
50
51     @VisibleForTesting
52     NetconfCallHomeServer(final SshClient sshClient, final CallHomeAuthorizationProvider authProvider,
53             final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder,
54             final IoServiceFactory serviceFactory) {
55         this.client = requireNonNull(sshClient);
56         this.authProvider = requireNonNull(authProvider);
57         this.sessionFactory = requireNonNull(factory);
58         this.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 SessionFactory(sshClient));
66     }
67
68     @VisibleForTesting
69     SshClient 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                 ClientSession clientSession = (ClientSession) session;
78                 LOG.debug("SSH session {} event {}", session, event);
79                 switch (event) {
80                     case KeyEstablished:
81                         doAuth(clientSession);
82                         break;
83                     case Authenticated:
84                         CallHomeSessionContext.getFrom(clientSession).openNetconfChannel();
85                         break;
86                     default:
87                         break;
88                 }
89             }
90
91             @Override
92             public void sessionCreated(final Session session) {
93                 LOG.debug("SSH session {} created", session);
94             }
95
96             @Override
97             public void sessionClosed(final Session session) {
98                 CallHomeSessionContext ctx = CallHomeSessionContext.getFrom((ClientSession) session);
99                 if (ctx != null) {
100                     ctx.removeSelf();
101                 }
102                 LOG.debug("SSH Session {} closed", session);
103             }
104
105             private void doAuth(final ClientSession session) {
106                 try {
107                     final AuthFuture authFuture = CallHomeSessionContext.getFrom(session).authorize();
108                     authFuture.addListener(newAuthSshFutureListener(session));
109                 } catch (IOException e) {
110                     LOG.error("Failed to authorize session {}", session, e);
111                 }
112             }
113         };
114     }
115
116     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
117             justification = "https://github.com/spotbugs/spotbugs/issues/811")
118     private SshFutureListener<AuthFuture> newAuthSshFutureListener(final ClientSession session) {
119         final PublicKey serverKey = session.getKex().getServerKey();
120
121         return new SshFutureListener<AuthFuture>() {
122             @Override
123             public void operationComplete(final AuthFuture authFuture) {
124                 if (authFuture.isSuccess()) {
125                     onSuccess();
126                 } else if (authFuture.isFailure()) {
127                     onFailure(authFuture.getException());
128                 } else if (authFuture.isCanceled()) {
129                     onCanceled();
130                 }
131                 authFuture.removeListener(this);
132             }
133
134             private void onSuccess() {
135                 LOG.debug("Authorize success");
136             }
137
138             private void onFailure(final Throwable throwable) {
139                 LOG.error("Authorize failed for session {}", session, throwable);
140                 recorder.reportFailedAuth(serverKey);
141                 session.close(true);
142             }
143
144             private void onCanceled() {
145                 LOG.warn("Authorize cancelled");
146                 session.close(true);
147             }
148         };
149     }
150
151     @Override
152     public boolean verifyServerKey(final ClientSession sshClientSession, final SocketAddress remoteAddress,
153             final PublicKey serverKey) {
154         final CallHomeAuthorization authorization = authProvider.provideAuth(remoteAddress, serverKey);
155         // server is not authorized
156         if (!authorization.isServerAllowed()) {
157             LOG.info("Incoming session {} was rejected by Authorization Provider.", sshClientSession);
158             return false;
159         }
160         CallHomeSessionContext session = sessionFactory.createIfNotExists(
161             sshClientSession, authorization, remoteAddress);
162         // Session was created, session with same name does not exists
163         if (session != null) {
164             return true;
165         }
166         // Session was not created, session with same name exists
167         LOG.info("Incoming session {} was rejected. Session with same name {} is already active.",
168             sshClientSession, authorization.getSessionName());
169         return false;
170     }
171
172     public void bind() throws IOException {
173         try {
174             client.start();
175             acceptor.bind(bindAddress);
176         } catch (IOException e) {
177             LOG.error("Unable to start NETCONF CallHome Service on {}", bindAddress, e);
178             throw e;
179         }
180     }
181
182     @Override
183     public void close() {
184         acceptor.close(true);
185         serviceFactory.close(true);
186     }
187 }