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