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