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.lang.management.ManagementFactory;
  34 import java.net.InetAddress;
  35 import java.net.MalformedURLException;
  36 import java.net.UnknownHostException;
  37 import java.rmi.NoSuchObjectException;
  38 import java.rmi.Remote;
  39 import java.rmi.RemoteException;
  40 import java.rmi.registry.Registry;
  41 import java.rmi.server.RMIClientSocketFactory;
  42 import java.rmi.server.RMIServerSocketFactory;
  43 import java.rmi.server.RemoteObject;
  44 import java.rmi.server.UnicastRemoteObject;
  45 import java.security.KeyStore;
  46 import java.security.Principal;
  47 import java.util.HashMap;
  48 import java.util.HashSet;
  49 import java.util.Iterator;
  50 import java.util.Map;
  51 import java.util.Properties;
  52 import java.util.Set;
  53 import java.util.StringTokenizer;
  54 
  55 import javax.management.MBeanServer;
  56 import javax.management.remote.JMXAuthenticator;
  57 import javax.management.remote.JMXConnectorServer;
  58 import javax.management.remote.JMXConnectorServerFactory;
  59 import javax.management.remote.JMXServiceURL;
  60 import javax.management.remote.rmi.RMIConnectorServer;
  61 import javax.net.ssl.KeyManagerFactory;
  62 import javax.net.ssl.SSLContext;
  63 import javax.net.ssl.TrustManagerFactory;
  64 import javax.rmi.ssl.SslRMIClientSocketFactory;
  65 import javax.rmi.ssl.SslRMIServerSocketFactory;
  66 import javax.security.auth.Subject;
  67 
  68 import com.sun.jmx.remote.internal.RMIExporter;
  69 import com.sun.jmx.remote.security.JMXPluggableAuthenticator;
  70 import com.sun.jmx.remote.util.ClassLogger;
  71 
  72 import sun.management.Agent;
  73 import sun.management.AgentConfigurationError;
  74 import static sun.management.AgentConfigurationError.*;
  75 import sun.management.ConnectorAddressLink;
  76 import sun.management.FileSystem;
  77 import sun.rmi.server.UnicastRef;
  78 import sun.rmi.server.UnicastServerRef;
  79 import sun.rmi.server.UnicastServerRef2;
  80 
  81 /**
  82  * This class initializes and starts the RMIConnectorServer for JSR 163
  83  * JMX Monitoring.
  84  **/
  85 public final class ConnectorBootstrap {
  86 
  87     /**
  88      * Default values for JMX configuration properties.
  89      **/
  90     public static interface DefaultValues {
  91 
  92         public static final String PORT = "0";
  93         public static final String CONFIG_FILE_NAME = "management.properties";
  94         public static final String USE_SSL = "true";
  95         public static final String USE_LOCAL_ONLY = "true";
  96         public static final String USE_REGISTRY_SSL = "false";
  97         public static final String USE_AUTHENTICATION = "true";
  98         public static final String PASSWORD_FILE_NAME = "jmxremote.password";
  99         public static final String ACCESS_FILE_NAME = "jmxremote.access";
 100         public static final String SSL_NEED_CLIENT_AUTH = "false";
 101     }
 102 
 103     /**
 104      * Names of JMX configuration properties.
 105      **/
 106     public static interface PropertyNames {
 107 
 108         public static final String PORT =
 109                 "com.sun.management.jmxremote.port";
 110         public static final String RMI_PORT =
 111                 "com.sun.management.jmxremote.rmi.port";
 112         public static final String CONFIG_FILE_NAME =
 113                 "com.sun.management.config.file";
 114         public static final String USE_LOCAL_ONLY =
 115                 "com.sun.management.jmxremote.local.only";
 116         public static final String USE_SSL =
 117                 "com.sun.management.jmxremote.ssl";
 118         public static final String USE_REGISTRY_SSL =
 119                 "com.sun.management.jmxremote.registry.ssl";
 120         public static final String USE_AUTHENTICATION =
 121                 "com.sun.management.jmxremote.authenticate";
 122         public static final String PASSWORD_FILE_NAME =
 123                 "com.sun.management.jmxremote.password.file";
 124         public static final String ACCESS_FILE_NAME =
 125                 "com.sun.management.jmxremote.access.file";
 126         public static final String LOGIN_CONFIG_NAME =
 127                 "com.sun.management.jmxremote.login.config";
 128         public static final String SSL_ENABLED_CIPHER_SUITES =
 129                 "com.sun.management.jmxremote.ssl.enabled.cipher.suites";
 130         public static final String SSL_ENABLED_PROTOCOLS =
 131                 "com.sun.management.jmxremote.ssl.enabled.protocols";
 132         public static final String SSL_NEED_CLIENT_AUTH =
 133                 "com.sun.management.jmxremote.ssl.need.client.auth";
 134         public static final String SSL_CONFIG_FILE_NAME =
 135                 "com.sun.management.jmxremote.ssl.config.file";
 136     }
 137 
 138     /**
 139      * JMXConnectorServer associated data.
 140      */
 141     private static class JMXConnectorServerData {
 142 
 143         public JMXConnectorServerData(
 144                 JMXConnectorServer jmxConnectorServer,
 145                 JMXServiceURL jmxRemoteURL) {
 146             this.jmxConnectorServer = jmxConnectorServer;
 147             this.jmxRemoteURL = jmxRemoteURL;
 148         }
 149         JMXConnectorServer jmxConnectorServer;
 150         JMXServiceURL jmxRemoteURL;
 151     }
 152 
 153     /**
 154      * <p>Prevents our RMI server objects from keeping the JVM alive.</p>
 155      *
 156      * <p>We use a private interface in Sun's JMX Remote API implementation
 157      * that allows us to specify how to export RMI objects.  We do so using
 158      * UnicastServerRef, a class in Sun's RMI implementation.  This is all
 159      * non-portable, of course, so this is only valid because we are inside
 160      * Sun's JRE.</p>
 161      *
 162      * <p>Objects are exported using {@link
 163      * UnicastServerRef#exportObject(Remote, Object, boolean)}.  The
 164      * boolean parameter is called <code>permanent</code> and means
 165      * both that the object is not eligible for Distributed Garbage
 166      * Collection, and that its continued existence will not prevent
 167      * the JVM from exiting.  It is the latter semantics we want (we
 168      * already have the former because of the way the JMX Remote API
 169      * works).  Hence the somewhat misleading name of this class.</p>
 170      */
 171     private static class PermanentExporter implements RMIExporter {
 172 
 173         public Remote exportObject(Remote obj,
 174                 int port,
 175                 RMIClientSocketFactory csf,
 176                 RMIServerSocketFactory ssf)
 177                 throws RemoteException {
 178 
 179             synchronized (this) {
 180                 if (firstExported == null) {
 181                     firstExported = obj;
 182                 }
 183             }
 184 
 185             final UnicastServerRef ref;
 186             if (csf == null && ssf == null) {
 187                 ref = new UnicastServerRef(port);
 188             } else {
 189                 ref = new UnicastServerRef2(port, csf, ssf);
 190             }
 191             return ref.exportObject(obj, null, true);
 192         }
 193 
 194         // Nothing special to be done for this case
 195         public boolean unexportObject(Remote obj, boolean force)
 196                 throws NoSuchObjectException {
 197             return UnicastRemoteObject.unexportObject(obj, force);
 198         }
 199         Remote firstExported;
 200     }
 201 
 202     /**
 203      * This JMXAuthenticator wraps the JMXPluggableAuthenticator and verifies
 204      * that at least one of the principal names contained in the authenticated
 205      * Subject is present in the access file.
 206      */
 207     private static class AccessFileCheckerAuthenticator
 208             implements JMXAuthenticator {
 209 
 210         public AccessFileCheckerAuthenticator(Map<String, Object> env) throws IOException {
 211             environment = env;
 212             accessFile = (String) env.get("jmx.remote.x.access.file");
 213             properties = propertiesFromFile(accessFile);
 214         }
 215 
 216         public Subject authenticate(Object credentials) {
 217             final JMXAuthenticator authenticator =
 218                     new JMXPluggableAuthenticator(environment);
 219             final Subject subject = authenticator.authenticate(credentials);
 220             checkAccessFileEntries(subject);
 221             return subject;
 222         }
 223 
 224         private void checkAccessFileEntries(Subject subject) {
 225             if (subject == null) {
 226                 throw new SecurityException(
 227                         "Access denied! No matching entries found in " +
 228                         "the access file [" + accessFile + "] as the " +
 229                         "authenticated Subject is null");
 230             }
 231             final Set<Principal> principals = subject.getPrincipals();
 232             for (Principal p1: principals) {
 233                 if (properties.containsKey(p1.getName())) {
 234                     return;
 235                 }
 236             }
 237 
 238             final Set<String> principalsStr = new HashSet<>();
 239             for (Principal p2: principals) {
 240                 principalsStr.add(p2.getName());
 241             }
 242             throw new SecurityException(
 243                     "Access denied! No entries found in the access file [" +
 244                     accessFile + "] for any of the authenticated identities " +
 245                     principalsStr);
 246         }
 247 
 248         private static Properties propertiesFromFile(String fname)
 249                 throws IOException {
 250             Properties p = new Properties();
 251             if (fname == null) {
 252                 return p;
 253             }
 254             try (FileInputStream fin = new FileInputStream(fname)) {
 255                 p.load(fin);
 256             }
 257             return p;
 258         }
 259         private final Map<String, Object> environment;
 260         private final Properties properties;
 261         private final String accessFile;
 262     }
 263 
 264     // The variable below is here to support stop functionality
 265     // It would be overriten if you call startRemoteCommectionServer second
 266     // time. It's OK for now as logic in Agent.java forbids mutiple agents
 267     private static Registry registry = null;
 268 
 269     public static void unexportRegistry() {
 270         // Remove the entry from registry
 271         try {
 272             if (registry != null) {
 273                 UnicastRemoteObject.unexportObject(registry, true);
 274                 registry = null;
 275             }
 276         } catch(NoSuchObjectException ex) {
 277             // This exception can appears only if we attempt
 278             // to unexportRegistry second time. So it's safe
 279             // to ignore it without additional messages.
 280         }
 281     }
 282 
 283      /**
 284       * Initializes and starts the JMX Connector Server.
 285       * If the com.sun.management.jmxremote.port property is not defined,
 286       * simply return. Otherwise, attempts to load the config file, and
 287       * then calls {@link #startRemoteConnectorServer
 288       *                            (java.lang.String, java.util.Properties)}.
 289       *
 290       * This method is used by some jtreg tests.
 291       **/
 292       public static synchronized JMXConnectorServer initialize() {
 293 
 294          // Load a new management properties
 295          final Properties props = Agent.loadManagementProperties();
 296          if (props == null) {
 297               return null;
 298          }
 299 
 300          final String portStr = props.getProperty(PropertyNames.PORT);
 301          return startRemoteConnectorServer(portStr, props);
 302      }
 303 
 304     /**
 305      * This method is used by some jtreg tests.
 306      *
 307      * @see #startRemoteConnectorServer
 308      *             (String portStr, Properties props)
 309      */
 310     public static synchronized JMXConnectorServer initialize(String portStr, Properties props)  {
 311          return startRemoteConnectorServer(portStr, props);
 312     }
 313 
 314     /**
 315      * Initializes and starts a JMX Connector Server for remote
 316      * monitoring and management.
 317      **/
 318     public static synchronized JMXConnectorServer startRemoteConnectorServer(String portStr, Properties props) {
 319 
 320         // Get port number
 321         final int port;
 322         try {
 323             port = Integer.parseInt(portStr);
 324         } catch (NumberFormatException x) {
 325             throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, x, portStr);
 326         }
 327         if (port < 0) {
 328             throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, portStr);
 329         }
 330 
 331         // User can specify a port to be used to export rmi object,
 332         // in order to simplify firewall rules
 333         // if port is not specified random one will be allocated.
 334         int rmiPort = 0;
 335         String rmiPortStr = props.getProperty(PropertyNames.RMI_PORT);
 336         try {
 337             if (rmiPortStr != null) {
 338                rmiPort = Integer.parseInt(rmiPortStr);
 339             }
 340         } catch (NumberFormatException x) {
 341             throw new AgentConfigurationError(INVALID_JMXREMOTE_RMI_PORT, x, rmiPortStr);
 342         }
 343         if (rmiPort < 0) {
 344             throw new AgentConfigurationError(INVALID_JMXREMOTE_RMI_PORT, rmiPortStr);
 345         }
 346 
 347         // Do we use authentication?
 348         final String useAuthenticationStr =
 349                 props.getProperty(PropertyNames.USE_AUTHENTICATION,
 350                 DefaultValues.USE_AUTHENTICATION);
 351         final boolean useAuthentication =
 352                 Boolean.valueOf(useAuthenticationStr).booleanValue();
 353 
 354         // Do we use SSL?
 355         final String useSslStr =
 356                 props.getProperty(PropertyNames.USE_SSL,
 357                 DefaultValues.USE_SSL);
 358         final boolean useSsl =
 359                 Boolean.valueOf(useSslStr).booleanValue();
 360 
 361         // Do we use RMI Registry SSL?
 362         final String useRegistrySslStr =
 363                 props.getProperty(PropertyNames.USE_REGISTRY_SSL,
 364                 DefaultValues.USE_REGISTRY_SSL);
 365         final boolean useRegistrySsl =
 366                 Boolean.valueOf(useRegistrySslStr).booleanValue();
 367 
 368         final String enabledCipherSuites =
 369                 props.getProperty(PropertyNames.SSL_ENABLED_CIPHER_SUITES);
 370         String enabledCipherSuitesList[] = null;
 371         if (enabledCipherSuites != null) {
 372             StringTokenizer st = new StringTokenizer(enabledCipherSuites, ",");
 373             int tokens = st.countTokens();
 374             enabledCipherSuitesList = new String[tokens];
 375             for (int i = 0; i < tokens; i++) {
 376                 enabledCipherSuitesList[i] = st.nextToken();
 377             }
 378         }
 379 
 380         final String enabledProtocols =
 381                 props.getProperty(PropertyNames.SSL_ENABLED_PROTOCOLS);
 382         String enabledProtocolsList[] = null;
 383         if (enabledProtocols != null) {
 384             StringTokenizer st = new StringTokenizer(enabledProtocols, ",");
 385             int tokens = st.countTokens();
 386             enabledProtocolsList = new String[tokens];
 387             for (int i = 0; i < tokens; i++) {
 388                 enabledProtocolsList[i] = st.nextToken();
 389             }
 390         }
 391 
 392         final String sslNeedClientAuthStr =
 393                 props.getProperty(PropertyNames.SSL_NEED_CLIENT_AUTH,
 394                 DefaultValues.SSL_NEED_CLIENT_AUTH);
 395         final boolean sslNeedClientAuth =
 396                 Boolean.valueOf(sslNeedClientAuthStr).booleanValue();
 397 
 398         // Read SSL config file name
 399         final String sslConfigFileName =
 400                 props.getProperty(PropertyNames.SSL_CONFIG_FILE_NAME);
 401 
 402         String loginConfigName = null;
 403         String passwordFileName = null;
 404         String accessFileName = null;
 405 
 406         // Initialize settings when authentication is active
 407         if (useAuthentication) {
 408 
 409             // Get non-default login configuration
 410             loginConfigName =
 411                     props.getProperty(PropertyNames.LOGIN_CONFIG_NAME);
 412 
 413             if (loginConfigName == null) {
 414                 // Get password file
 415                 passwordFileName =
 416                         props.getProperty(PropertyNames.PASSWORD_FILE_NAME,
 417                         getDefaultFileName(DefaultValues.PASSWORD_FILE_NAME));
 418                 checkPasswordFile(passwordFileName);
 419             }
 420 
 421             // Get access file
 422             accessFileName = props.getProperty(PropertyNames.ACCESS_FILE_NAME,
 423                     getDefaultFileName(DefaultValues.ACCESS_FILE_NAME));
 424             checkAccessFile(accessFileName);
 425         }
 426 
 427         if (log.debugOn()) {
 428             log.debug("startRemoteConnectorServer",
 429                     Agent.getText("jmxremote.ConnectorBootstrap.starting") +
 430                     "\n\t" + PropertyNames.PORT + "=" + port +
 431                     "\n\t" + PropertyNames.RMI_PORT + "=" + rmiPort +
 432                     "\n\t" + PropertyNames.USE_SSL + "=" + useSsl +
 433                     "\n\t" + PropertyNames.USE_REGISTRY_SSL + "=" + useRegistrySsl +
 434                     "\n\t" + PropertyNames.SSL_CONFIG_FILE_NAME + "=" + sslConfigFileName +
 435                     "\n\t" + PropertyNames.SSL_ENABLED_CIPHER_SUITES + "=" +
 436                     enabledCipherSuites +
 437                     "\n\t" + PropertyNames.SSL_ENABLED_PROTOCOLS + "=" +
 438                     enabledProtocols +
 439                     "\n\t" + PropertyNames.SSL_NEED_CLIENT_AUTH + "=" +
 440                     sslNeedClientAuth +
 441                     "\n\t" + PropertyNames.USE_AUTHENTICATION + "=" +
 442                     useAuthentication +
 443                     (useAuthentication ? (loginConfigName == null ? ("\n\t" + PropertyNames.PASSWORD_FILE_NAME + "=" +
 444                     passwordFileName) : ("\n\t" + PropertyNames.LOGIN_CONFIG_NAME + "=" +
 445                     loginConfigName)) : "\n\t" +
 446                     Agent.getText("jmxremote.ConnectorBootstrap.noAuthentication")) +
 447                     (useAuthentication ? ("\n\t" + PropertyNames.ACCESS_FILE_NAME + "=" +
 448                     accessFileName) : "") +
 449                     "");
 450         }
 451 
 452         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
 453         JMXConnectorServer cs = null;
 454         JMXServiceURL url = null;
 455         try {
 456             final JMXConnectorServerData data = exportMBeanServer(
 457                     mbs, port, rmiPort, useSsl, useRegistrySsl,
 458                     sslConfigFileName, enabledCipherSuitesList,
 459                     enabledProtocolsList, sslNeedClientAuth,
 460                     useAuthentication, loginConfigName,
 461                     passwordFileName, accessFileName);
 462             cs = data.jmxConnectorServer;
 463             url = data.jmxRemoteURL;
 464             log.config("startRemoteConnectorServer",
 465                     Agent.getText("jmxremote.ConnectorBootstrap.ready",
 466                     url.toString()));
 467         } catch (Exception e) {
 468             throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString());
 469         }
 470         try {
 471             // Export remote connector address and associated configuration
 472             // properties to the instrumentation buffer.
 473             Map<String, String> properties = new HashMap<>();
 474             properties.put("remoteAddress", url.toString());
 475             properties.put("authenticate", useAuthenticationStr);
 476             properties.put("ssl", useSslStr);
 477             properties.put("sslRegistry", useRegistrySslStr);
 478             properties.put("sslNeedClientAuth", sslNeedClientAuthStr);
 479             ConnectorAddressLink.exportRemote(properties);
 480         } catch (Exception e) {
 481             // Remote connector server started but unable to export remote
 482             // connector address and associated configuration properties to
 483             // the instrumentation buffer - non-fatal error.
 484             log.debug("startRemoteConnectorServer", e);
 485         }
 486         return cs;
 487     }
 488 
 489     /*
 490      * Creates and starts a RMI Connector Server for "local" monitoring
 491      * and management.
 492      */
 493     public static JMXConnectorServer startLocalConnectorServer() {
 494         // Ensure cryptographically strong random number generater used
 495         // to choose the object number - see java.rmi.server.ObjID
 496         System.setProperty("java.rmi.server.randomIDs", "true");
 497 
 498         // This RMI server should not keep the VM alive
 499         Map<String, Object> env = new HashMap<>();
 500         env.put(RMIExporter.EXPORTER_ATTRIBUTE, new PermanentExporter());
 501 
 502         // The local connector server need only be available via the
 503         // loopback connection.
 504         String localhost = "localhost";
 505         InetAddress lh = null;
 506         try {
 507             lh = InetAddress.getByName(localhost);
 508             localhost = lh.getHostAddress();
 509         } catch (UnknownHostException x) {
 510         }
 511 
 512         // localhost unknown or (somehow) didn't resolve to
 513         // a loopback address.
 514         if (lh == null || !lh.isLoopbackAddress()) {
 515             localhost = "127.0.0.1";
 516         }
 517 
 518         MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
 519         try {
 520             JMXServiceURL url = new JMXServiceURL("rmi", localhost, 0);
 521             // Do we accept connections from local interfaces only?
 522             Properties props = Agent.getManagementProperties();
 523             if (props ==  null) {
 524                 props = new Properties();
 525             }
 526             String useLocalOnlyStr = props.getProperty(
 527                     PropertyNames.USE_LOCAL_ONLY, DefaultValues.USE_LOCAL_ONLY);
 528             boolean useLocalOnly = Boolean.valueOf(useLocalOnlyStr).booleanValue();
 529             if (useLocalOnly) {
 530                 env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
 531                         new LocalRMIServerSocketFactory());
 532             }
 533             JMXConnectorServer server =
 534                     JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
 535             server.start();
 536             return server;
 537         } catch (Exception e) {
 538             throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString());
 539         }
 540     }
 541 
 542     private static void checkPasswordFile(String passwordFileName) {
 543         if (passwordFileName == null || passwordFileName.length() == 0) {
 544             throw new AgentConfigurationError(PASSWORD_FILE_NOT_SET);
 545         }
 546         File file = new File(passwordFileName);
 547         if (!file.exists()) {
 548             throw new AgentConfigurationError(PASSWORD_FILE_NOT_FOUND, passwordFileName);
 549         }
 550 
 551         if (!file.canRead()) {
 552             throw new AgentConfigurationError(PASSWORD_FILE_NOT_READABLE, passwordFileName);
 553         }
 554 
 555         FileSystem fs = FileSystem.open();
 556         try {
 557             if (fs.supportsFileSecurity(file)) {
 558                 if (!fs.isAccessUserOnly(file)) {
 559                     final String msg = Agent.getText("jmxremote.ConnectorBootstrap.password.readonly",
 560                             passwordFileName);
 561                     log.config("startRemoteConnectorServer", msg);
 562                     throw new AgentConfigurationError(PASSWORD_FILE_ACCESS_NOT_RESTRICTED,
 563                             passwordFileName);
 564                 }
 565             }
 566         } catch (IOException e) {
 567             throw new AgentConfigurationError(PASSWORD_FILE_READ_FAILED,
 568                     e, passwordFileName);
 569         }
 570     }
 571 
 572     private static void checkAccessFile(String accessFileName) {
 573         if (accessFileName == null || accessFileName.length() == 0) {
 574             throw new AgentConfigurationError(ACCESS_FILE_NOT_SET);
 575         }
 576         File file = new File(accessFileName);
 577         if (!file.exists()) {
 578             throw new AgentConfigurationError(ACCESS_FILE_NOT_FOUND, accessFileName);
 579         }
 580 
 581         if (!file.canRead()) {
 582             throw new AgentConfigurationError(ACCESS_FILE_NOT_READABLE, accessFileName);
 583         }
 584     }
 585 
 586     private static void checkRestrictedFile(String restrictedFileName) {
 587         if (restrictedFileName == null || restrictedFileName.length() == 0) {
 588             throw new AgentConfigurationError(FILE_NOT_SET);
 589         }
 590         File file = new File(restrictedFileName);
 591         if (!file.exists()) {
 592             throw new AgentConfigurationError(FILE_NOT_FOUND, restrictedFileName);
 593         }
 594         if (!file.canRead()) {
 595             throw new AgentConfigurationError(FILE_NOT_READABLE, restrictedFileName);
 596         }
 597         FileSystem fs = FileSystem.open();
 598         try {
 599             if (fs.supportsFileSecurity(file)) {
 600                 if (!fs.isAccessUserOnly(file)) {
 601                     final String msg = Agent.getText(
 602                             "jmxremote.ConnectorBootstrap.file.readonly",
 603                             restrictedFileName);
 604                     log.config("startRemoteConnectorServer", msg);
 605                     throw new AgentConfigurationError(
 606                             FILE_ACCESS_NOT_RESTRICTED, restrictedFileName);
 607                 }
 608             }
 609         } catch (IOException e) {
 610             throw new AgentConfigurationError(
 611                     FILE_READ_FAILED, e, restrictedFileName);
 612         }
 613     }
 614 
 615     /**
 616      * Compute the full path name for a default file.
 617      * @param basename basename (with extension) of the default file.
 618      * @return ${JRE}/lib/management/${basename}
 619      **/
 620     private static String getDefaultFileName(String basename) {
 621         final String fileSeparator = File.separator;
 622         return System.getProperty("java.home") + fileSeparator + "lib" +
 623                 fileSeparator + "management" + fileSeparator +
 624                 basename;
 625     }
 626 
 627     private static SslRMIServerSocketFactory createSslRMIServerSocketFactory(
 628             String sslConfigFileName,
 629             String[] enabledCipherSuites,
 630             String[] enabledProtocols,
 631             boolean sslNeedClientAuth) {
 632         if (sslConfigFileName == null) {
 633             return new SslRMIServerSocketFactory(
 634                     enabledCipherSuites,
 635                     enabledProtocols,
 636                     sslNeedClientAuth);
 637         } else {
 638             checkRestrictedFile(sslConfigFileName);
 639             try {
 640                 // Load the SSL keystore properties from the config file
 641                 Properties p = new Properties();
 642                 try (InputStream in = new FileInputStream(sslConfigFileName)) {
 643                     BufferedInputStream bin = new BufferedInputStream(in);
 644                     p.load(bin);
 645                 }
 646                 String keyStore =
 647                         p.getProperty("javax.net.ssl.keyStore");
 648                 String keyStorePassword =
 649                         p.getProperty("javax.net.ssl.keyStorePassword", "");
 650                 String trustStore =
 651                         p.getProperty("javax.net.ssl.trustStore");
 652                 String trustStorePassword =
 653                         p.getProperty("javax.net.ssl.trustStorePassword", "");
 654 
 655                 char[] keyStorePasswd = null;
 656                 if (keyStorePassword.length() != 0) {
 657                     keyStorePasswd = keyStorePassword.toCharArray();
 658                 }
 659 
 660                 char[] trustStorePasswd = null;
 661                 if (trustStorePassword.length() != 0) {
 662                     trustStorePasswd = trustStorePassword.toCharArray();
 663                 }
 664 
 665                 KeyStore ks = null;
 666                 if (keyStore != null) {
 667                     ks = KeyStore.getInstance(KeyStore.getDefaultType());
 668                     try (FileInputStream ksfis = new FileInputStream(keyStore)) {
 669                         ks.load(ksfis, keyStorePasswd);
 670                     }
 671                 }
 672                 KeyManagerFactory kmf = KeyManagerFactory.getInstance(
 673                         KeyManagerFactory.getDefaultAlgorithm());
 674                 kmf.init(ks, keyStorePasswd);
 675 
 676                 KeyStore ts = null;
 677                 if (trustStore != null) {
 678                     ts = KeyStore.getInstance(KeyStore.getDefaultType());
 679                     try (FileInputStream tsfis = new FileInputStream(trustStore)) {
 680                         ts.load(tsfis, trustStorePasswd);
 681                     }
 682                 }
 683                 TrustManagerFactory tmf = TrustManagerFactory.getInstance(
 684                         TrustManagerFactory.getDefaultAlgorithm());
 685                 tmf.init(ts);
 686 
 687                 SSLContext ctx = SSLContext.getInstance("SSL");
 688                 ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
 689 
 690                 return new SslRMIServerSocketFactory(
 691                         ctx,
 692                         enabledCipherSuites,
 693                         enabledProtocols,
 694                         sslNeedClientAuth);
 695             } catch (Exception e) {
 696                 throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString());
 697             }
 698         }
 699     }
 700 
 701     private static JMXConnectorServerData exportMBeanServer(
 702             MBeanServer mbs,
 703             int port,
 704             int rmiPort,
 705             boolean useSsl,
 706             boolean useRegistrySsl,
 707             String sslConfigFileName,
 708             String[] enabledCipherSuites,
 709             String[] enabledProtocols,
 710             boolean sslNeedClientAuth,
 711             boolean useAuthentication,
 712             String loginConfigName,
 713             String passwordFileName,
 714             String accessFileName)
 715             throws IOException, MalformedURLException {
 716 
 717         /* Make sure we use non-guessable RMI object IDs.  Otherwise
 718          * attackers could hijack open connections by guessing their
 719          * IDs.  */
 720         System.setProperty("java.rmi.server.randomIDs", "true");
 721 
 722         JMXServiceURL url = new JMXServiceURL("rmi", null, rmiPort);
 723 
 724         Map<String, Object> env = new HashMap<>();
 725 
 726         PermanentExporter exporter = new PermanentExporter();
 727 
 728         env.put(RMIExporter.EXPORTER_ATTRIBUTE, exporter);
 729 
 730         if (useAuthentication) {
 731             if (loginConfigName != null) {
 732                 env.put("jmx.remote.x.login.config", loginConfigName);
 733             }
 734             if (passwordFileName != null) {
 735                 env.put("jmx.remote.x.password.file", passwordFileName);
 736             }
 737 
 738             env.put("jmx.remote.x.access.file", accessFileName);
 739 
 740             if (env.get("jmx.remote.x.password.file") != null ||
 741                     env.get("jmx.remote.x.login.config") != null) {
 742                 env.put(JMXConnectorServer.AUTHENTICATOR,
 743                         new AccessFileCheckerAuthenticator(env));
 744             }
 745         }
 746 
 747         RMIClientSocketFactory csf = null;
 748         RMIServerSocketFactory ssf = null;
 749 
 750         if (useSsl || useRegistrySsl) {
 751             csf = new SslRMIClientSocketFactory();
 752             ssf = createSslRMIServerSocketFactory(
 753                     sslConfigFileName, enabledCipherSuites,
 754                     enabledProtocols, sslNeedClientAuth);
 755         }
 756 
 757         if (useSsl) {
 758             env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE,
 759                     csf);
 760             env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
 761                     ssf);
 762         }
 763 
 764         JMXConnectorServer connServer = null;
 765         try {
 766             connServer =
 767                     JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
 768             connServer.start();
 769         } catch (IOException e) {
 770             if (connServer == null || connServer.getAddress() == null) {
 771                 throw new AgentConfigurationError(CONNECTOR_SERVER_IO_ERROR,
 772                         e, url.toString());
 773             } else {
 774                 throw new AgentConfigurationError(CONNECTOR_SERVER_IO_ERROR,
 775                         e, connServer.getAddress().toString());
 776             }
 777         }
 778 
 779         if (useRegistrySsl) {
 780             registry =
 781                     new SingleEntryRegistry(port, csf, ssf,
 782                     "jmxrmi", exporter.firstExported);
 783         } else {
 784             registry =
 785                     new SingleEntryRegistry(port,
 786                     "jmxrmi", exporter.firstExported);
 787         }
 788 
 789 
 790         int registryPort =
 791             ((UnicastRef) ((RemoteObject) registry).getRef()).getLiveRef().getPort();
 792         String jmxUrlStr =  String.format("service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi",
 793                                            url.getHost(), registryPort);
 794         JMXServiceURL remoteURL = new JMXServiceURL(jmxUrlStr);
 795 
 796         /* Our exporter remembers the first object it was asked to
 797         export, which will be an RMIServerImpl appropriate for
 798         publication in our special registry.  We could
 799         alternatively have constructed the RMIServerImpl explicitly
 800         and then constructed an RMIConnectorServer passing it as a
 801         parameter, but that's quite a bit more verbose and pulls in
 802         lots of knowledge of the RMI connector.  */
 803 
 804         return new JMXConnectorServerData(connServer, remoteURL);
 805     }
 806 
 807     /**
 808      * This class cannot be instantiated.
 809      **/
 810     private ConnectorBootstrap() {
 811     }
 812 
 813     private static final ClassLogger log =
 814         new ClassLogger(ConnectorBootstrap.class.getPackage().getName(),
 815                         "ConnectorBootstrap");
 816 }