001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.server.protocols.jmx;
028
029import java.io.IOException;
030import java.net.InetAddress;
031import java.rmi.RemoteException;
032import java.rmi.registry.LocateRegistry;
033import java.rmi.registry.Registry;
034import java.util.HashMap;
035import java.util.SortedSet;
036
037import javax.net.ssl.KeyManager;
038import javax.net.ssl.SSLSocketFactory;
039import javax.net.ssl.SSLContext;
040
041import javax.management.MBeanServer;
042import javax.management.ObjectName;
043import javax.management.remote.JMXConnectorServer;
044import javax.management.remote.JMXServiceURL;
045import javax.management.remote.rmi.RMIConnectorServer;
046import javax.rmi.ssl.SslRMIClientSocketFactory;
047
048import org.opends.server.api.KeyManagerProvider;
049import org.opends.server.config.JMXMBean;
050import org.opends.server.core.DirectoryServer;
051import org.opends.server.extensions.NullKeyManagerProvider;
052
053import org.forgerock.i18n.slf4j.LocalizedLogger;
054
055import org.opends.server.util.SelectableCertificateKeyManager;
056
057/**
058 * The RMI connector class starts and stops the JMX RMI connector server.
059 * There are 2 different connector servers
060 * <ul>
061 * <li> the RMI Client connector server, supporting TLS-encrypted.
062 * communication, server authentication by certificate and client
063 * authentication by providing appropriate LDAP credentials through
064 * SASL/PLAIN.
065 * <li> the RMI client connector server, supporting TLS-encrypted
066 * communication, server authentication by certificate, client
067 * authentication by certificate and identity assertion through SASL/PLAIN.
068 * </ul>
069 * <p>
070 * Each connector is registered into the JMX MBean server.
071 */
072public class RmiConnector
073{
074  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
075
076
077  /**
078   * The MBean server used to handle JMX interaction.
079   */
080  private MBeanServer mbs;
081
082  /**
083   * The associated JMX Connection Handler.
084   */
085  private JmxConnectionHandler jmxConnectionHandler;
086
087  /**
088   * The name of the JMX connector with no SSL client
089   * authentication.
090   */
091  private String jmxRmiConnectorNoClientCertificateName;
092
093  /**
094   * The reference to the JMX connector client with no SSL client
095   * authentication.
096   */
097  protected JMXConnectorServer jmxRmiConnectorNoClientCertificate;
098
099  /**
100   * The reference to the JMX connector client with SSL client
101   * authentication.
102   */
103  private JMXConnectorServer jmxRmiConnectorClientCertificate;
104
105  /**
106   * The reference to authenticator.
107   */
108  private RmiAuthenticator rmiAuthenticator;
109
110  /**
111   * The reference to the created RMI registry.
112   */
113  private Registry registry;
114
115  /**
116   * The Underlying Socket factory.
117   */
118  private OpendsRmiServerSocketFactory rmiSsf;
119
120  /**
121   * The RMI protocol version used by this connector.
122   */
123  private String rmiVersion;
124
125  // ===================================================================
126  // CONSTRUCTOR
127  // ===================================================================
128  /**
129   * Create a new instance of RmiConnector .
130   *
131   * @param mbs
132   *            The MBean server.
133   * @param jmxConnectionHandler
134   *            The associated JMX Connection Handler
135   */
136  public RmiConnector(MBeanServer mbs,
137      JmxConnectionHandler jmxConnectionHandler)
138  {
139    this.mbs = mbs;
140    this.jmxConnectionHandler = jmxConnectionHandler;
141
142    String baseName = JMXMBean.getJmxName(jmxConnectionHandler
143        .getComponentEntryDN());
144
145    jmxRmiConnectorNoClientCertificateName = baseName + ","
146        + "Type=jmxRmiConnectorNoClientCertificateName";
147  }
148
149  // ===================================================================
150  // Initialization
151  // ===================================================================
152  /**
153   * Activates the RMI Connectors. It starts the secure connectors.
154   */
155  public void initialize()
156  {
157    try
158    {
159      startCommonRegistry();
160
161      // start the RMI connector (SSL + server authentication)
162      startConnectorNoClientCertificate();
163
164      // start the RMI connector (SSL + server authentication +
165      // client authentication + identity given part SASL/PLAIN)
166      // TODO startConnectorClientCertificate(clientConnection);
167    }
168    catch (Exception e)
169    {
170      logger.traceException(e);
171
172      throw new RuntimeException("Error while starting the RMI module : "
173          + e.getMessage());
174    }
175
176    if (logger.isTraceEnabled())
177    {
178      logger.trace("RMI module started");
179    }
180  }
181
182  /**
183   * Starts the common RMI registry. In order to provide RMI stub for
184   * remote client, the JMX RMI connector should be register into an RMI
185   * registry. Each server will maintain its own private one.
186   *
187   * @throws Exception
188   *             if the registry cannot be started
189   */
190  private void startCommonRegistry() throws Exception
191  {
192    final InetAddress listenAddress = jmxConnectionHandler.getListenAddress();
193    int registryPort = jmxConnectionHandler.getListenPort();
194
195    // create our local RMI registry if it does not exist already
196    if (logger.isTraceEnabled())
197    {
198      logger.trace("start or reach an RMI registry on port %d",
199                          registryPort);
200    }
201    try
202    {
203      // TODO Not yet implemented: If the host has several interfaces
204      if (registry == null)
205      {
206        rmiSsf = new OpendsRmiServerSocketFactory(listenAddress);
207        registry = LocateRegistry.createRegistry(registryPort, null, rmiSsf);
208      }
209    }
210    catch (RemoteException re)
211    {
212      // is the registry already created ?
213      if (logger.isTraceEnabled())
214      {
215        logger.trace("cannot create the RMI registry -> already done ?");
216      }
217      try
218      {
219        // get a 'remote' reference on the registry
220        Registry reg = LocateRegistry.getRegistry(registryPort);
221
222        // 'ping' the registry
223        reg.list();
224        registry = reg;
225      }
226      catch (Exception e)
227      {
228        if (logger.isTraceEnabled())
229        {
230          // no 'valid' registry found on the specified port
231          logger.trace("exception thrown while pinging the RMI registry");
232
233          // throw the original exception
234          logger.traceException(re);
235        }
236        throw re;
237      }
238
239      // here the registry is ok even though
240      // it was not created by this call
241      if (logger.isTraceEnabled())
242      {
243        logger.trace("RMI was registry already started");
244      }
245    }
246  }
247
248  /**
249   * Starts a secure RMI connector, with a client that doesn't have to
250   * present a certificate, on the local MBean server.
251   * This method assumes that the common registry was successfully
252   * started.
253   * <p>
254   * If the connector is already started, this method simply returns
255   * without doing anything.
256   *
257   * @throws Exception
258   *             if an error occurs
259   */
260  private void startConnectorNoClientCertificate() throws Exception
261  {
262    try
263    {
264      // Environment map
265      HashMap<String, Object> env = new HashMap<>();
266
267      // ---------------------
268      // init an ssl context
269      // ---------------------
270      SslRMIClientSocketFactory rmiClientSockeyFactory = null;
271      DirectoryRMIServerSocketFactory rmiServerSockeyFactory = null;
272      if (jmxConnectionHandler.isUseSSL())
273      {
274        if (logger.isTraceEnabled())
275        {
276          logger.trace("SSL connection");
277        }
278
279        // ---------------------
280        // SERVER SIDE
281        // ---------------------
282        // Get a Server socket factory
283        KeyManagerProvider provider = DirectoryServer
284            .getKeyManagerProvider(jmxConnectionHandler
285                .getKeyManagerProviderDN());
286        final KeyManager[] keyManagers;
287        if (provider == null) {
288          keyManagers = new NullKeyManagerProvider().getKeyManagers();
289        }
290        else
291        {
292          final SortedSet<String> nicknames = jmxConnectionHandler.getSSLServerCertNicknames();
293          keyManagers = nicknames == null
294              ? provider.getKeyManagers()
295              : SelectableCertificateKeyManager.wrap(provider.getKeyManagers(), nicknames);
296        }
297
298        SSLContext ctx = SSLContext.getInstance("TLSv1");
299        ctx.init(
300            keyManagers,
301            null,
302            null);
303        SSLSocketFactory ssf = ctx.getSocketFactory();
304
305        // set the Server socket factory in the JMX map
306        rmiServerSockeyFactory = new DirectoryRMIServerSocketFactory(ssf, false);
307        env.put(
308            "jmx.remote.rmi.server.socket.factory",
309            rmiServerSockeyFactory);
310
311        // ---------------------
312        // CLIENT SIDE : Rmi stores the client stub in the
313        // registry
314        // ---------------------
315        // Set the Client socket factory in the JMX map
316        rmiClientSockeyFactory = new SslRMIClientSocketFactory();
317        env.put(
318            "jmx.remote.rmi.client.socket.factory",
319            rmiClientSockeyFactory);
320      }
321      else
322      {
323        if (logger.isTraceEnabled())
324        {
325          logger.trace("UNSECURE CONNECTION");
326        }
327      }
328
329      // specify the rmi JMX authenticator to be used
330      if (logger.isTraceEnabled())
331      {
332        logger.trace("Add RmiAuthenticator into JMX map");
333      }
334      rmiAuthenticator = new RmiAuthenticator(jmxConnectionHandler);
335
336      env.put(JMXConnectorServer.AUTHENTICATOR, rmiAuthenticator);
337
338      // Create the JMX Service URL
339      String uri = "org.opends.server.protocols.jmx.client-unknown";
340      String serviceUrl = "service:jmx:rmi:///jndi/rmi://"
341          + jmxConnectionHandler.getListenAddress().getHostName() + ":" + jmxConnectionHandler.getListenPort()
342          + "/" + uri;
343      JMXServiceURL url = new JMXServiceURL(serviceUrl);
344
345      // Create and start the connector
346      if (logger.isTraceEnabled())
347      {
348        logger.trace("Create and start the JMX RMI connector");
349      }
350      OpendsRMIJRMPServerImpl opendsRmiConnectorServer =
351          new OpendsRMIJRMPServerImpl(jmxConnectionHandler.getRmiPort(),
352              rmiClientSockeyFactory, rmiServerSockeyFactory, env);
353      jmxRmiConnectorNoClientCertificate = new RMIConnectorServer(url, env,
354          opendsRmiConnectorServer, mbs);
355      jmxRmiConnectorNoClientCertificate.start();
356
357      // Register the connector into the RMI registry
358      // TODO Should we do that?
359      ObjectName name = new ObjectName(jmxRmiConnectorNoClientCertificateName);
360      mbs.registerMBean(jmxRmiConnectorNoClientCertificate, name);
361      rmiVersion = opendsRmiConnectorServer.getVersion();
362
363      if (logger.isTraceEnabled())
364      {
365        logger.trace("JMX RMI connector Started");
366      }
367
368    }
369    catch (Exception e)
370    {
371      logger.traceException(e);
372      throw e;
373    }
374
375  }
376
377  /**
378   * Closes this connection handler so that it will no longer accept new
379   * client connections. It may or may not disconnect existing client
380   * connections based on the provided flag.
381   *
382   * @param stopRegistry Indicates if the RMI registry should be stopped
383   */
384  public void finalizeConnectionHandler(boolean stopRegistry)
385  {
386    try
387    {
388      if (jmxRmiConnectorNoClientCertificate != null)
389      {
390        jmxRmiConnectorNoClientCertificate.stop();
391      }
392      if (jmxRmiConnectorClientCertificate != null)
393      {
394        jmxRmiConnectorClientCertificate.stop();
395      }
396    }
397    catch (Exception e)
398    {
399      logger.traceException(e);
400    }
401
402    jmxRmiConnectorNoClientCertificate = null;
403    jmxRmiConnectorClientCertificate = null;
404
405    // Unregister connectors and stop them.
406    try
407    {
408      ObjectName name = new ObjectName(jmxRmiConnectorNoClientCertificateName);
409      if (mbs.isRegistered(name))
410      {
411        mbs.unregisterMBean(name);
412      }
413      if (jmxRmiConnectorNoClientCertificate != null)
414      {
415        jmxRmiConnectorNoClientCertificate.stop();
416      }
417
418      // TODO: unregister the connector with SSL client authen
419//      name = new ObjectName(jmxRmiConnectorClientCertificateName);
420//      if (mbs.isRegistered(name))
421//      {
422//        mbs.unregisterMBean(name);
423//      }
424//      jmxRmiConnectorClientCertificate.stop() ;
425    }
426    catch (Exception e)
427    {
428      // TODO Log an error message
429      logger.traceException(e);
430    }
431
432    if (stopRegistry)
433    {
434      // Close the socket
435      try
436      {
437        if (rmiSsf != null)
438        {
439          rmiSsf.close();
440        }
441      }
442      catch (IOException e)
443      {
444        // TODO Log an error message
445        logger.traceException(e);
446      }
447      registry = null;
448    }
449  }
450
451
452
453  /**
454   * Retrieves the RMI protocol version string in use for this connector.
455   *
456   * @return  The RMI protocol version string in use for this connector.
457   */
458  public String getProtocolVersion()
459  {
460    return rmiVersion;
461  }
462}