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