BUG-1422 Introduce Call-Home functionality for the NETCONF Topology.
[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.common.Session;
22 import org.apache.sshd.common.SessionListener;
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.io.mina.MinaServiceFactory;
27 import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
28 import org.opendaylight.netconf.callhome.protocol.CallHomeSessionContext.Factory;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 public class NetconfCallHomeServer implements AutoCloseable, ServerKeyVerifier {
33
34     private static final Logger LOG = LoggerFactory.getLogger(NetconfCallHomeServer.class);
35
36     private final IoAcceptor acceptor;
37     private final SshClient client;
38     private final CallHomeAuthorizationProvider authProvider;
39     private final CallHomeSessionContext.Factory sessionFactory;
40     private final InetSocketAddress bindAddress;
41
42     NetconfCallHomeServer(SshClient sshClient, CallHomeAuthorizationProvider authProvider, Factory factory,
43             InetSocketAddress socketAddress) {
44         this.client = Preconditions.checkNotNull(sshClient);
45         this.authProvider = Preconditions.checkNotNull(authProvider);
46         this.sessionFactory = Preconditions.checkNotNull(factory);
47         this.bindAddress = socketAddress;
48
49         sshClient.setServerKeyVerifier(this);
50
51         SessionFactory clientSessions = new SessionFactory();
52         clientSessions.setClient(sshClient);
53         clientSessions.addListener(createSessionListener());
54
55         IoServiceFactory minaFactory = createServiceFactory(sshClient);
56         this.acceptor = minaFactory.createAcceptor(clientSessions);
57     }
58
59     IoServiceFactory createServiceFactory(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);
65         }
66     }
67
68
69     protected IoServiceFactory createMinaServiceFactory(SshClient sshClient) {
70         return new MinaServiceFactory(sshClient);
71     }
72
73     SessionListener createSessionListener() {
74         return new SessionListener() {
75
76             @Override
77             public void sessionEvent(Session session, Event event) {
78                 ClientSession cSession = (ClientSession) session;
79                 LOG.debug("SSH session {} event {}", session, event);
80                 switch (event) {
81                     case KeyEstablished:
82                         doAuth(cSession);
83                         break;
84                     case Authenticated:
85                         doPostAuth(cSession);
86                         break;
87                     default:
88                         break;
89                 }
90             }
91
92             @Override
93             public void sessionCreated(Session session) {
94                 LOG.debug("SSH session {} created", session);
95             }
96
97             @Override
98             public void sessionClosed(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 void doPostAuth(final ClientSession cSession) {
109         CallHomeSessionContext.getFrom(cSession).openNetconfChannel();
110     }
111
112     private void doAuth(final ClientSession cSession) {
113         try {
114             final AuthFuture authFuture = CallHomeSessionContext.getFrom(cSession).authorize();
115             authFuture.addListener(newAuthSshFutureListener(cSession));
116         } catch (IOException e) {
117             LOG.error("Failed to authorize session {}", cSession, e);
118         }
119     }
120
121     SshFutureListener<AuthFuture> newAuthSshFutureListener(final ClientSession cSession) {
122         return new SshFutureListener<AuthFuture>() {
123             @Override
124             public void operationComplete(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(Throwable throwable) {
140                 LOG.error("Failed to authorize session {}", cSession, throwable);
141                 cSession.close(true);
142             }
143
144             private void onCanceled() {
145                 LOG.warn("Authorize cancelled");
146                 cSession.close(true);
147             }
148         };
149     }
150
151     @Override
152     public boolean verifyServerKey(ClientSession sshClientSession, SocketAddress remoteAddress, PublicKey serverKey) {
153         final CallHomeAuthorization authorization = authProvider.provideAuth(remoteAddress, serverKey);
154         // server is not authorized
155         if (!authorization.isServerAllowed()) {
156             LOG.info("Incoming session {} was rejected by Authorization Provider.",sshClientSession);
157             return false;
158         }
159         CallHomeSessionContext session = sessionFactory.createIfNotExists(sshClientSession, authorization, remoteAddress);
160         // Session was created, session with same name does not exists
161         if(session != null) {
162             return true;
163         }
164         // Session was not created, session with same name exists
165         LOG.info("Incoming session {} was rejected. Session with same name {} is already active.",sshClientSession,authorization.getSessionName());
166         return false;
167     }
168
169     public void bind() throws IOException {
170         try {
171             client.start();
172             acceptor.bind(bindAddress);
173         } catch (IOException e) {
174             LOG.error("Unable to start NETCONF CallHome Service", e);
175             throw e;
176         }
177     }
178
179
180     @Override
181     public void close() throws Exception {
182         acceptor.close(true);
183     }
184 }