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}