Adjust to yangtools-2.0.0/odlparent-3.0.0 changes
[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
9 package org.opendaylight.netconf.callhome.protocol;
10
11 import com.google.common.base.Preconditions;
12 import java.io.IOException;
13 import java.net.InetSocketAddress;
14 import java.net.SocketAddress;
15 import java.security.PublicKey;
16 import org.apache.sshd.client.SshClient;
17 import org.apache.sshd.client.future.AuthFuture;
18 import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
19 import org.apache.sshd.client.session.ClientSession;
20 import org.apache.sshd.client.session.ClientSessionImpl;
21 import org.apache.sshd.client.session.SessionFactory;
22 import org.apache.sshd.common.future.SshFutureListener;
23 import org.apache.sshd.common.io.IoAcceptor;
24 import org.apache.sshd.common.io.IoServiceFactory;
25 import org.apache.sshd.common.io.mina.MinaServiceFactory;
26 import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
27 import org.apache.sshd.common.kex.KeyExchange;
28 import org.apache.sshd.common.session.Session;
29 import org.apache.sshd.common.session.SessionListener;
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 IoAcceptor acceptor;
39     private final SshClient client;
40     private final CallHomeAuthorizationProvider authProvider;
41     private final CallHomeSessionContext.Factory sessionFactory;
42     private final InetSocketAddress bindAddress;
43     private final StatusRecorder recorder;
44
45     NetconfCallHomeServer(final SshClient sshClient, final CallHomeAuthorizationProvider authProvider,
46             final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder) {
47         this.client = Preconditions.checkNotNull(sshClient);
48         this.authProvider = Preconditions.checkNotNull(authProvider);
49         this.sessionFactory = Preconditions.checkNotNull(factory);
50         this.bindAddress = socketAddress;
51         this.recorder = recorder;
52
53         sshClient.setServerKeyVerifier(this);
54         sshClient.addSessionListener(createSessionListener());
55
56         this.acceptor = createServiceFactory(sshClient).createAcceptor(new SessionFactory(sshClient));
57     }
58
59     private IoServiceFactory createServiceFactory(final SshClient sshClient) {
60         try {
61             return createMinaServiceFactory(sshClient);
62         } catch (NoClassDefFoundError e) {
63             LOG.warn("Mina is not available, defaulting to NIO.");
64             return new Nio2ServiceFactory(sshClient, sshClient.getScheduledExecutorService(), false);
65         }
66     }
67
68     protected IoServiceFactory createMinaServiceFactory(final SshClient sshClient) {
69         return new MinaServiceFactory(sshClient, sshClient.getScheduledExecutorService(), false);
70     }
71
72     SessionListener createSessionListener() {
73         return new SessionListener() {
74             @Override
75             public void sessionEvent(final Session session, final Event event) {
76                 ClientSession clientSession = (ClientSession) session;
77                 LOG.debug("SSH session {} event {}", session, event);
78                 switch (event) {
79                     case KeyEstablished:
80                         doAuth(clientSession);
81                         break;
82                     case Authenticated:
83                         doPostAuth(clientSession);
84                         break;
85                     default:
86                         break;
87                 }
88             }
89
90             @Override
91             public void sessionCreated(final Session session) {
92                 LOG.debug("SSH session {} created", session);
93             }
94
95             @Override
96             public void sessionClosed(final Session session) {
97                 CallHomeSessionContext ctx = CallHomeSessionContext.getFrom((ClientSession) session);
98                 if (ctx != null) {
99                     ctx.removeSelf();
100                 }
101                 LOG.debug("SSH Session {} closed", session);
102             }
103         };
104     }
105
106     private static void doPostAuth(final ClientSession session) {
107         CallHomeSessionContext.getFrom(session).openNetconfChannel();
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     private SshFutureListener<AuthFuture> newAuthSshFutureListener(final ClientSession session) {
120         return new SshFutureListener<AuthFuture>() {
121             @Override
122             public void operationComplete(final AuthFuture authFuture) {
123                 if (authFuture.isSuccess()) {
124                     onSuccess();
125                 } else if (authFuture.isFailure()) {
126                     onFailure(authFuture.getException());
127                 } else if (authFuture.isCanceled()) {
128                     onCanceled();
129                 }
130                 authFuture.removeListener(this);
131             }
132
133             private void onSuccess() {
134                 LOG.debug("Authorize success");
135             }
136
137             private void onFailure(final Throwable throwable) {
138                 ClientSessionImpl impl = (ClientSessionImpl) session;
139                 LOG.error("Authorize failed for session {}", session, throwable);
140
141                 KeyExchange kex = impl.getKex();
142                 PublicKey key = kex.getServerKey();
143                 recorder.reportFailedAuth(key);
144
145                 session.close(true);
146             }
147
148             private void onCanceled() {
149                 LOG.warn("Authorize cancelled");
150                 session.close(true);
151             }
152         };
153     }
154
155     @Override
156     public boolean verifyServerKey(final ClientSession sshClientSession, final SocketAddress remoteAddress,
157             final PublicKey serverKey) {
158         final CallHomeAuthorization authorization = authProvider.provideAuth(remoteAddress, serverKey);
159         // server is not authorized
160         if (!authorization.isServerAllowed()) {
161             LOG.info("Incoming session {} was rejected by Authorization Provider.", sshClientSession);
162             return false;
163         }
164         CallHomeSessionContext session = sessionFactory.createIfNotExists(
165             sshClientSession, authorization, remoteAddress);
166         // Session was created, session with same name does not exists
167         if (session != null) {
168             return true;
169         }
170         // Session was not created, session with same name exists
171         LOG.info("Incoming session {} was rejected. Session with same name {} is already active.",
172             sshClientSession, authorization.getSessionName());
173         return false;
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", e);
182             throw e;
183         }
184     }
185
186     @Override
187     public void close() throws Exception {
188         acceptor.close(true);
189     }
190 }