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.forgerock.opendj.config.server.ConfigurationChangeListener; 036import org.forgerock.opendj.server.config.server.ConnectionHandlerCfg; 037import org.forgerock.opendj.server.config.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 @Override 101 public ConfigChangeResult applyConfigurationChange( 102 JMXConnectionHandlerCfg config) { 103 final ConfigChangeResult ccr = new ConfigChangeResult(); 104 105 // Determine whether the RMI connection needs restarting. 106 boolean rmiConnectorRestart = false; 107 boolean portChanged = false; 108 109 if (currentConfig.getListenPort() != config.getListenPort()) { 110 rmiConnectorRestart = true; 111 portChanged = true; 112 } 113 114 if (currentConfig.getRmiPort() != config.getRmiPort()) 115 { 116 rmiConnectorRestart = true; 117 } 118 if (currentConfig.isUseSSL() != config.isUseSSL()) { 119 rmiConnectorRestart = true; 120 } 121 122 if (notEqualsNotNull(config.getSSLCertNickname(), currentConfig.getSSLCertNickname()) 123 || notEqualsNotNull(config.getSSLCertNickname(), currentConfig.getSSLCertNickname())) { 124 rmiConnectorRestart = true; 125 } 126 127 // Save the configuration. 128 currentConfig = config; 129 130 // Restart the connector if required. 131 if (rmiConnectorRestart) { 132 if (config.isUseSSL()) { 133 protocol = "JMX+SSL"; 134 } else { 135 protocol = "JMX"; 136 } 137 138 listeners.clear(); 139 listeners.add(HostPort.allAddresses(config.getListenPort())); 140 141 rmiConnector.finalizeConnectionHandler(portChanged); 142 try 143 { 144 rmiConnector.initialize(); 145 } 146 catch (RuntimeException e) 147 { 148 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 149 ccr.addMessage(LocalizableMessage.raw(e.getMessage())); 150 } 151 } 152 153 // If the port number has changed then update the JMX port information 154 // stored in the system properties. 155 if (portChanged) 156 { 157 String key = protocol + "_port"; 158 String value = String.valueOf(config.getListenPort()); 159 System.clearProperty(key); 160 System.setProperty(key, value); 161 } 162 163 return ccr; 164 } 165 166 167 private <T> boolean notEqualsNotNull(T o1, T o2) 168 { 169 return o1 != null && !o1.equals(o2); 170 } 171 172 @Override 173 public void finalizeConnectionHandler(LocalizableMessage finalizeReason) { 174 // Make sure that we don't get notified of any more changes. 175 currentConfig.removeJMXChangeListener(this); 176 177 // We should also close the RMI registry. 178 rmiConnector.finalizeConnectionHandler(true); 179 } 180 181 182 /** 183 * Retrieves the set of active client connections that have been 184 * established through this connection handler. 185 * 186 * @return The set of active client connections that have been 187 * established through this connection handler. 188 */ 189 @Override 190 public Collection<ClientConnection> getClientConnections() { 191 return connectionList; 192 } 193 194 195 196 /** 197 * Retrieves the DN of the configuration entry with which this alert 198 * generator is associated. 199 * 200 * @return The DN of the configuration entry with which this alert 201 * generator is associated. 202 */ 203 @Override 204 public DN getComponentEntryDN() { 205 return currentConfig.dn(); 206 } 207 208 209 210 /** 211 * Retrieves the DN of the key manager provider that should be used 212 * for operations associated with this connection handler which need 213 * access to a key manager. 214 * 215 * @return The DN of the key manager provider that should be used 216 * for operations associated with this connection handler 217 * which need access to a key manager, or {@code null} if no 218 * key manager provider has been configured for this 219 * connection handler. 220 */ 221 public DN getKeyManagerProviderDN() { 222 return currentConfig.getKeyManagerProviderDN(); 223 } 224 225 226 /** 227 * Get the JMX connection handler's listen address. 228 * 229 * @return Returns the JMX connection handler's listen address. 230 */ 231 public InetAddress getListenAddress() 232 { 233 return currentConfig.getListenAddress(); 234 } 235 236 /** 237 * Get the JMX connection handler's listen port. 238 * 239 * @return Returns the JMX connection handler's listen port. 240 */ 241 public int getListenPort() { 242 return currentConfig.getListenPort(); 243 } 244 245 /** 246 * Get the JMX connection handler's rmi port. 247 * 248 * @return Returns the JMX connection handler's rmi port. 249 */ 250 public int getRmiPort() { 251 return currentConfig.getRmiPort(); 252 } 253 254 255 /** 256 * Get the JMX connection handler's RMI connector. 257 * 258 * @return Returns the JMX connection handler's RMI connector. 259 */ 260 public RmiConnector getRMIConnector() { 261 return rmiConnector; 262 } 263 264 @Override 265 public String getShutdownListenerName() { 266 return connectionHandlerName; 267 } 268 269 270 271 /** 272 * Retrieves the nicknames of the server certificates that should be 273 * used in conjunction with this JMX connection handler. 274 * 275 * @return The nicknames of the server certificates that should be 276 * used in conjunction with this JMX connection handler. 277 */ 278 public SortedSet<String> getSSLServerCertNicknames() { 279 return currentConfig.getSSLCertNickname(); 280 } 281 282 @Override 283 public void initializeConnectionHandler(ServerContext serverContext, JMXConnectionHandlerCfg config) 284 throws ConfigException, InitializationException 285 { 286 // Configuration is ok. 287 currentConfig = config; 288 289 final List<LocalizableMessage> reasons = new LinkedList<>(); 290 if (!isPortConfigurationAcceptable(String.valueOf(config.dn()), 291 config.getListenPort(), reasons)) 292 { 293 LocalizableMessage message = reasons.get(0); 294 logger.error(message); 295 throw new InitializationException(message); 296 } 297 298 if (config.isUseSSL()) { 299 protocol = "JMX+SSL"; 300 } else { 301 protocol = "JMX"; 302 } 303 304 listeners.clear(); 305 listeners.add(HostPort.allAddresses(config.getListenPort())); 306 connectionHandlerName = "JMX Connection Handler " + config.getListenPort(); 307 308 // Create a system property to store the JMX port the server is 309 // listening to. This information can be displayed with jinfo. 310 System.setProperty( 311 protocol + "_port", String.valueOf(config.getListenPort())); 312 313 // Create the associated RMI Connector. 314 rmiConnector = new RmiConnector(DirectoryServer.getJMXMBeanServer(), this); 315 316 // Register this as a change listener. 317 config.addJMXChangeListener(this); 318 } 319 320 @Override 321 public String getConnectionHandlerName() { 322 return connectionHandlerName; 323 } 324 325 @Override 326 public String getProtocol() { 327 return protocol; 328 } 329 330 @Override 331 public Collection<HostPort> getListeners() { 332 return listeners; 333 } 334 335 @Override 336 public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration, 337 List<LocalizableMessage> unacceptableReasons) 338 { 339 JMXConnectionHandlerCfg config = (JMXConnectionHandlerCfg) configuration; 340 341 if ((currentConfig == null || 342 (!currentConfig.isEnabled() && config.isEnabled()) || 343 currentConfig.getListenPort() != config.getListenPort()) && 344 !isPortConfigurationAcceptable(String.valueOf(config.dn()), 345 config.getListenPort(), unacceptableReasons)) 346 { 347 return false; 348 } 349 350 if (config.getRmiPort() != 0 && 351 (currentConfig == null || 352 (!currentConfig.isEnabled() && config.isEnabled()) || 353 currentConfig.getRmiPort() != config.getRmiPort()) && 354 !isPortConfigurationAcceptable(String.valueOf(config.dn()), 355 config.getRmiPort(), unacceptableReasons)) 356 { 357 return false; 358 } 359 360 return isConfigurationChangeAcceptable(config, unacceptableReasons); 361 } 362 363 /** 364 * Attempt to bind to the port to verify whether the connection 365 * handler will be able to start. 366 * @return true is the port is free to use, false otherwise. 367 */ 368 private boolean isPortConfigurationAcceptable(String configDN, 369 int newPort, List<LocalizableMessage> unacceptableReasons) { 370 try { 371 if (StaticUtils.isAddressInUse( 372 new InetSocketAddress(newPort).getAddress(), newPort, true)) { 373 throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString()); 374 } 375 return true; 376 } catch (Exception e) { 377 LocalizableMessage message = ERR_CONNHANDLER_CANNOT_BIND.get("JMX", configDN, 378 WILDCARD_ADDRESS, newPort, getExceptionMessage(e)); 379 unacceptableReasons.add(message); 380 return false; 381 } 382 } 383 384 @Override 385 public boolean isConfigurationChangeAcceptable( 386 JMXConnectionHandlerCfg config, 387 List<LocalizableMessage> unacceptableReasons) { 388 // All validation is performed by the admin framework. 389 return true; 390 } 391 392 393 394 /** 395 * Determines whether clients are allowed to connect over JMX using SSL. 396 * 397 * @return Returns {@code true} if clients are allowed to 398 * connect over JMX using SSL. 399 */ 400 public boolean isUseSSL() { 401 return currentConfig.isUseSSL(); 402 } 403 404 @Override 405 public void processServerShutdown(LocalizableMessage reason) { 406 // We should also close the RMI registry. 407 rmiConnector.finalizeConnectionHandler(true); 408 } 409 410 411 412 /** 413 * Registers a client connection with this JMX connection handler. 414 * 415 * @param connection 416 * The client connection. 417 */ 418 public void registerClientConnection(ClientConnection connection) { 419 connectionList.add(connection); 420 } 421 422 423 /** 424 * Unregisters a client connection from this JMX connection handler. 425 * 426 * @param connection 427 * The client connection. 428 */ 429 public void unregisterClientConnection(ClientConnection connection) { 430 connectionList.remove(connection); 431 } 432 433 @Override 434 public void run() { 435 try 436 { 437 rmiConnector.initialize(); 438 } 439 catch (RuntimeException ignore) 440 { 441 // Already caught and logged 442 } 443 } 444 445 @Override 446 public void toString(StringBuilder buffer) { 447 buffer.append(connectionHandlerName); 448 } 449}