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