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