@  rev 11403 : 6425769: Allow specifying an address to bind JMX remote connector
|  Reviewed-by: jbachorik, dfuchs

   1 /*
   2  * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.management.jmxremote;
  27 
  28 import java.io.BufferedInputStream;
  29 import java.io.File;
  30 import java.io.FileInputStream;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.io.Serializable;
  34 import java.lang.management.ManagementFactory;
  35 import java.net.InetAddress;
  36 import java.net.MalformedURLException;
  37 import java.net.Socket;
  38 import java.net.ServerSocket;
  39 import java.net.UnknownHostException;
  40 import java.rmi.NoSuchObjectException;
  41 import java.rmi.Remote;
  42 import java.rmi.RemoteException;
  43 import java.rmi.registry.Registry;
  44 import java.rmi.server.RMIClientSocketFactory;
  45 import java.rmi.server.RMIServerSocketFactory;
  46 import java.rmi.server.RMISocketFactory;
  47 import java.rmi.server.RemoteObject;
  48 import java.rmi.server.UnicastRemoteObject;
  49 import java.security.KeyStore;
  50 import java.security.Principal;
  51 import java.util.HashMap;
  52 import java.util.HashSet;
  53 import java.util.Iterator;
  54 import java.util.Map;
  55 import java.util.Properties;
  56 import java.util.Set;
  57 import java.util.StringTokenizer;
  58 
  59 import javax.management.MBeanServer;
  60 import javax.management.remote.JMXAuthenticator;
  61 import javax.management.remote.JMXConnectorServer;
  62 import javax.management.remote.JMXConnectorServerFactory;
  63 import javax.management.remote.JMXServiceURL;
  64 import javax.management.remote.rmi.RMIConnectorServer;
  65 import javax.net.ssl.KeyManagerFactory;
  66 import javax.net.ssl.SSLContext;
  67 import javax.net.ssl.SSLSocket;
  68 import javax.net.ssl.SSLSocketFactory;
  69 import javax.net.ssl.TrustManagerFactory;
  70 import javax.rmi.ssl.SslRMIClientSocketFactory;
  71 import javax.rmi.ssl.SslRMIServerSocketFactory;
  72 import javax.security.auth.Subject;
  73 
  74 import com.sun.jmx.remote.internal.RMIExporter;
  75 import com.sun.jmx.remote.security.JMXPluggableAuthenticator;
  76 import com.sun.jmx.remote.util.ClassLogger;
  77 
  78 import sun.management.Agent;
  79 import sun.management.AgentConfigurationError;
  80 import static sun.management.AgentConfigurationError.*;
  81 import sun.management.ConnectorAddressLink;
  82 import sun.management.FileSystem;
  83 import sun.rmi.server.UnicastRef;
  84 import sun.rmi.server.UnicastServerRef;
  85 import sun.rmi.server.UnicastServerRef2;
  86 
  87 /**
  88  * This class initializes and starts the RMIConnectorServer for JSR 163
  89  * JMX Monitoring.
  90  **/
  91 public final class ConnectorBootstrap {
  92 
  93     /**
  94      * Default values for JMX configuration properties.
  95      **/
  96     public static interface DefaultValues {
  97 
  98         public static final String PORT = "0";
  99         public static final String CONFIG_FILE_NAME = "management.properties";
 100         public static final String USE_SSL = "true";
 101         public static final String USE_LOCAL_ONLY = "true";
 102         public static final String USE_REGISTRY_SSL = "false";
 103         public static final String USE_AUTHENTICATION = "true";
 104         public static final String PASSWORD_FILE_NAME = "jmxremote.password";
 105         public static final String ACCESS_FILE_NAME = "jmxremote.access";
 106         public static final String SSL_NEED_CLIENT_AUTH = "false";
 107     }
 108 
 109     /**
 110      * Names of JMX configuration properties.
 111      **/
 112     public static interface PropertyNames {
 113 
 114         public static final String PORT =
 115                 "com.sun.management.jmxremote.port";
 116         public static final String HOST =
 117                 "com.sun.management.jmxremote.host";
 118         public static final String RMI_PORT =
 119                 "com.sun.management.jmxremote.rmi.port";
 120         public static final String CONFIG_FILE_NAME =
 121                 "com.sun.management.config.file";
 122         public static final String USE_LOCAL_ONLY =
 123                 "com.sun.management.jmxremote.local.only";
 124         public static final String USE_SSL =
 125                 "com.sun.management.jmxremote.ssl";
 126         public static final String USE_REGISTRY_SSL =
 127                 "com.sun.management.jmxremote.registry.ssl";
 128         public static final String USE_AUTHENTICATION =
 129                 "com.sun.management.jmxremote.authenticate";
 130         public static final String PASSWORD_FILE_NAME =
 131                 "com.sun.management.jmxremote.password.file";
 132         public static final String ACCESS_FILE_NAME =
 133                 "com.sun.management.jmxremote.access.file";
 134         public static final String LOGIN_CONFIG_NAME =
 135                 "com.sun.management.jmxremote.login.config";
 136         public static final String SSL_ENABLED_CIPHER_SUITES =
 137                 "com.sun.management.jmxremote.ssl.enabled.cipher.suites";
 138         public static final String SSL_ENABLED_PROTOCOLS =
 139                 "com.sun.management.jmxremote.ssl.enabled.protocols";
 140         public static final String SSL_NEED_CLIENT_AUTH =
 141                 "com.sun.management.jmxremote.ssl.need.client.auth";
 142         public static final String SSL_CONFIG_FILE_NAME =
 143                 "com.sun.management.jmxremote.ssl.config.file";
 144     }
 145 
 146     /**
 147      * JMXConnectorServer associated data.
 148      */
 149     private static class JMXConnectorServerData {
 150 
 151         public JMXConnectorServerData(
 152                 JMXConnectorServer jmxConnectorServer,
 153                 JMXServiceURL jmxRemoteURL) {
 154             this.jmxConnectorServer = jmxConnectorServer;
 155             this.jmxRemoteURL = jmxRemoteURL;
 156         }
 157         JMXConnectorServer jmxConnectorServer;
 158         JMXServiceURL jmxRemoteURL;
 159     }
 160 
 161     /**
 162      * <p>Prevents our RMI server objects from keeping the JVM alive.</p>
 163      *
 164      * <p>We use a private interface in Sun's JMX Remote API implementation
 165      * that allows us to specify how to export RMI objects.  We do so using
 166      * UnicastServerRef, a class in Sun's RMI implementation.  This is all
 167      * non-portable, of course, so this is only valid because we are inside
 168      * Sun's JRE.</p>
 169      *
 170      * <p>Objects are exported using {@link
 171      * UnicastServerRef#exportObject(Remote, Object, boolean)}.  The
 172      * boolean parameter is called <code>permanent</code> and means
 173      * both that the object is not eligible for Distributed Garbage
 174      * Collection, and that its continued existence will not prevent
 175      * the JVM from exiting.  It is the latter semantics we want (we
 176      * already have the former because of the way the JMX Remote API
 177      * works).  Hence the somewhat misleading name of this class.</p>
 178      */
 179     private static class PermanentExporter implements RMIExporter {
 180 
 181         public Remote exportObject(Remote obj,
 182                 int port,
 183                 RMIClientSocketFactory csf,
 184                 RMIServerSocketFactory ssf)
 185                 throws RemoteException {
 186 
 187             synchronized (this) {
 188                 if (firstExported == null) {
 189                     firstExported = obj;
 190                 }
 191             }
 192 
 193             final UnicastServerRef ref;
 194             if (csf == null && ssf == null) {
 195                 ref = new UnicastServerRef(port);
 196             } else {
 197                 ref = new UnicastServerRef2(port, csf, ssf);
 198             }
 199             return ref.exportObject(obj, null, true);
 200         }
 201 
 202         // Nothing special to be done for this case
 203         public boolean unexportObject(Remote obj, boolean force)
 204                 throws NoSuchObjectException {
 205             return UnicastRemoteObject.unexportObject(obj, force);
 206         }
 207         Remote firstExported;
 208     }
 209 
 210     /**
 211      * This JMXAuthenticator wraps the JMXPluggableAuthenticator and verifies
 212      * that at least one of the principal names contained in the authenticated
 213      * Subject is present in the access file.
 214      */
 215     private static class AccessFileCheckerAuthenticator
 216             implements JMXAuthenticator {
 217 
 218         public AccessFileCheckerAuthenticator(Map<String, Object> env) throws IOException {
 219             environment = env;
 220             accessFile = (String) env.get("jmx.remote.x.access.file");
 221             properties = propertiesFromFile(accessFile);
 222         }
 223 
 224         public Subject authenticate(Object credentials) {
 225             final JMXAuthenticator authenticator =
 226                     new JMXPluggableAuthenticator(environment);
 227             final Subject subject = authenticator.authenticate(credentials);
 228             checkAccessFileEntries(subject);
 229             return subject;
 230         }
 231 
 232         private void checkAccessFileEntries(Subject subject) {
 233             if (subject == null) {
 234                 throw new SecurityException(
 235                         "Access denied! No matching entries found in " +
 236                         "the access file [" + accessFile + "] as the " +
 237                         "authenticated Subject is null");
 238             }
 239             final Set<Principal> principals = subject.getPrincipals();
 240             for (Principal p1: principals) {
 241                 if (properties.containsKey(p1.getName())) {
 242                     return;
 243                 }
 244             }
 245 
 246             final Set<String> principalsStr = new HashSet<>();
 247             for (Principal p2: principals) {
 248                 principalsStr.add(p2.getName());
 249             }
 250             throw new SecurityException(
 251                     "Access denied! No entries found in the access file [" +
 252                     accessFile + "] for any of the authenticated identities " +
 253                     principalsStr);
 254         }
 255 
 256         private static Properties propertiesFromFile(String fname)
 257                 throws IOException {
 258             Properties p = new Properties();
 259             if (fname == null) {
 260                 return p;
 261             }
 262             try (FileInputStream fin = new FileInputStream(fname)) {
 263                 p.load(fin);
 264             }
 265             return p;
 266         }
 267         private final Map<String, Object> environment;
 268         private final Properties properties;
 269         private final String accessFile;
 270     }
 271 
 272     // The variable below is here to support stop functionality
 273     // It would be overriten if you call startRemoteCommectionServer second
 274     // time. It's OK for now as logic in Agent.java forbids mutiple agents
 275     private static Registry registry = null;
 276 
 277     public static void unexportRegistry() {
 278         // Remove the entry from registry
 279         try {
 280             if (registry != null) {
 281                 UnicastRemoteObject.unexportObject(registry, true);
 282                 registry = null;
 283             }
 284         } catch(NoSuchObjectException ex) {
 285             // This exception can appears only if we attempt
 286             // to unexportRegistry second time. So it's safe
 287             // to ignore it without additional messages.
 288         }
 289     }
 290 
 291      /**
 292       * Initializes and starts the JMX Connector Server.
 293       * If the com.sun.management.jmxremote.port property is not defined,
 294       * simply return. Otherwise, attempts to load the config file, and
 295       * then calls {@link #startRemoteConnectorServer
 296       *                            (java.lang.String, java.util.Properties)}.
 297       *
 298       * This method is used by some jtreg tests.
 299       **/
 300       public static synchronized JMXConnectorServer initialize() {
 301 
 302          // Load a new management properties
 303          final Properties props = Agent.loadManagementProperties();
 304          if (props == null) {
 305               return null;
 306          }
 307 
 308          final String portStr = props.getProperty(PropertyNames.PORT);
 309          return startRemoteConnectorServer(portStr, props);
 310      }
 311 
 312     /**
 313      * This method is used by some jtreg tests.
 314      *
 315      * @see #startRemoteConnectorServer
 316      *             (String portStr, Properties props)
 317      */
 318     public static synchronized JMXConnectorServer initialize(String portStr, Properties props)  {
 319          return startRemoteConnectorServer(portStr, props);
 320     }
 321 
 322     /**
 323      * Initializes and starts a JMX Connector Server for remote
 324      * monitoring and management.
 325      **/
 326     public static synchronized JMXConnectorServer startRemoteConnectorServer(String portStr, Properties props) {
 327 
 328         // Get port number
 329         final int port;
 330         try {
 331             port = Integer.parseInt(portStr);
 332         } catch (NumberFormatException x) {
 333             throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, x, portStr);
 334         }
 335         if (port < 0) {
 336             throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, portStr);
 337         }
 338 
 339         // User can specify a port to be used to export rmi object,
 340         // in order to simplify firewall rules
 341         // if port is not specified random one will be allocated.
 342         int rmiPort = 0;
 343         String rmiPortStr = props.getProperty(PropertyNames.RMI_PORT);
 344         try {
 345             if (rmiPortStr != null) {
 346                rmiPort = Integer.parseInt(rmiPortStr);
 347             }
 348         } catch (NumberFormatException x) {
 349             throw new AgentConfigurationError(INVALID_JMXREMOTE_RMI_PORT, x, rmiPortStr);
 350         }
 351         if (rmiPort < 0) {
 352             throw new AgentConfigurationError(INVALID_JMXREMOTE_RMI_PORT, rmiPortStr);
 353         }
 354 
 355         // Do we use authentication?
 356         final String useAuthenticationStr =
 357                 props.getProperty(PropertyNames.USE_AUTHENTICATION,
 358                 DefaultValues.USE_AUTHENTICATION);
 359         final boolean useAuthentication =
 360                 Boolean.valueOf(useAuthenticationStr).booleanValue();
 361 
 362         // Do we use SSL?
 363         final String useSslStr =
 364                 props.getProperty(PropertyNames.USE_SSL,
 365                 DefaultValues.USE_SSL);
 366         final boolean useSsl =
 367                 Boolean.valueOf(useSslStr).booleanValue();
 368 
 369         // Do we use RMI Registry SSL?
 370         final String useRegistrySslStr =
 371                 props.getProperty(PropertyNames.USE_REGISTRY_SSL,
 372                 DefaultValues.USE_REGISTRY_SSL);
 373         final boolean useRegistrySsl =
 374                 Boolean.valueOf(useRegistrySslStr).booleanValue();
 375 
 376         final String enabledCipherSuites =
 377                 props.getProperty(PropertyNames.SSL_ENABLED_CIPHER_SUITES);
 378         String enabledCipherSuitesList[] = null;
 379         if (enabledCipherSuites != null) {
 380             StringTokenizer st = new StringTokenizer(enabledCipherSuites, ",");
 381             int tokens = st.countTokens();
 382             enabledCipherSuitesList = new String[tokens];
 383             for (int i = 0; i < tokens; i++) {
 384                 enabledCipherSuitesList[i] = st.nextToken();
 385             }
 386         }
 387 
 388         final String enabledProtocols =
 389                 props.getProperty(PropertyNames.SSL_ENABLED_PROTOCOLS);
 390         String enabledProtocolsList[] = null;
 391         if (enabledProtocols != null) {
 392             StringTokenizer st = new StringTokenizer(enabledProtocols, ",");
 393             int tokens = st.countTokens();
 394             enabledProtocolsList = new String[tokens];
 395             for (int i = 0; i < tokens; i++) {
 396                 enabledProtocolsList[i] = st.nextToken();
 397             }
 398         }
 399 
 400         final String sslNeedClientAuthStr =
 401                 props.getProperty(PropertyNames.SSL_NEED_CLIENT_AUTH,
 402                 DefaultValues.SSL_NEED_CLIENT_AUTH);
 403         final boolean sslNeedClientAuth =
 404                 Boolean.valueOf(sslNeedClientAuthStr).booleanValue();
 405 
 406         // Read SSL config file name
 407         final String sslConfigFileName =
 408                 props.getProperty(PropertyNames.SSL_CONFIG_FILE_NAME);
 409 
 410         String loginConfigName = null;
 411         String passwordFileName = null;
 412         String accessFileName = null;
 413 
 414         // Initialize settings when authentication is active
 415         if (useAuthentication) {
 416 
 417             // Get non-default login configuration
 418             loginConfigName =
 419                     props.getProperty(PropertyNames.LOGIN_CONFIG_NAME);
 420 
 421             if (loginConfigName == null) {
 422                 // Get password file
 423                 passwordFileName =
 424                         props.getProperty(PropertyNames.PASSWORD_FILE_NAME,
 425                         getDefaultFileName(DefaultValues.PASSWORD_FILE_NAME));
 426                 checkPasswordFile(passwordFileName);
 427             }
 428 
 429             // Get access file
 430             accessFileName = props.getProperty(PropertyNames.ACCESS_FILE_NAME,
 431                     getDefaultFileName(DefaultValues.ACCESS_FILE_NAME));
 432             checkAccessFile(accessFileName);
 433         }
 434 
 435         final String bindAddress =
 436                 props.getProperty(PropertyNames.HOST);
 437 
 438         if (log.debugOn()) {
 439             log.debug("startRemoteConnectorServer",
 440                     Agent.getText("jmxremote.ConnectorBootstrap.starting") +
 441                     "\n\t" + PropertyNames.PORT + "=" + port +
 442                     (bindAddress == null ? "" : "\n\t" + PropertyNames.HOST + "=" + bindAddress) +
 443                     "\n\t" + PropertyNames.RMI_PORT + "=" + rmiPort +
 444                     "\n\t" + PropertyNames.USE_SSL + "=" + useSsl +
 445                     "\n\t" + PropertyNames.USE_REGISTRY_SSL + "=" + useRegistrySsl +
 446                     "\n\t" + PropertyNames.SSL_CONFIG_FILE_NAME + "=" + sslConfigFileName +
 447                     "\n\t" + PropertyNames.SSL_ENABLED_CIPHER_SUITES + "=" +
 448                     enabledCipherSuites +
 449                     "\n\t" + PropertyNames.SSL_ENABLED_PROTOCOLS + "=" +
 450                     enabledProtocols +
 451                     "\n\t" + PropertyNames.SSL_NEED_CLIENT_AUTH + "=" +
 452                     sslNeedClientAuth +
 453                     "\n\t" + PropertyNames.USE_AUTHENTICATION + "=" +
 454                     useAuthentication +
 455                     (useAuthentication ? (loginConfigName == null ? ("\n\t" + PropertyNames.PASSWORD_FILE_NAME + "=" +
 456                     passwordFileName) : ("\n\t" + PropertyNames.LOGIN_CONFIG_NAME + "=" +
 457                     loginConfigName)) : "\n\t" +
 458                     Agent.getText("jmxremote.ConnectorBootstrap.noAuthentication")) +
 459                     (useAuthentication ? ("\n\t" + PropertyNames.ACCESS_FILE_NAME + "=" +
 460                     accessFileName) : "") +
 461                     "");
 462         }
 463 
 464         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
 465         JMXConnectorServer cs = null;
 466         JMXServiceURL url = null;
 467         try {
 468             final JMXConnectorServerData data = exportMBeanServer(
 469                     mbs, port, rmiPort, useSsl, useRegistrySsl,
 470                     sslConfigFileName, enabledCipherSuitesList,
 471                     enabledProtocolsList, sslNeedClientAuth,
 472                     useAuthentication, loginConfigName,
 473                     passwordFileName, accessFileName, bindAddress);
 474             cs = data.jmxConnectorServer;
 475             url = data.jmxRemoteURL;
 476             log.config("startRemoteConnectorServer",
 477                     Agent.getText("jmxremote.ConnectorBootstrap.ready",
 478                     url.toString()));
 479         } catch (Exception e) {
 480             throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString());
 481         }
 482         try {
 483             // Export remote connector address and associated configuration
 484             // properties to the instrumentation buffer.
 485             Map<String, String> properties = new HashMap<>();
 486             properties.put("remoteAddress", url.toString());
 487             properties.put("authenticate", useAuthenticationStr);
 488             properties.put("ssl", useSslStr);
 489             properties.put("sslRegistry", useRegistrySslStr);
 490             properties.put("sslNeedClientAuth", sslNeedClientAuthStr);
 491             ConnectorAddressLink.exportRemote(properties);
 492         } catch (Exception e) {
 493             // Remote connector server started but unable to export remote
 494             // connector address and associated configuration properties to
 495             // the instrumentation buffer - non-fatal error.
 496             log.debug("startRemoteConnectorServer", e);
 497         }
 498         return cs;
 499     }
 500 
 501     /*
 502      * Creates and starts a RMI Connector Server for "local" monitoring
 503      * and management.
 504      */
 505     public static JMXConnectorServer startLocalConnectorServer() {
 506         // Ensure cryptographically strong random number generater used
 507         // to choose the object number - see java.rmi.server.ObjID
 508         System.setProperty("java.rmi.server.randomIDs", "true");
 509 
 510         // This RMI server should not keep the VM alive
 511         Map<String, Object> env = new HashMap<>();
 512         env.put(RMIExporter.EXPORTER_ATTRIBUTE, new PermanentExporter());
 513 
 514         // The local connector server need only be available via the
 515         // loopback connection.
 516         String localhost = "localhost";
 517         InetAddress lh = null;
 518         try {
 519             lh = InetAddress.getByName(localhost);
 520             localhost = lh.getHostAddress();
 521         } catch (UnknownHostException x) {
 522         }
 523 
 524         // localhost unknown or (somehow) didn't resolve to
 525         // a loopback address.
 526         if (lh == null || !lh.isLoopbackAddress()) {
 527             localhost = "127.0.0.1";
 528         }
 529 
 530         MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
 531         try {
 532             JMXServiceURL url = new JMXServiceURL("rmi", localhost, 0);
 533             // Do we accept connections from local interfaces only?
 534             Properties props = Agent.getManagementProperties();
 535             if (props ==  null) {
 536                 props = new Properties();
 537             }
 538             String useLocalOnlyStr = props.getProperty(
 539                     PropertyNames.USE_LOCAL_ONLY, DefaultValues.USE_LOCAL_ONLY);
 540             boolean useLocalOnly = Boolean.valueOf(useLocalOnlyStr).booleanValue();
 541             if (useLocalOnly) {
 542                 env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
 543                         new LocalRMIServerSocketFactory());
 544             }
 545             JMXConnectorServer server =
 546                     JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
 547             server.start();
 548             return server;
 549         } catch (Exception e) {
 550             throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString());
 551         }
 552     }
 553 
 554     private static void checkPasswordFile(String passwordFileName) {
 555         if (passwordFileName == null || passwordFileName.length() == 0) {
 556             throw new AgentConfigurationError(PASSWORD_FILE_NOT_SET);
 557         }
 558         File file = new File(passwordFileName);
 559         if (!file.exists()) {
 560             throw new AgentConfigurationError(PASSWORD_FILE_NOT_FOUND, passwordFileName);
 561         }
 562 
 563         if (!file.canRead()) {
 564             throw new AgentConfigurationError(PASSWORD_FILE_NOT_READABLE, passwordFileName);
 565         }
 566 
 567         FileSystem fs = FileSystem.open();
 568         try {
 569             if (fs.supportsFileSecurity(file)) {
 570                 if (!fs.isAccessUserOnly(file)) {
 571                     final String msg = Agent.getText("jmxremote.ConnectorBootstrap.password.readonly",
 572                             passwordFileName);
 573                     log.config("startRemoteConnectorServer", msg);
 574                     throw new AgentConfigurationError(PASSWORD_FILE_ACCESS_NOT_RESTRICTED,
 575                             passwordFileName);
 576                 }
 577             }
 578         } catch (IOException e) {
 579             throw new AgentConfigurationError(PASSWORD_FILE_READ_FAILED,
 580                     e, passwordFileName);
 581         }
 582     }
 583 
 584     private static void checkAccessFile(String accessFileName) {
 585         if (accessFileName == null || accessFileName.length() == 0) {
 586             throw new AgentConfigurationError(ACCESS_FILE_NOT_SET);
 587         }
 588         File file = new File(accessFileName);
 589         if (!file.exists()) {
 590             throw new AgentConfigurationError(ACCESS_FILE_NOT_FOUND, accessFileName);
 591         }
 592 
 593         if (!file.canRead()) {
 594             throw new AgentConfigurationError(ACCESS_FILE_NOT_READABLE, accessFileName);
 595         }
 596     }
 597 
 598     private static void checkRestrictedFile(String restrictedFileName) {
 599         if (restrictedFileName == null || restrictedFileName.length() == 0) {
 600             throw new AgentConfigurationError(FILE_NOT_SET);
 601         }
 602         File file = new File(restrictedFileName);
 603         if (!file.exists()) {
 604             throw new AgentConfigurationError(FILE_NOT_FOUND, restrictedFileName);
 605         }
 606         if (!file.canRead()) {
 607             throw new AgentConfigurationError(FILE_NOT_READABLE, restrictedFileName);
 608         }
 609         FileSystem fs = FileSystem.open();
 610         try {
 611             if (fs.supportsFileSecurity(file)) {
 612                 if (!fs.isAccessUserOnly(file)) {
 613                     final String msg = Agent.getText(
 614                             "jmxremote.ConnectorBootstrap.file.readonly",
 615                             restrictedFileName);
 616                     log.config("startRemoteConnectorServer", msg);
 617                     throw new AgentConfigurationError(
 618                             FILE_ACCESS_NOT_RESTRICTED, restrictedFileName);
 619                 }
 620             }
 621         } catch (IOException e) {
 622             throw new AgentConfigurationError(
 623                     FILE_READ_FAILED, e, restrictedFileName);
 624         }
 625     }
 626 
 627     /**
 628      * Compute the full path name for a default file.
 629      * @param basename basename (with extension) of the default file.
 630      * @return ${JRE}/lib/management/${basename}
 631      **/
 632     private static String getDefaultFileName(String basename) {
 633         final String fileSeparator = File.separator;
 634         return System.getProperty("java.home") + fileSeparator + "lib" +
 635                 fileSeparator + "management" + fileSeparator +
 636                 basename;
 637     }
 638 
 639     private static SslRMIServerSocketFactory createSslRMIServerSocketFactory(
 640             String sslConfigFileName,
 641             String[] enabledCipherSuites,
 642             String[] enabledProtocols,
 643             boolean sslNeedClientAuth,
 644             String bindAddress) {
 645         if (sslConfigFileName == null) {
 646             return new HostAwareSslSocketFactory(
 647                     enabledCipherSuites,
 648                     enabledProtocols,
 649                     sslNeedClientAuth, bindAddress);
 650         } else {
 651             checkRestrictedFile(sslConfigFileName);
 652             try {
 653                 // Load the SSL keystore properties from the config file
 654                 Properties p = new Properties();
 655                 try (InputStream in = new FileInputStream(sslConfigFileName)) {
 656                     BufferedInputStream bin = new BufferedInputStream(in);
 657                     p.load(bin);
 658                 }
 659                 String keyStore =
 660                         p.getProperty("javax.net.ssl.keyStore");
 661                 String keyStorePassword =
 662                         p.getProperty("javax.net.ssl.keyStorePassword", "");
 663                 String trustStore =
 664                         p.getProperty("javax.net.ssl.trustStore");
 665                 String trustStorePassword =
 666                         p.getProperty("javax.net.ssl.trustStorePassword", "");
 667 
 668                 char[] keyStorePasswd = null;
 669                 if (keyStorePassword.length() != 0) {
 670                     keyStorePasswd = keyStorePassword.toCharArray();
 671                 }
 672 
 673                 char[] trustStorePasswd = null;
 674                 if (trustStorePassword.length() != 0) {
 675                     trustStorePasswd = trustStorePassword.toCharArray();
 676                 }
 677 
 678                 KeyStore ks = null;
 679                 if (keyStore != null) {
 680                     ks = KeyStore.getInstance(KeyStore.getDefaultType());
 681                     try (FileInputStream ksfis = new FileInputStream(keyStore)) {
 682                         ks.load(ksfis, keyStorePasswd);
 683                     }
 684                 }
 685                 KeyManagerFactory kmf = KeyManagerFactory.getInstance(
 686                         KeyManagerFactory.getDefaultAlgorithm());
 687                 kmf.init(ks, keyStorePasswd);
 688 
 689                 KeyStore ts = null;
 690                 if (trustStore != null) {
 691                     ts = KeyStore.getInstance(KeyStore.getDefaultType());
 692                     try (FileInputStream tsfis = new FileInputStream(trustStore)) {
 693                         ts.load(tsfis, trustStorePasswd);
 694                     }
 695                 }
 696                 TrustManagerFactory tmf = TrustManagerFactory.getInstance(
 697                         TrustManagerFactory.getDefaultAlgorithm());
 698                 tmf.init(ts);
 699 
 700                 SSLContext ctx = SSLContext.getInstance("SSL");
 701                 ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
 702 
 703                 return new HostAwareSslSocketFactory(
 704                         ctx,
 705                         enabledCipherSuites,
 706                         enabledProtocols,
 707                         sslNeedClientAuth, bindAddress);
 708             } catch (Exception e) {
 709                 throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString());
 710             }
 711         }
 712     }
 713 
 714     private static JMXConnectorServerData exportMBeanServer(
 715             MBeanServer mbs,
 716             int port,
 717             int rmiPort,
 718             boolean useSsl,
 719             boolean useRegistrySsl,
 720             String sslConfigFileName,
 721             String[] enabledCipherSuites,
 722             String[] enabledProtocols,
 723             boolean sslNeedClientAuth,
 724             boolean useAuthentication,
 725             String loginConfigName,
 726             String passwordFileName,
 727             String accessFileName,
 728             String bindAddress)
 729             throws IOException, MalformedURLException {
 730 
 731         /* Make sure we use non-guessable RMI object IDs.  Otherwise
 732          * attackers could hijack open connections by guessing their
 733          * IDs.  */
 734         System.setProperty("java.rmi.server.randomIDs", "true");
 735 
 736         JMXServiceURL url = new JMXServiceURL("rmi", bindAddress, rmiPort);
 737 
 738         Map<String, Object> env = new HashMap<>();
 739 
 740         PermanentExporter exporter = new PermanentExporter();
 741 
 742         env.put(RMIExporter.EXPORTER_ATTRIBUTE, exporter);
 743 
 744         boolean useSocketFactory = bindAddress != null && !useSsl;
 745 
 746         if (useAuthentication) {
 747             if (loginConfigName != null) {
 748                 env.put("jmx.remote.x.login.config", loginConfigName);
 749             }
 750             if (passwordFileName != null) {
 751                 env.put("jmx.remote.x.password.file", passwordFileName);
 752             }
 753 
 754             env.put("jmx.remote.x.access.file", accessFileName);
 755 
 756             if (env.get("jmx.remote.x.password.file") != null ||
 757                     env.get("jmx.remote.x.login.config") != null) {
 758                 env.put(JMXConnectorServer.AUTHENTICATOR,
 759                         new AccessFileCheckerAuthenticator(env));
 760             }
 761         }
 762 
 763         RMIClientSocketFactory csf = null;
 764         RMIServerSocketFactory ssf = null;
 765 
 766         if (useSsl || useRegistrySsl) {
 767             csf = new SslRMIClientSocketFactory();
 768             ssf = createSslRMIServerSocketFactory(
 769                     sslConfigFileName, enabledCipherSuites,
 770                     enabledProtocols, sslNeedClientAuth, bindAddress);
 771         }
 772 
 773         if (useSsl) {
 774             env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE,
 775                     csf);
 776             env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
 777                     ssf);
 778         }
 779 
 780         if (useSocketFactory) {
 781             ssf = new HostAwareSocketFactory(bindAddress);
 782             env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
 783                     ssf);
 784         }
 785 
 786         JMXConnectorServer connServer = null;
 787         try {
 788             connServer =
 789                     JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
 790             connServer.start();
 791         } catch (IOException e) {
 792             if (connServer == null || connServer.getAddress() == null) {
 793                 throw new AgentConfigurationError(CONNECTOR_SERVER_IO_ERROR,
 794                         e, url.toString());
 795             } else {
 796                 throw new AgentConfigurationError(CONNECTOR_SERVER_IO_ERROR,
 797                         e, connServer.getAddress().toString());
 798             }
 799         }
 800 
 801         if (useRegistrySsl) {
 802             registry =
 803                     new SingleEntryRegistry(port, csf, ssf,
 804                     "jmxrmi", exporter.firstExported);
 805         } else if (useSocketFactory) {
 806             registry =
 807                     new SingleEntryRegistry(port, csf, ssf,
 808                     "jmxrmi", exporter.firstExported);
 809         } else {
 810             registry =
 811                     new SingleEntryRegistry(port,
 812                     "jmxrmi", exporter.firstExported);
 813         }
 814 
 815 
 816         int registryPort =
 817             ((UnicastRef) ((RemoteObject) registry).getRef()).getLiveRef().getPort();
 818         String jmxUrlStr =  String.format("service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi",
 819                                            url.getHost(), registryPort);
 820         JMXServiceURL remoteURL = new JMXServiceURL(jmxUrlStr);
 821 
 822         /* Our exporter remembers the first object it was asked to
 823         export, which will be an RMIServerImpl appropriate for
 824         publication in our special registry.  We could
 825         alternatively have constructed the RMIServerImpl explicitly
 826         and then constructed an RMIConnectorServer passing it as a
 827         parameter, but that's quite a bit more verbose and pulls in
 828         lots of knowledge of the RMI connector.  */
 829 
 830         return new JMXConnectorServerData(connServer, remoteURL);
 831     }
 832 
 833     /**
 834      * This class cannot be instantiated.
 835      **/
 836     private ConnectorBootstrap() {
 837     }
 838 
 839     private static final ClassLogger log =
 840         new ClassLogger(ConnectorBootstrap.class.getPackage().getName(),
 841                         "ConnectorBootstrap");
 842 
 843     private static class HostAwareSocketFactory implements RMIServerSocketFactory {
 844 
 845         private final String bindAddress;
 846 
 847         private HostAwareSocketFactory(String bindAddress) {
 848              this.bindAddress = bindAddress;
 849         }
 850 
 851         @Override
 852         public ServerSocket createServerSocket(int port) throws IOException {
 853             if (bindAddress == null) {
 854                 return new ServerSocket(port);
 855             } else {
 856                 try {
 857                     InetAddress addr = InetAddress.getByName(bindAddress);
 858                     return new ServerSocket(port, 0, addr);
 859                 } catch (UnknownHostException e) {
 860                     return new ServerSocket(port);
 861                 }
 862             }
 863         }
 864     }
 865 
 866     private static class HostAwareSslSocketFactory extends SslRMIServerSocketFactory {
 867 
 868         private final String bindAddress;
 869         private final String[] enabledCipherSuites;
 870         private final String[] enabledProtocols;
 871         private final boolean needClientAuth;
 872         private final SSLContext context;
 873 
 874         private HostAwareSslSocketFactory(String[] enabledCipherSuites,
 875                                           String[] enabledProtocols,
 876                                           boolean sslNeedClientAuth,
 877                                           String bindAddress) throws IllegalArgumentException {
 878             this(null, enabledCipherSuites, enabledProtocols, sslNeedClientAuth, bindAddress);
 879         }
 880 
 881         private HostAwareSslSocketFactory(SSLContext ctx,
 882                                           String[] enabledCipherSuites,
 883                                           String[] enabledProtocols,
 884                                           boolean sslNeedClientAuth,
 885                                           String bindAddress) throws IllegalArgumentException {
 886             this.context = ctx;
 887             this.bindAddress = bindAddress;
 888             this.enabledProtocols = enabledProtocols;
 889             this.enabledCipherSuites = enabledCipherSuites;
 890             this.needClientAuth = sslNeedClientAuth;
 891             checkValues(ctx, enabledCipherSuites, enabledProtocols);
 892         }
 893 
 894         @Override
 895         public ServerSocket createServerSocket(int port) throws IOException {
 896             if (bindAddress != null) {
 897                 try {
 898                     InetAddress addr = InetAddress.getByName(bindAddress);
 899                     return new SslServerSocket(port, 0, addr, context,
 900                                                enabledCipherSuites, enabledProtocols, needClientAuth);
 901                 } catch (UnknownHostException e) {
 902                     return new SslServerSocket(port, context,
 903                                                enabledCipherSuites, enabledProtocols, needClientAuth);
 904                 }
 905             } else {
 906                 return new SslServerSocket(port, context,
 907                                            enabledCipherSuites, enabledProtocols, needClientAuth);
 908             }
 909         }
 910 
 911         private static void checkValues(SSLContext context,
 912                                         String[] enabledCipherSuites,
 913                                         String[] enabledProtocols) throws IllegalArgumentException {
 914             // Force the initialization of the default at construction time,
 915             // rather than delaying it to the first time createServerSocket()
 916             // is called.
 917             //
 918             final SSLSocketFactory sslSocketFactory =
 919                     context == null ?
 920                         (SSLSocketFactory)SSLSocketFactory.getDefault() : context.getSocketFactory();
 921             SSLSocket sslSocket = null;
 922             if (enabledCipherSuites != null || enabledProtocols != null) {
 923                 try {
 924                     sslSocket = (SSLSocket) sslSocketFactory.createSocket();
 925                 } catch (Exception e) {
 926                     final String msg = "Unable to check if the cipher suites " +
 927                             "and protocols to enable are supported";
 928                     throw (IllegalArgumentException)
 929                     new IllegalArgumentException(msg).initCause(e);
 930                 }
 931             }
 932 
 933             // Check if all the cipher suites and protocol versions to enable
 934             // are supported by the underlying SSL/TLS implementation and if
 935             // true create lists from arrays.
 936             //
 937             if (enabledCipherSuites != null) {
 938                 sslSocket.setEnabledCipherSuites(enabledCipherSuites);
 939             }
 940             if (enabledProtocols != null) {
 941                 sslSocket.setEnabledProtocols(enabledProtocols);
 942             }
 943         }
 944     }
 945 
 946     private static class SslServerSocket extends ServerSocket {
 947 
 948         private static SSLSocketFactory defaultSSLSocketFactory;
 949         private final String[] enabledCipherSuites;
 950         private final String[] enabledProtocols;
 951         private final boolean needClientAuth;
 952         private final SSLContext context;
 953 
 954         private SslServerSocket(int port,
 955                                 SSLContext ctx,
 956                                 String[] enabledCipherSuites,
 957                                 String[] enabledProtocols,
 958                                 boolean needClientAuth) throws IOException {
 959             super(port);
 960             this.enabledProtocols = enabledProtocols;
 961             this.enabledCipherSuites = enabledCipherSuites;
 962             this.needClientAuth = needClientAuth;
 963             this.context = ctx;
 964         }
 965 
 966         private SslServerSocket(int port,
 967                                 int backlog,
 968                                 InetAddress bindAddr,
 969                                 SSLContext ctx,
 970                                 String[] enabledCipherSuites,
 971                                 String[] enabledProtocols,
 972                                 boolean needClientAuth) throws IOException {
 973             super(port, backlog, bindAddr);
 974             this.enabledProtocols = enabledProtocols;
 975             this.enabledCipherSuites = enabledCipherSuites;
 976             this.needClientAuth = needClientAuth;
 977             this.context = ctx;
 978         }
 979 
 980         @Override
 981         public Socket accept() throws IOException {
 982             final SSLSocketFactory sslSocketFactory =
 983                     context == null ?
 984                         getDefaultSSLSocketFactory() : context.getSocketFactory();
 985             Socket socket = super.accept();
 986             SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
 987                     socket, socket.getInetAddress().getHostName(),
 988                     socket.getPort(), true);
 989             sslSocket.setUseClientMode(false);
 990             if (enabledCipherSuites != null) {
 991                 sslSocket.setEnabledCipherSuites(enabledCipherSuites);
 992             }
 993             if (enabledProtocols != null) {
 994                 sslSocket.setEnabledProtocols(enabledProtocols);
 995             }
 996             sslSocket.setNeedClientAuth(needClientAuth);
 997             return sslSocket;
 998         }
 999 
1000         private static synchronized SSLSocketFactory getDefaultSSLSocketFactory() {
1001             if (defaultSSLSocketFactory == null) {
1002                 defaultSSLSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
1003                 return defaultSSLSocketFactory;
1004             } else {
1005                 return defaultSSLSocketFactory;
1006             }
1007         }
1008 
1009     }
1010 }
--- EOF ---