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}