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}