/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2009 Sun Microsystems, Inc. * Portions Copyright 2013-2015 ForgeRock AS */ package org.opends.server.protocols.jmx; import static org.opends.messages.ProtocolMessages.*; import static org.opends.server.types.HostPort.*; import static org.opends.server.util.StaticUtils.*; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.SortedSet; import java.util.concurrent.CopyOnWriteArrayList; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.config.server.ConfigException; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.ConnectionHandlerCfg; import org.opends.server.admin.std.server.JMXConnectionHandlerCfg; import org.opends.server.api.ClientConnection; import org.opends.server.api.ConnectionHandler; import org.opends.server.api.ServerShutdownListener; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ServerContext; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.opends.server.types.DN; import org.opends.server.types.HostPort; import org.opends.server.types.InitializationException; import org.opends.server.util.StaticUtils; /** * This class defines a connection handler that will be used for * communicating with administrative clients over JMX. The connection * handler is responsible for accepting new connections, reading * requests from the clients and parsing them as operations. A single * request handler should be used. */ public final class JmxConnectionHandler extends ConnectionHandler<JMXConnectionHandlerCfg> implements ServerShutdownListener, ConfigurationChangeListener<JMXConnectionHandlerCfg> { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** * Key that may be placed into a JMX connection environment map to * provide a custom {@code javax.net.ssl.TrustManager} array * for a connection. */ public static final String TRUST_MANAGER_ARRAY_KEY = "org.opends.server.protocol.jmx.ssl.trust.manager.array"; /** The list of active client connection. */ private final List<ClientConnection> connectionList; /** The current configuration state. */ private JMXConnectionHandlerCfg currentConfig; /** The JMX RMI Connector associated with the Connection handler. */ private RmiConnector rmiConnector; /** The unique name for this connection handler. */ private String connectionHandlerName; /** The protocol used to communicate with clients. */ private String protocol; /** The set of listeners for this connection handler. */ private final List<HostPort> listeners = new LinkedList<>(); /** * Creates a new instance of this JMX connection handler. It must be * initialized before it may be used. */ public JmxConnectionHandler() { super("JMX Connection Handler Thread"); this.connectionList = new CopyOnWriteArrayList<>(); } /** {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( JMXConnectionHandlerCfg config) { final ConfigChangeResult ccr = new ConfigChangeResult(); // Determine whether or not the RMI connection needs restarting. boolean rmiConnectorRestart = false; boolean portChanged = false; if (currentConfig.getListenPort() != config.getListenPort()) { rmiConnectorRestart = true; portChanged = true; } if (currentConfig.getRmiPort() != config.getRmiPort()) { rmiConnectorRestart = true; } if (currentConfig.isUseSSL() != config.isUseSSL()) { rmiConnectorRestart = true; } if (notEqualsNotNull(config.getSSLCertNickname(), currentConfig.getSSLCertNickname()) || notEqualsNotNull(config.getSSLCertNickname(), currentConfig.getSSLCertNickname())) { rmiConnectorRestart = true; } // Save the configuration. currentConfig = config; // Restart the connector if required. if (rmiConnectorRestart) { if (config.isUseSSL()) { protocol = "JMX+SSL"; } else { protocol = "JMX"; } listeners.clear(); listeners.add(HostPort.allAddresses(config.getListenPort())); rmiConnector.finalizeConnectionHandler(portChanged); try { rmiConnector.initialize(); } catch (RuntimeException e) { ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); ccr.addMessage(LocalizableMessage.raw(e.getMessage())); } } // If the port number has changed then update the JMX port information // stored in the system properties. if (portChanged) { String key = protocol + "_port"; String value = String.valueOf(config.getListenPort()); System.clearProperty(key); System.setProperty(key, value); } return ccr; } private <T> boolean notEqualsNotNull(T o1, T o2) { return o1 != null && !o1.equals(o2); } /** {@inheritDoc} */ @Override public void finalizeConnectionHandler(LocalizableMessage finalizeReason) { // Make sure that we don't get notified of any more changes. currentConfig.removeJMXChangeListener(this); // We should also close the RMI registry. rmiConnector.finalizeConnectionHandler(true); } /** * Retrieves the set of active client connections that have been * established through this connection handler. * * @return The set of active client connections that have been * established through this connection handler. */ @Override public Collection<ClientConnection> getClientConnections() { return connectionList; } /** * Retrieves the DN of the configuration entry with which this alert * generator is associated. * * @return The DN of the configuration entry with which this alert * generator is associated. */ @Override public DN getComponentEntryDN() { return currentConfig.dn(); } /** * Retrieves the DN of the key manager provider that should be used * for operations associated with this connection handler which need * access to a key manager. * * @return The DN of the key manager provider that should be used * for operations associated with this connection handler * which need access to a key manager, or {@code null} if no * key manager provider has been configured for this * connection handler. */ public DN getKeyManagerProviderDN() { return currentConfig.getKeyManagerProviderDN(); } /** * Get the JMX connection handler's listen address. * * @return Returns the JMX connection handler's listen address. */ public InetAddress getListenAddress() { return currentConfig.getListenAddress(); } /** * Get the JMX connection handler's listen port. * * @return Returns the JMX connection handler's listen port. */ public int getListenPort() { return currentConfig.getListenPort(); } /** * Get the JMX connection handler's rmi port. * * @return Returns the JMX connection handler's rmi port. */ public int getRmiPort() { return currentConfig.getRmiPort(); } /** * Get the JMX connection handler's RMI connector. * * @return Returns the JMX connection handler's RMI connector. */ public RmiConnector getRMIConnector() { return rmiConnector; } /** {@inheritDoc} */ @Override public String getShutdownListenerName() { return connectionHandlerName; } /** * Retrieves the nicknames of the server certificates that should be * used in conjunction with this JMX connection handler. * * @return The nicknames of the server certificates that should be * used in conjunction with this JMX connection handler. */ public SortedSet<String> getSSLServerCertNicknames() { return currentConfig.getSSLCertNickname(); } /** {@inheritDoc} */ @Override public void initializeConnectionHandler(ServerContext serverContext, JMXConnectionHandlerCfg config) throws ConfigException, InitializationException { // Configuration is ok. currentConfig = config; final List<LocalizableMessage> reasons = new LinkedList<>(); if (!isPortConfigurationAcceptable(String.valueOf(config.dn()), config.getListenPort(), reasons)) { LocalizableMessage message = reasons.get(0); logger.error(message); throw new InitializationException(message); } if (config.isUseSSL()) { protocol = "JMX+SSL"; } else { protocol = "JMX"; } listeners.clear(); listeners.add(HostPort.allAddresses(config.getListenPort())); connectionHandlerName = "JMX Connection Handler " + config.getListenPort(); // Create a system property to store the JMX port the server is // listening to. This information can be displayed with jinfo. System.setProperty( protocol + "_port", String.valueOf(config.getListenPort())); // Create the associated RMI Connector. rmiConnector = new RmiConnector(DirectoryServer.getJMXMBeanServer(), this); // Register this as a change listener. config.addJMXChangeListener(this); } /** {@inheritDoc} */ @Override public String getConnectionHandlerName() { return connectionHandlerName; } /** {@inheritDoc} */ @Override public String getProtocol() { return protocol; } /** {@inheritDoc} */ @Override public Collection<HostPort> getListeners() { return listeners; } /** {@inheritDoc} */ @Override public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration, List<LocalizableMessage> unacceptableReasons) { JMXConnectionHandlerCfg config = (JMXConnectionHandlerCfg) configuration; if ((currentConfig == null || (!currentConfig.isEnabled() && config.isEnabled()) || currentConfig.getListenPort() != config.getListenPort()) && !isPortConfigurationAcceptable(String.valueOf(config.dn()), config.getListenPort(), unacceptableReasons)) { return false; } if (config.getRmiPort() != 0 && (currentConfig == null || (!currentConfig.isEnabled() && config.isEnabled()) || currentConfig.getRmiPort() != config.getRmiPort()) && !isPortConfigurationAcceptable(String.valueOf(config.dn()), config.getRmiPort(), unacceptableReasons)) { return false; } return isConfigurationChangeAcceptable(config, unacceptableReasons); } /** * Attempt to bind to the port to verify whether the connection * handler will be able to start. * @return true is the port is free to use, false otherwise. */ private boolean isPortConfigurationAcceptable(String configDN, int newPort, List<LocalizableMessage> unacceptableReasons) { try { if (StaticUtils.isAddressInUse( new InetSocketAddress(newPort).getAddress(), newPort, true)) { throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString()); } } catch (Exception e) { LocalizableMessage message = ERR_CONNHANDLER_CANNOT_BIND.get("JMX", configDN, WILDCARD_ADDRESS, newPort, getExceptionMessage(e)); unacceptableReasons.add(message); return false; } return true; } /** {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( JMXConnectionHandlerCfg config, List<LocalizableMessage> unacceptableReasons) { // All validation is performed by the admin framework. return true; } /** * Determines whether or not clients are allowed to connect over JMX * using SSL. * * @return Returns {@code true} if clients are allowed to * connect over JMX using SSL. */ public boolean isUseSSL() { return currentConfig.isUseSSL(); } /** {@inheritDoc} */ @Override public void processServerShutdown(LocalizableMessage reason) { // We should also close the RMI registry. rmiConnector.finalizeConnectionHandler(true); } /** * Registers a client connection with this JMX connection handler. * * @param connection * The client connection. */ public void registerClientConnection(ClientConnection connection) { connectionList.add(connection); } /** * Unregisters a client connection from this JMX connection handler. * * @param connection * The client connection. */ public void unregisterClientConnection(ClientConnection connection) { connectionList.remove(connection); } /** {@inheritDoc} */ @Override public void run() { try { rmiConnector.initialize(); } catch (RuntimeException ignore) { // Already caught and logged } } /** {@inheritDoc} */ @Override public void toString(StringBuilder buffer) { buffer.append(connectionHandlerName); } }