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 static org.opends.messages.ProtocolMessages.*; 030import static org.opends.server.types.HostPort.*; 031import static org.opends.server.util.StaticUtils.*; 032 033import java.io.IOException; 034import java.net.InetAddress; 035import java.net.InetSocketAddress; 036import java.util.Collection; 037import java.util.LinkedList; 038import java.util.List; 039import java.util.concurrent.CopyOnWriteArrayList; 040 041import org.forgerock.i18n.LocalizableMessage; 042import org.forgerock.i18n.slf4j.LocalizedLogger; 043import org.forgerock.opendj.config.server.ConfigException; 044import org.opends.server.admin.server.ConfigurationChangeListener; 045import org.opends.server.admin.std.server.ConnectionHandlerCfg; 046import org.opends.server.admin.std.server.JMXConnectionHandlerCfg; 047import org.opends.server.api.ClientConnection; 048import org.opends.server.api.ConnectionHandler; 049import org.opends.server.api.ServerShutdownListener; 050import org.opends.server.core.DirectoryServer; 051import org.forgerock.opendj.config.server.ConfigChangeResult; 052import org.opends.server.types.DN; 053import org.opends.server.types.HostPort; 054import org.opends.server.types.InitializationException; 055import org.opends.server.util.StaticUtils; 056 057/** 058 * This class defines a connection handler that will be used for 059 * communicating with administrative clients over JMX. The connection 060 * handler is responsible for accepting new connections, reading 061 * requests from the clients and parsing them as operations. A single 062 * request handler should be used. 063 */ 064public final class JmxConnectionHandler extends 065 ConnectionHandler<JMXConnectionHandlerCfg> implements 066 ServerShutdownListener, 067 ConfigurationChangeListener<JMXConnectionHandlerCfg> { 068 069 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 070 071 072 /** 073 * Key that may be placed into a JMX connection environment map to 074 * provide a custom {@code javax.net.ssl.TrustManager} array 075 * for a connection. 076 */ 077 public static final String TRUST_MANAGER_ARRAY_KEY = 078 "org.opends.server.protocol.jmx.ssl.trust.manager.array"; 079 080 /** The list of active client connection. */ 081 private final List<ClientConnection> connectionList; 082 083 /** The current configuration state. */ 084 private JMXConnectionHandlerCfg currentConfig; 085 086 /** The JMX RMI Connector associated with the Connection handler. */ 087 private RmiConnector rmiConnector; 088 089 /** The unique name for this connection handler. */ 090 private String connectionHandlerName; 091 092 /** The protocol used to communicate with clients. */ 093 private String protocol; 094 095 /** The set of listeners for this connection handler. */ 096 private final List<HostPort> listeners = new LinkedList<>(); 097 098 /** 099 * Creates a new instance of this JMX connection handler. It must be 100 * initialized before it may be used. 101 */ 102 public JmxConnectionHandler() { 103 super("JMX Connection Handler Thread"); 104 105 this.connectionList = new CopyOnWriteArrayList<>(); 106 } 107 108 109 110 /** {@inheritDoc} */ 111 @Override 112 public ConfigChangeResult applyConfigurationChange( 113 JMXConnectionHandlerCfg config) { 114 final ConfigChangeResult ccr = new ConfigChangeResult(); 115 116 // Determine whether or not the RMI connection needs restarting. 117 boolean rmiConnectorRestart = false; 118 boolean portChanged = false; 119 120 if (currentConfig.getListenPort() != config.getListenPort()) { 121 rmiConnectorRestart = true; 122 portChanged = true; 123 } 124 125 if (currentConfig.getRmiPort() != config.getRmiPort()) 126 { 127 rmiConnectorRestart = true; 128 } 129 if (currentConfig.isUseSSL() != config.isUseSSL()) { 130 rmiConnectorRestart = true; 131 } 132 133 if (notEqualsNotNull(config.getSSLCertNickname(), currentConfig.getSSLCertNickname()) 134 || notEqualsNotNull(config.getSSLCertNickname(), currentConfig.getSSLCertNickname())) { 135 rmiConnectorRestart = true; 136 } 137 138 // Save the configuration. 139 currentConfig = config; 140 141 // Restart the connector if required. 142 if (rmiConnectorRestart) { 143 if (config.isUseSSL()) { 144 protocol = "JMX+SSL"; 145 } else { 146 protocol = "JMX"; 147 } 148 149 listeners.clear(); 150 listeners.add(HostPort.allAddresses(config.getListenPort())); 151 152 rmiConnector.finalizeConnectionHandler(portChanged); 153 try 154 { 155 rmiConnector.initialize(); 156 } 157 catch (RuntimeException e) 158 { 159 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 160 ccr.addMessage(LocalizableMessage.raw(e.getMessage())); 161 } 162 } 163 164 // If the port number has changed then update the JMX port information 165 // stored in the system properties. 166 if (portChanged) 167 { 168 String key = protocol + "_port"; 169 String value = String.valueOf(config.getListenPort()); 170 System.clearProperty(key); 171 System.setProperty(key, value); 172 } 173 174 return ccr; 175 } 176 177 178 private boolean notEqualsNotNull(String o1, String o2) 179 { 180 return o1 != null && !o1.equals(o2); 181 } 182 183 /** {@inheritDoc} */ 184 @Override 185 public void finalizeConnectionHandler(LocalizableMessage finalizeReason) { 186 // Make sure that we don't get notified of any more changes. 187 currentConfig.removeJMXChangeListener(this); 188 189 // We should also close the RMI registry. 190 rmiConnector.finalizeConnectionHandler(true); 191 } 192 193 194 /** 195 * Retrieves the set of active client connections that have been 196 * established through this connection handler. 197 * 198 * @return The set of active client connections that have been 199 * established through this connection handler. 200 */ 201 @Override 202 public Collection<ClientConnection> getClientConnections() { 203 return connectionList; 204 } 205 206 207 208 /** 209 * Retrieves the DN of the configuration entry with which this alert 210 * generator is associated. 211 * 212 * @return The DN of the configuration entry with which this alert 213 * generator is associated. 214 */ 215 @Override 216 public DN getComponentEntryDN() { 217 return currentConfig.dn(); 218 } 219 220 221 222 /** 223 * Retrieves the DN of the key manager provider that should be used 224 * for operations associated with this connection handler which need 225 * access to a key manager. 226 * 227 * @return The DN of the key manager provider that should be used 228 * for operations associated with this connection handler 229 * which need access to a key manager, or {@code null} if no 230 * key manager provider has been configured for this 231 * connection handler. 232 */ 233 public DN getKeyManagerProviderDN() { 234 return currentConfig.getKeyManagerProviderDN(); 235 } 236 237 238 /** 239 * Get the JMX connection handler's listen address. 240 * 241 * @return Returns the JMX connection handler's listen address. 242 */ 243 public InetAddress getListenAddress() 244 { 245 return currentConfig.getListenAddress(); 246 } 247 248 /** 249 * Get the JMX connection handler's listen port. 250 * 251 * @return Returns the JMX connection handler's listen port. 252 */ 253 public int getListenPort() { 254 return currentConfig.getListenPort(); 255 } 256 257 /** 258 * Get the JMX connection handler's rmi port. 259 * 260 * @return Returns the JMX connection handler's rmi port. 261 */ 262 public int getRmiPort() { 263 return currentConfig.getRmiPort(); 264 } 265 266 267 /** 268 * Get the JMX connection handler's RMI connector. 269 * 270 * @return Returns the JMX connection handler's RMI connector. 271 */ 272 public RmiConnector getRMIConnector() { 273 return rmiConnector; 274 } 275 276 277 278 /** {@inheritDoc} */ 279 @Override 280 public String getShutdownListenerName() { 281 return connectionHandlerName; 282 } 283 284 285 286 /** 287 * Retrieves the nickname of the server certificate that should be 288 * used in conjunction with this JMX connection handler. 289 * 290 * @return The nickname of the server certificate that should be 291 * used in conjunction with this JMX connection handler. 292 */ 293 public String getSSLServerCertNickname() { 294 return currentConfig.getSSLCertNickname(); 295 } 296 297 298 299 /** {@inheritDoc} */ 300 @Override 301 public void initializeConnectionHandler(JMXConnectionHandlerCfg config) 302 throws ConfigException, InitializationException 303 { 304 // Configuration is ok. 305 currentConfig = config; 306 307 final List<LocalizableMessage> reasons = new LinkedList<>(); 308 if (!isPortConfigurationAcceptable(String.valueOf(config.dn()), 309 config.getListenPort(), reasons)) 310 { 311 LocalizableMessage message = reasons.get(0); 312 logger.error(message); 313 throw new InitializationException(message); 314 } 315 316 if (config.isUseSSL()) { 317 protocol = "JMX+SSL"; 318 } else { 319 protocol = "JMX"; 320 } 321 322 listeners.clear(); 323 listeners.add(HostPort.allAddresses(config.getListenPort())); 324 connectionHandlerName = "JMX Connection Handler " + config.getListenPort(); 325 326 // Create a system property to store the JMX port the server is 327 // listening to. This information can be displayed with jinfo. 328 System.setProperty( 329 protocol + "_port", String.valueOf(config.getListenPort())); 330 331 // Create the associated RMI Connector. 332 rmiConnector = new RmiConnector(DirectoryServer.getJMXMBeanServer(), this); 333 334 // Register this as a change listener. 335 config.addJMXChangeListener(this); 336 } 337 338 339 340 /** {@inheritDoc} */ 341 @Override 342 public String getConnectionHandlerName() { 343 return connectionHandlerName; 344 } 345 346 347 348 /** {@inheritDoc} */ 349 @Override 350 public String getProtocol() { 351 return protocol; 352 } 353 354 355 356 /** {@inheritDoc} */ 357 @Override 358 public Collection<HostPort> getListeners() { 359 return listeners; 360 } 361 362 363 /** {@inheritDoc} */ 364 @Override 365 public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration, 366 List<LocalizableMessage> unacceptableReasons) 367 { 368 JMXConnectionHandlerCfg config = (JMXConnectionHandlerCfg) configuration; 369 370 if ((currentConfig == null || 371 (!currentConfig.isEnabled() && config.isEnabled()) || 372 currentConfig.getListenPort() != config.getListenPort()) && 373 !isPortConfigurationAcceptable(String.valueOf(config.dn()), 374 config.getListenPort(), unacceptableReasons)) 375 { 376 return false; 377 } 378 379 if (config.getRmiPort() != 0 && 380 (currentConfig == null || 381 (!currentConfig.isEnabled() && config.isEnabled()) || 382 currentConfig.getRmiPort() != config.getRmiPort()) && 383 !isPortConfigurationAcceptable(String.valueOf(config.dn()), 384 config.getRmiPort(), unacceptableReasons)) 385 { 386 return false; 387 } 388 389 return isConfigurationChangeAcceptable(config, unacceptableReasons); 390 } 391 392 /** 393 * Attempt to bind to the port to verify whether the connection 394 * handler will be able to start. 395 * @return true is the port is free to use, false otherwise. 396 */ 397 private boolean isPortConfigurationAcceptable(String configDN, 398 int newPort, List<LocalizableMessage> unacceptableReasons) { 399 try { 400 if (StaticUtils.isAddressInUse( 401 new InetSocketAddress(newPort).getAddress(), newPort, true)) { 402 throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString()); 403 } 404 } catch (Exception e) { 405 LocalizableMessage message = ERR_CONNHANDLER_CANNOT_BIND.get("JMX", configDN, 406 WILDCARD_ADDRESS, newPort, getExceptionMessage(e)); 407 unacceptableReasons.add(message); 408 return false; 409 } 410 return true; 411 } 412 413 /** {@inheritDoc} */ 414 @Override 415 public boolean isConfigurationChangeAcceptable( 416 JMXConnectionHandlerCfg config, 417 List<LocalizableMessage> unacceptableReasons) { 418 // All validation is performed by the admin framework. 419 return true; 420 } 421 422 423 424 /** 425 * Determines whether or not clients are allowed to connect over JMX 426 * using SSL. 427 * 428 * @return Returns {@code true} if clients are allowed to 429 * connect over JMX using SSL. 430 */ 431 public boolean isUseSSL() { 432 return currentConfig.isUseSSL(); 433 } 434 435 436 437 /** {@inheritDoc} */ 438 @Override 439 public void processServerShutdown(LocalizableMessage reason) { 440 // We should also close the RMI registry. 441 rmiConnector.finalizeConnectionHandler(true); 442 } 443 444 445 446 /** 447 * Registers a client connection with this JMX connection handler. 448 * 449 * @param connection 450 * The client connection. 451 */ 452 public void registerClientConnection(ClientConnection connection) { 453 connectionList.add(connection); 454 } 455 456 457 /** 458 * Unregisters a client connection from this JMX connection handler. 459 * 460 * @param connection 461 * The client connection. 462 */ 463 public void unregisterClientConnection(ClientConnection connection) { 464 connectionList.remove(connection); 465 } 466 467 468 /** {@inheritDoc} */ 469 @Override 470 public void run() { 471 try 472 { 473 rmiConnector.initialize(); 474 } 475 catch (RuntimeException ignore) 476 { 477 // Already caught and logged 478 } 479 } 480 481 482 483 /** {@inheritDoc} */ 484 @Override 485 public void toString(StringBuilder buffer) { 486 buffer.append(connectionHandlerName); 487 } 488}