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