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