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