All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.netconf.callhome.protocol.NetconfCallHomeServer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2016 Brocade Communication Systems and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.netconf.callhome.protocol;

import static java.util.Objects.requireNonNull;

import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.PublicKey;
import org.opendaylight.netconf.callhome.protocol.CallHomeSessionContext.Factory;
import org.opendaylight.netconf.shaded.sshd.client.SshClient;
import org.opendaylight.netconf.shaded.sshd.client.future.AuthFuture;
import org.opendaylight.netconf.shaded.sshd.client.keyverifier.ServerKeyVerifier;
import org.opendaylight.netconf.shaded.sshd.client.session.ClientSession;
import org.opendaylight.netconf.shaded.sshd.client.session.SessionFactory;
import org.opendaylight.netconf.shaded.sshd.common.future.SshFutureListener;
import org.opendaylight.netconf.shaded.sshd.common.io.IoAcceptor;
import org.opendaylight.netconf.shaded.sshd.common.io.IoServiceFactory;
import org.opendaylight.netconf.shaded.sshd.common.session.Session;
import org.opendaylight.netconf.shaded.sshd.common.session.SessionListener;
import org.opendaylight.netconf.shaded.sshd.netty.NettyIoServiceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class NetconfCallHomeServer implements AutoCloseable, ServerKeyVerifier {
    private static final Logger LOG = LoggerFactory.getLogger(NetconfCallHomeServer.class);

    private final CallHomeAuthorizationProvider authProvider;
    private final IoServiceFactory serviceFactory;
    private final InetSocketAddress bindAddress;
    private final StatusRecorder recorder;
    private final Factory sessionFactory;
    private final IoAcceptor acceptor;
    private final SshClient client;

    NetconfCallHomeServer(final SshClient sshClient, final CallHomeAuthorizationProvider authProvider,
            final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder) {
        this(sshClient, authProvider, factory, socketAddress, recorder,
            new NettyIoServiceFactory(sshClient, factory.getNettyGroup()));
    }

    @VisibleForTesting
    NetconfCallHomeServer(final SshClient sshClient, final CallHomeAuthorizationProvider authProvider,
            final Factory factory, final InetSocketAddress socketAddress, final StatusRecorder recorder,
            final IoServiceFactory serviceFactory) {
        client = requireNonNull(sshClient);
        this.authProvider = requireNonNull(authProvider);
        sessionFactory = requireNonNull(factory);
        bindAddress = socketAddress;
        this.recorder = recorder;
        this.serviceFactory = requireNonNull(serviceFactory);

        sshClient.setServerKeyVerifier(this);
        sshClient.addSessionListener(createSessionListener());

        acceptor = serviceFactory.createAcceptor(new SessionFactory(sshClient));
    }

    @VisibleForTesting
    SshClient getClient() {
        return client;
    }

    SessionListener createSessionListener() {
        return new SessionListener() {
            @Override
            public void sessionEvent(final Session session, final Event event) {
                ClientSession clientSession = (ClientSession) session;
                LOG.debug("SSH session {} event {}", session, event);
                switch (event) {
                    case KeyEstablished:
                        // Case of key re-exchange - if session is once authenticated, it does not need to be made again
                        if (!clientSession.isAuthenticated()) {
                            doAuth(clientSession);
                        }
                        break;
                    case Authenticated:
                        CallHomeSessionContext.getFrom(clientSession).openNetconfChannel();
                        break;
                    default:
                        break;
                }
            }

            @Override
            public void sessionCreated(final Session session) {
                LOG.debug("SSH session {} created", session);
            }

            @Override
            public void sessionClosed(final Session session) {
                CallHomeSessionContext ctx = CallHomeSessionContext.getFrom((ClientSession) session);
                if (ctx != null) {
                    ctx.removeSelf();
                }
                LOG.debug("SSH Session {} closed", session);
            }

            private void doAuth(final ClientSession session) {
                try {
                    final AuthFuture authFuture = CallHomeSessionContext.getFrom(session).authorize();
                    authFuture.addListener(newAuthSshFutureListener(session));
                } catch (IOException e) {
                    LOG.error("Failed to authorize session {}", session, e);
                }
            }
        };
    }

    private SshFutureListener newAuthSshFutureListener(final ClientSession session) {
        final PublicKey serverKey = session.getServerKey();

        return new SshFutureListener<>() {
            @Override
            public void operationComplete(final AuthFuture authFuture) {
                if (authFuture.isSuccess()) {
                    onSuccess();
                } else if (authFuture.isFailure()) {
                    onFailure(authFuture.getException());
                } else if (authFuture.isCanceled()) {
                    onCanceled();
                }
                authFuture.removeListener(this);
            }

            private void onSuccess() {
                LOG.debug("Authorize success");
            }

            private void onFailure(final Throwable throwable) {
                LOG.error("Authorize failed for session {}", session, throwable);
                recorder.reportFailedAuth(serverKey);
                session.close(true);
            }

            private void onCanceled() {
                LOG.warn("Authorize cancelled");
                session.close(true);
            }
        };
    }

    @Override
    public boolean verifyServerKey(final ClientSession sshClientSession, final SocketAddress remoteAddress,
            final PublicKey serverKey) {
        final CallHomeAuthorization authorization = authProvider.provideAuth(remoteAddress, serverKey);
        if (!authorization.isServerAllowed()) {
            // server is not authorized
            LOG.info("Incoming session {} was rejected by Authorization Provider.", sshClientSession);
            return false;
        }

        if (sessionFactory.createIfNotExists(sshClientSession, authorization) == null) {
            // Session was not created, session with same name exists
            LOG.info("Incoming session {} was rejected. Session with same name {} is already active.", sshClientSession,
                authorization.getSessionName());
            return false;
        }

        // Session was created, session with same name does not exist
        LOG.debug("Incoming session {} was successfully verified.", sshClientSession);
        return true;
    }

    public void bind() throws IOException {
        try {
            client.start();
            acceptor.bind(bindAddress);
        } catch (IOException e) {
            LOG.error("Unable to start NETCONF CallHome Service on {}", bindAddress, e);
            throw e;
        }
    }

    @Override
    public void close() {
        acceptor.close(true);
        serviceFactory.close(true);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy