/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2009 Sun Microsystems, Inc. * Portions Copyright 2013-2015 ForgeRock AS. */ package org.opends.server.protocols.jmx; import java.io.IOException; import java.net.InetAddress; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.HashMap; import java.util.SortedSet; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLContext; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnectorServer; import javax.rmi.ssl.SslRMIClientSocketFactory; import org.opends.server.api.KeyManagerProvider; import org.opends.server.config.JMXMBean; import org.opends.server.core.DirectoryServer; import org.opends.server.extensions.NullKeyManagerProvider; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.opends.server.util.SelectableCertificateKeyManager; /** * The RMI connector class starts and stops the JMX RMI connector server. * There are 2 different connector servers * <ul> * <li> the RMI Client connector server, supporting TLS-encrypted. * communication, server authentication by certificate and client * authentication by providing appropriate LDAP credentials through * SASL/PLAIN. * <li> the RMI client connector server, supporting TLS-encrypted * communication, server authentication by certificate, client * authentication by certificate and identity assertion through SASL/PLAIN. * </ul> * <p> * Each connector is registered into the JMX MBean server. */ public class RmiConnector { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** * The MBean server used to handle JMX interaction. */ private MBeanServer mbs; /** * The associated JMX Connection Handler. */ private JmxConnectionHandler jmxConnectionHandler; /** * The name of the JMX connector with no SSL client * authentication. */ private String jmxRmiConnectorNoClientCertificateName; /** * The reference to the JMX connector client with no SSL client * authentication. */ protected JMXConnectorServer jmxRmiConnectorNoClientCertificate; /** * The reference to the JMX connector client with SSL client * authentication. */ private JMXConnectorServer jmxRmiConnectorClientCertificate; /** * The reference to authenticator. */ private RmiAuthenticator rmiAuthenticator; /** * The reference to the created RMI registry. */ private Registry registry; /** * The Underlying Socket factory. */ private OpendsRmiServerSocketFactory rmiSsf; /** * The RMI protocol version used by this connector. */ private String rmiVersion; // =================================================================== // CONSTRUCTOR // =================================================================== /** * Create a new instance of RmiConnector . * * @param mbs * The MBean server. * @param jmxConnectionHandler * The associated JMX Connection Handler */ public RmiConnector(MBeanServer mbs, JmxConnectionHandler jmxConnectionHandler) { this.mbs = mbs; this.jmxConnectionHandler = jmxConnectionHandler; String baseName = JMXMBean.getJmxName(jmxConnectionHandler .getComponentEntryDN()); jmxRmiConnectorNoClientCertificateName = baseName + "," + "Type=jmxRmiConnectorNoClientCertificateName"; } // =================================================================== // Initialization // =================================================================== /** * Activates the RMI Connectors. It starts the secure connectors. */ public void initialize() { try { startCommonRegistry(); // start the RMI connector (SSL + server authentication) startConnectorNoClientCertificate(); // start the RMI connector (SSL + server authentication + // client authentication + identity given part SASL/PLAIN) // TODO startConnectorClientCertificate(clientConnection); } catch (Exception e) { logger.traceException(e); throw new RuntimeException("Error while starting the RMI module : " + e.getMessage()); } if (logger.isTraceEnabled()) { logger.trace("RMI module started"); } } /** * Starts the common RMI registry. In order to provide RMI stub for * remote client, the JMX RMI connector should be register into an RMI * registry. Each server will maintain its own private one. * * @throws Exception * if the registry cannot be started */ private void startCommonRegistry() throws Exception { final InetAddress listenAddress = jmxConnectionHandler.getListenAddress(); int registryPort = jmxConnectionHandler.getListenPort(); // create our local RMI registry if it does not exist already if (logger.isTraceEnabled()) { logger.trace("start or reach an RMI registry on port %d", registryPort); } try { // TODO Not yet implemented: If the host has several interfaces if (registry == null) { rmiSsf = new OpendsRmiServerSocketFactory(listenAddress); registry = LocateRegistry.createRegistry(registryPort, null, rmiSsf); } } catch (RemoteException re) { // is the registry already created ? if (logger.isTraceEnabled()) { logger.trace("cannot create the RMI registry -> already done ?"); } try { // get a 'remote' reference on the registry Registry reg = LocateRegistry.getRegistry(registryPort); // 'ping' the registry reg.list(); registry = reg; } catch (Exception e) { if (logger.isTraceEnabled()) { // no 'valid' registry found on the specified port logger.trace("exception thrown while pinging the RMI registry"); // throw the original exception logger.traceException(re); } throw re; } // here the registry is ok even though // it was not created by this call if (logger.isTraceEnabled()) { logger.trace("RMI was registry already started"); } } } /** * Starts a secure RMI connector, with a client that doesn't have to * present a certificate, on the local MBean server. * This method assumes that the common registry was successfully * started. * <p> * If the connector is already started, this method simply returns * without doing anything. * * @throws Exception * if an error occurs */ private void startConnectorNoClientCertificate() throws Exception { try { // Environment map HashMap<String, Object> env = new HashMap<>(); // --------------------- // init an ssl context // --------------------- SslRMIClientSocketFactory rmiClientSockeyFactory = null; DirectoryRMIServerSocketFactory rmiServerSockeyFactory = null; if (jmxConnectionHandler.isUseSSL()) { if (logger.isTraceEnabled()) { logger.trace("SSL connection"); } // --------------------- // SERVER SIDE // --------------------- // Get a Server socket factory KeyManagerProvider provider = DirectoryServer .getKeyManagerProvider(jmxConnectionHandler .getKeyManagerProviderDN()); final KeyManager[] keyManagers; if (provider == null) { keyManagers = new NullKeyManagerProvider().getKeyManagers(); } else { final SortedSet<String> nicknames = jmxConnectionHandler.getSSLServerCertNicknames(); keyManagers = nicknames == null ? provider.getKeyManagers() : SelectableCertificateKeyManager.wrap(provider.getKeyManagers(), nicknames); } SSLContext ctx = SSLContext.getInstance("TLSv1"); ctx.init( keyManagers, null, null); SSLSocketFactory ssf = ctx.getSocketFactory(); // set the Server socket factory in the JMX map rmiServerSockeyFactory = new DirectoryRMIServerSocketFactory(ssf, false); env.put( "jmx.remote.rmi.server.socket.factory", rmiServerSockeyFactory); // --------------------- // CLIENT SIDE : Rmi stores the client stub in the // registry // --------------------- // Set the Client socket factory in the JMX map rmiClientSockeyFactory = new SslRMIClientSocketFactory(); env.put( "jmx.remote.rmi.client.socket.factory", rmiClientSockeyFactory); } else { if (logger.isTraceEnabled()) { logger.trace("UNSECURE CONNECTION"); } } // specify the rmi JMX authenticator to be used if (logger.isTraceEnabled()) { logger.trace("Add RmiAuthenticator into JMX map"); } rmiAuthenticator = new RmiAuthenticator(jmxConnectionHandler); env.put(JMXConnectorServer.AUTHENTICATOR, rmiAuthenticator); // Create the JMX Service URL String uri = "org.opends.server.protocols.jmx.client-unknown"; String serviceUrl = "service:jmx:rmi:///jndi/rmi://" + jmxConnectionHandler.getListenAddress().getHostName() + ":" + jmxConnectionHandler.getListenPort() + "/" + uri; JMXServiceURL url = new JMXServiceURL(serviceUrl); // Create and start the connector if (logger.isTraceEnabled()) { logger.trace("Create and start the JMX RMI connector"); } OpendsRMIJRMPServerImpl opendsRmiConnectorServer = new OpendsRMIJRMPServerImpl(jmxConnectionHandler.getRmiPort(), rmiClientSockeyFactory, rmiServerSockeyFactory, env); jmxRmiConnectorNoClientCertificate = new RMIConnectorServer(url, env, opendsRmiConnectorServer, mbs); jmxRmiConnectorNoClientCertificate.start(); // Register the connector into the RMI registry // TODO Should we do that? ObjectName name = new ObjectName(jmxRmiConnectorNoClientCertificateName); mbs.registerMBean(jmxRmiConnectorNoClientCertificate, name); rmiVersion = opendsRmiConnectorServer.getVersion(); if (logger.isTraceEnabled()) { logger.trace("JMX RMI connector Started"); } } catch (Exception e) { logger.traceException(e); throw e; } } /** * Closes this connection handler so that it will no longer accept new * client connections. It may or may not disconnect existing client * connections based on the provided flag. * * @param stopRegistry Indicates if the RMI registry should be stopped */ public void finalizeConnectionHandler(boolean stopRegistry) { try { if (jmxRmiConnectorNoClientCertificate != null) { jmxRmiConnectorNoClientCertificate.stop(); } if (jmxRmiConnectorClientCertificate != null) { jmxRmiConnectorClientCertificate.stop(); } } catch (Exception e) { logger.traceException(e); } jmxRmiConnectorNoClientCertificate = null; jmxRmiConnectorClientCertificate = null; // Unregister connectors and stop them. try { ObjectName name = new ObjectName(jmxRmiConnectorNoClientCertificateName); if (mbs.isRegistered(name)) { mbs.unregisterMBean(name); } if (jmxRmiConnectorNoClientCertificate != null) { jmxRmiConnectorNoClientCertificate.stop(); } // TODO: unregister the connector with SSL client authen // name = new ObjectName(jmxRmiConnectorClientCertificateName); // if (mbs.isRegistered(name)) // { // mbs.unregisterMBean(name); // } // jmxRmiConnectorClientCertificate.stop() ; } catch (Exception e) { // TODO Log an error message logger.traceException(e); } if (stopRegistry) { // Close the socket try { if (rmiSsf != null) { rmiSsf.close(); } } catch (IOException e) { // TODO Log an error message logger.traceException(e); } registry = null; } } /** * Retrieves the RMI protocol version string in use for this connector. * * @return The RMI protocol version string in use for this connector. */ public String getProtocolVersion() { return rmiVersion; } }