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}