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