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