c519d2bb7b30ab504880a30099ae78d2fb4cf931
[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 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;
33
34 public class NetconfCallHomeServer implements AutoCloseable, ServerKeyVerifier {
35
36     private static final Logger LOG = LoggerFactory.getLogger(NetconfCallHomeServer.class);
37
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;
45
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()));
50     }
51
52     @VisibleForTesting
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);
62
63         sshClient.setServerKeyVerifier(this);
64         sshClient.addSessionListener(createSessionListener());
65
66         acceptor = serviceFactory.createAcceptor(new SessionFactory(sshClient));
67     }
68
69     @VisibleForTesting
70     SshClient getClient() {
71         return client;
72     }
73
74     SessionListener createSessionListener() {
75         return new SessionListener() {
76             @Override
77             public void sessionEvent(final Session session, final Event event) {
78                 ClientSession clientSession = (ClientSession) session;
79                 LOG.debug("SSH session {} event {}", session, event);
80                 switch (event) {
81                     case KeyEstablished:
82                         doAuth(clientSession);
83                         break;
84                     case Authenticated:
85                         doPostAuth(clientSession);
86                         break;
87                     default:
88                         break;
89                 }
90             }
91
92             @Override
93             public void sessionCreated(final Session session) {
94                 LOG.debug("SSH session {} created", session);
95             }
96
97             @Override
98             public void sessionClosed(final Session session) {
99                 CallHomeSessionContext ctx = CallHomeSessionContext.getFrom((ClientSession) session);
100                 if (ctx != null) {
101                     ctx.removeSelf();
102                 }
103                 LOG.debug("SSH Session {} closed", session);
104             }
105         };
106     }
107
108     private static void doPostAuth(final ClientSession session) {
109         CallHomeSessionContext.getFrom(session).openNetconfChannel();
110     }
111
112     private void doAuth(final ClientSession session) {
113         try {
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);
118         }
119     }
120
121     private SshFutureListener<AuthFuture> newAuthSshFutureListener(final ClientSession session) {
122         return new SshFutureListener<AuthFuture>() {
123             @Override
124             public void operationComplete(final AuthFuture authFuture) {
125                 if (authFuture.isSuccess()) {
126                     onSuccess();
127                 } else if (authFuture.isFailure()) {
128                     onFailure(authFuture.getException());
129                 } else if (authFuture.isCanceled()) {
130                     onCanceled();
131                 }
132                 authFuture.removeListener(this);
133             }
134
135             private void onSuccess() {
136                 LOG.debug("Authorize success");
137             }
138
139             private void onFailure(final Throwable throwable) {
140                 ClientSessionImpl impl = (ClientSessionImpl) session;
141                 LOG.error("Authorize failed for session {}", session, throwable);
142
143                 KeyExchange kex = impl.getKex();
144                 PublicKey key = kex.getServerKey();
145                 recorder.reportFailedAuth(key);
146
147                 session.close(true);
148             }
149
150             private void onCanceled() {
151                 LOG.warn("Authorize cancelled");
152                 session.close(true);
153             }
154         };
155     }
156
157     @Override
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);
164             return false;
165         }
166         CallHomeSessionContext session = sessionFactory.createIfNotExists(
167             sshClientSession, authorization, remoteAddress);
168         // Session was created, session with same name does not exists
169         if (session != null) {
170             return true;
171         }
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());
175         return false;
176     }
177
178     public void bind() throws IOException {
179         try {
180             client.start();
181             acceptor.bind(bindAddress);
182         } catch (IOException e) {
183             LOG.error("Unable to start NETCONF CallHome Service on {}", bindAddress, e);
184             throw e;
185         }
186     }
187
188     @Override
189     public void close() {
190         acceptor.close(true);
191         serviceFactory.close(true);
192     }
193 }