| /* |
| * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.management.jmxremote; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Serializable; |
| import java.lang.management.ManagementFactory; |
| import java.net.InetAddress; |
| import java.net.MalformedURLException; |
| import java.net.Socket; |
| import java.net.ServerSocket; |
| import java.net.UnknownHostException; |
| import java.rmi.NoSuchObjectException; |
| import java.rmi.Remote; |
| import java.rmi.RemoteException; |
| import java.rmi.registry.Registry; |
| import java.rmi.server.RMIClientSocketFactory; |
| import java.rmi.server.RMIServerSocketFactory; |
| import java.rmi.server.RMISocketFactory; |
| import java.rmi.server.RemoteObject; |
| import java.rmi.server.UnicastRemoteObject; |
| import java.security.KeyStore; |
| import java.security.Principal; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| |
| import javax.management.MBeanServer; |
| import javax.management.remote.JMXAuthenticator; |
| import javax.management.remote.JMXConnectorServer; |
| import javax.management.remote.JMXConnectorServerFactory; |
| import javax.management.remote.JMXServiceURL; |
| import javax.management.remote.rmi.RMIConnectorServer; |
| import javax.net.ssl.KeyManagerFactory; |
| import javax.net.ssl.SSLContext; |
| import javax.net.ssl.SSLSocket; |
| import javax.net.ssl.SSLSocketFactory; |
| import javax.net.ssl.TrustManagerFactory; |
| import javax.rmi.ssl.SslRMIClientSocketFactory; |
| import javax.rmi.ssl.SslRMIServerSocketFactory; |
| import javax.security.auth.Subject; |
| |
| import com.sun.jmx.remote.internal.RMIExporter; |
| import com.sun.jmx.remote.security.JMXPluggableAuthenticator; |
| import com.sun.jmx.remote.util.ClassLogger; |
| |
| import sun.management.Agent; |
| import sun.management.AgentConfigurationError; |
| import static sun.management.AgentConfigurationError.*; |
| import sun.management.ConnectorAddressLink; |
| import sun.management.FileSystem; |
| import sun.rmi.server.UnicastRef; |
| import sun.rmi.server.UnicastServerRef; |
| import sun.rmi.server.UnicastServerRef2; |
| |
| /** |
| * This class initializes and starts the RMIConnectorServer for JSR 163 |
| * JMX Monitoring. |
| **/ |
| public final class ConnectorBootstrap { |
| |
| /** |
| * Default values for JMX configuration properties. |
| **/ |
| public static interface DefaultValues { |
| |
| public static final String PORT = "0"; |
| public static final String CONFIG_FILE_NAME = "management.properties"; |
| public static final String USE_SSL = "true"; |
| public static final String USE_LOCAL_ONLY = "true"; |
| public static final String USE_REGISTRY_SSL = "false"; |
| public static final String USE_AUTHENTICATION = "true"; |
| public static final String PASSWORD_FILE_NAME = "jmxremote.password"; |
| public static final String ACCESS_FILE_NAME = "jmxremote.access"; |
| public static final String SSL_NEED_CLIENT_AUTH = "false"; |
| } |
| |
| /** |
| * Names of JMX configuration properties. |
| **/ |
| public static interface PropertyNames { |
| |
| public static final String PORT = |
| "com.sun.management.jmxremote.port"; |
| public static final String HOST = |
| "com.sun.management.jmxremote.host"; |
| public static final String RMI_PORT = |
| "com.sun.management.jmxremote.rmi.port"; |
| public static final String CONFIG_FILE_NAME = |
| "com.sun.management.config.file"; |
| public static final String USE_LOCAL_ONLY = |
| "com.sun.management.jmxremote.local.only"; |
| public static final String USE_SSL = |
| "com.sun.management.jmxremote.ssl"; |
| public static final String USE_REGISTRY_SSL = |
| "com.sun.management.jmxremote.registry.ssl"; |
| public static final String USE_AUTHENTICATION = |
| "com.sun.management.jmxremote.authenticate"; |
| public static final String PASSWORD_FILE_NAME = |
| "com.sun.management.jmxremote.password.file"; |
| public static final String ACCESS_FILE_NAME = |
| "com.sun.management.jmxremote.access.file"; |
| public static final String LOGIN_CONFIG_NAME = |
| "com.sun.management.jmxremote.login.config"; |
| public static final String SSL_ENABLED_CIPHER_SUITES = |
| "com.sun.management.jmxremote.ssl.enabled.cipher.suites"; |
| public static final String SSL_ENABLED_PROTOCOLS = |
| "com.sun.management.jmxremote.ssl.enabled.protocols"; |
| public static final String SSL_NEED_CLIENT_AUTH = |
| "com.sun.management.jmxremote.ssl.need.client.auth"; |
| public static final String SSL_CONFIG_FILE_NAME = |
| "com.sun.management.jmxremote.ssl.config.file"; |
| } |
| |
| /** |
| * JMXConnectorServer associated data. |
| */ |
| private static class JMXConnectorServerData { |
| |
| public JMXConnectorServerData( |
| JMXConnectorServer jmxConnectorServer, |
| JMXServiceURL jmxRemoteURL) { |
| this.jmxConnectorServer = jmxConnectorServer; |
| this.jmxRemoteURL = jmxRemoteURL; |
| } |
| JMXConnectorServer jmxConnectorServer; |
| JMXServiceURL jmxRemoteURL; |
| } |
| |
| /** |
| * <p>Prevents our RMI server objects from keeping the JVM alive.</p> |
| * |
| * <p>We use a private interface in Sun's JMX Remote API implementation |
| * that allows us to specify how to export RMI objects. We do so using |
| * UnicastServerRef, a class in Sun's RMI implementation. This is all |
| * non-portable, of course, so this is only valid because we are inside |
| * Sun's JRE.</p> |
| * |
| * <p>Objects are exported using {@link |
| * UnicastServerRef#exportObject(Remote, Object, boolean)}. The |
| * boolean parameter is called <code>permanent</code> and means |
| * both that the object is not eligible for Distributed Garbage |
| * Collection, and that its continued existence will not prevent |
| * the JVM from exiting. It is the latter semantics we want (we |
| * already have the former because of the way the JMX Remote API |
| * works). Hence the somewhat misleading name of this class.</p> |
| */ |
| private static class PermanentExporter implements RMIExporter { |
| |
| public Remote exportObject(Remote obj, |
| int port, |
| RMIClientSocketFactory csf, |
| RMIServerSocketFactory ssf) |
| throws RemoteException { |
| |
| synchronized (this) { |
| if (firstExported == null) { |
| firstExported = obj; |
| } |
| } |
| |
| final UnicastServerRef ref; |
| if (csf == null && ssf == null) { |
| ref = new UnicastServerRef(port); |
| } else { |
| ref = new UnicastServerRef2(port, csf, ssf); |
| } |
| return ref.exportObject(obj, null, true); |
| } |
| |
| // Nothing special to be done for this case |
| public boolean unexportObject(Remote obj, boolean force) |
| throws NoSuchObjectException { |
| return UnicastRemoteObject.unexportObject(obj, force); |
| } |
| Remote firstExported; |
| } |
| |
| /** |
| * This JMXAuthenticator wraps the JMXPluggableAuthenticator and verifies |
| * that at least one of the principal names contained in the authenticated |
| * Subject is present in the access file. |
| */ |
| private static class AccessFileCheckerAuthenticator |
| implements JMXAuthenticator { |
| |
| public AccessFileCheckerAuthenticator(Map<String, Object> env) throws IOException { |
| environment = env; |
| accessFile = (String) env.get("jmx.remote.x.access.file"); |
| properties = propertiesFromFile(accessFile); |
| } |
| |
| public Subject authenticate(Object credentials) { |
| final JMXAuthenticator authenticator = |
| new JMXPluggableAuthenticator(environment); |
| final Subject subject = authenticator.authenticate(credentials); |
| checkAccessFileEntries(subject); |
| return subject; |
| } |
| |
| private void checkAccessFileEntries(Subject subject) { |
| if (subject == null) { |
| throw new SecurityException( |
| "Access denied! No matching entries found in " + |
| "the access file [" + accessFile + "] as the " + |
| "authenticated Subject is null"); |
| } |
| final Set<Principal> principals = subject.getPrincipals(); |
| for (Principal p1: principals) { |
| if (properties.containsKey(p1.getName())) { |
| return; |
| } |
| } |
| |
| final Set<String> principalsStr = new HashSet<>(); |
| for (Principal p2: principals) { |
| principalsStr.add(p2.getName()); |
| } |
| throw new SecurityException( |
| "Access denied! No entries found in the access file [" + |
| accessFile + "] for any of the authenticated identities " + |
| principalsStr); |
| } |
| |
| private static Properties propertiesFromFile(String fname) |
| throws IOException { |
| Properties p = new Properties(); |
| if (fname == null) { |
| return p; |
| } |
| try (FileInputStream fin = new FileInputStream(fname)) { |
| p.load(fin); |
| } |
| return p; |
| } |
| private final Map<String, Object> environment; |
| private final Properties properties; |
| private final String accessFile; |
| } |
| |
| // The variable below is here to support stop functionality |
| // It would be overriten if you call startRemoteCommectionServer second |
| // time. It's OK for now as logic in Agent.java forbids mutiple agents |
| private static Registry registry = null; |
| |
| public static void unexportRegistry() { |
| // Remove the entry from registry |
| try { |
| if (registry != null) { |
| UnicastRemoteObject.unexportObject(registry, true); |
| registry = null; |
| } |
| } catch(NoSuchObjectException ex) { |
| // This exception can appears only if we attempt |
| // to unexportRegistry second time. So it's safe |
| // to ignore it without additional messages. |
| } |
| } |
| |
| /** |
| * Initializes and starts the JMX Connector Server. |
| * If the com.sun.management.jmxremote.port property is not defined, |
| * simply return. Otherwise, attempts to load the config file, and |
| * then calls {@link #startRemoteConnectorServer |
| * (java.lang.String, java.util.Properties)}. |
| * |
| * This method is used by some jtreg tests. |
| **/ |
| public static synchronized JMXConnectorServer initialize() { |
| |
| // Load a new management properties |
| final Properties props = Agent.loadManagementProperties(); |
| if (props == null) { |
| return null; |
| } |
| |
| final String portStr = props.getProperty(PropertyNames.PORT); |
| return startRemoteConnectorServer(portStr, props); |
| } |
| |
| /** |
| * This method is used by some jtreg tests. |
| * |
| * @see #startRemoteConnectorServer |
| * (String portStr, Properties props) |
| */ |
| public static synchronized JMXConnectorServer initialize(String portStr, Properties props) { |
| return startRemoteConnectorServer(portStr, props); |
| } |
| |
| /** |
| * Initializes and starts a JMX Connector Server for remote |
| * monitoring and management. |
| **/ |
| public static synchronized JMXConnectorServer startRemoteConnectorServer(String portStr, Properties props) { |
| |
| // Get port number |
| final int port; |
| try { |
| port = Integer.parseInt(portStr); |
| } catch (NumberFormatException x) { |
| throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, x, portStr); |
| } |
| if (port < 0) { |
| throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, portStr); |
| } |
| |
| // User can specify a port to be used to export rmi object, |
| // in order to simplify firewall rules |
| // if port is not specified random one will be allocated. |
| int rmiPort = 0; |
| String rmiPortStr = props.getProperty(PropertyNames.RMI_PORT); |
| try { |
| if (rmiPortStr != null) { |
| rmiPort = Integer.parseInt(rmiPortStr); |
| } |
| } catch (NumberFormatException x) { |
| throw new AgentConfigurationError(INVALID_JMXREMOTE_RMI_PORT, x, rmiPortStr); |
| } |
| if (rmiPort < 0) { |
| throw new AgentConfigurationError(INVALID_JMXREMOTE_RMI_PORT, rmiPortStr); |
| } |
| |
| // Do we use authentication? |
| final String useAuthenticationStr = |
| props.getProperty(PropertyNames.USE_AUTHENTICATION, |
| DefaultValues.USE_AUTHENTICATION); |
| final boolean useAuthentication = |
| Boolean.valueOf(useAuthenticationStr).booleanValue(); |
| |
| // Do we use SSL? |
| final String useSslStr = |
| props.getProperty(PropertyNames.USE_SSL, |
| DefaultValues.USE_SSL); |
| final boolean useSsl = |
| Boolean.valueOf(useSslStr).booleanValue(); |
| |
| // Do we use RMI Registry SSL? |
| final String useRegistrySslStr = |
| props.getProperty(PropertyNames.USE_REGISTRY_SSL, |
| DefaultValues.USE_REGISTRY_SSL); |
| final boolean useRegistrySsl = |
| Boolean.valueOf(useRegistrySslStr).booleanValue(); |
| |
| final String enabledCipherSuites = |
| props.getProperty(PropertyNames.SSL_ENABLED_CIPHER_SUITES); |
| String enabledCipherSuitesList[] = null; |
| if (enabledCipherSuites != null) { |
| StringTokenizer st = new StringTokenizer(enabledCipherSuites, ","); |
| int tokens = st.countTokens(); |
| enabledCipherSuitesList = new String[tokens]; |
| for (int i = 0; i < tokens; i++) { |
| enabledCipherSuitesList[i] = st.nextToken(); |
| } |
| } |
| |
| final String enabledProtocols = |
| props.getProperty(PropertyNames.SSL_ENABLED_PROTOCOLS); |
| String enabledProtocolsList[] = null; |
| if (enabledProtocols != null) { |
| StringTokenizer st = new StringTokenizer(enabledProtocols, ","); |
| int tokens = st.countTokens(); |
| enabledProtocolsList = new String[tokens]; |
| for (int i = 0; i < tokens; i++) { |
| enabledProtocolsList[i] = st.nextToken(); |
| } |
| } |
| |
| final String sslNeedClientAuthStr = |
| props.getProperty(PropertyNames.SSL_NEED_CLIENT_AUTH, |
| DefaultValues.SSL_NEED_CLIENT_AUTH); |
| final boolean sslNeedClientAuth = |
| Boolean.valueOf(sslNeedClientAuthStr).booleanValue(); |
| |
| // Read SSL config file name |
| final String sslConfigFileName = |
| props.getProperty(PropertyNames.SSL_CONFIG_FILE_NAME); |
| |
| String loginConfigName = null; |
| String passwordFileName = null; |
| String accessFileName = null; |
| |
| // Initialize settings when authentication is active |
| if (useAuthentication) { |
| |
| // Get non-default login configuration |
| loginConfigName = |
| props.getProperty(PropertyNames.LOGIN_CONFIG_NAME); |
| |
| if (loginConfigName == null) { |
| // Get password file |
| passwordFileName = |
| props.getProperty(PropertyNames.PASSWORD_FILE_NAME, |
| getDefaultFileName(DefaultValues.PASSWORD_FILE_NAME)); |
| checkPasswordFile(passwordFileName); |
| } |
| |
| // Get access file |
| accessFileName = props.getProperty(PropertyNames.ACCESS_FILE_NAME, |
| getDefaultFileName(DefaultValues.ACCESS_FILE_NAME)); |
| checkAccessFile(accessFileName); |
| } |
| |
| final String bindAddress = |
| props.getProperty(PropertyNames.HOST); |
| |
| if (log.debugOn()) { |
| log.debug("startRemoteConnectorServer", |
| Agent.getText("jmxremote.ConnectorBootstrap.starting") + |
| "\n\t" + PropertyNames.PORT + "=" + port + |
| (bindAddress == null ? "" : "\n\t" + PropertyNames.HOST + "=" + bindAddress) + |
| "\n\t" + PropertyNames.RMI_PORT + "=" + rmiPort + |
| "\n\t" + PropertyNames.USE_SSL + "=" + useSsl + |
| "\n\t" + PropertyNames.USE_REGISTRY_SSL + "=" + useRegistrySsl + |
| "\n\t" + PropertyNames.SSL_CONFIG_FILE_NAME + "=" + sslConfigFileName + |
| "\n\t" + PropertyNames.SSL_ENABLED_CIPHER_SUITES + "=" + |
| enabledCipherSuites + |
| "\n\t" + PropertyNames.SSL_ENABLED_PROTOCOLS + "=" + |
| enabledProtocols + |
| "\n\t" + PropertyNames.SSL_NEED_CLIENT_AUTH + "=" + |
| sslNeedClientAuth + |
| "\n\t" + PropertyNames.USE_AUTHENTICATION + "=" + |
| useAuthentication + |
| (useAuthentication ? (loginConfigName == null ? ("\n\t" + PropertyNames.PASSWORD_FILE_NAME + "=" + |
| passwordFileName) : ("\n\t" + PropertyNames.LOGIN_CONFIG_NAME + "=" + |
| loginConfigName)) : "\n\t" + |
| Agent.getText("jmxremote.ConnectorBootstrap.noAuthentication")) + |
| (useAuthentication ? ("\n\t" + PropertyNames.ACCESS_FILE_NAME + "=" + |
| accessFileName) : "") + |
| ""); |
| } |
| |
| final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); |
| JMXConnectorServer cs = null; |
| JMXServiceURL url = null; |
| try { |
| final JMXConnectorServerData data = exportMBeanServer( |
| mbs, port, rmiPort, useSsl, useRegistrySsl, |
| sslConfigFileName, enabledCipherSuitesList, |
| enabledProtocolsList, sslNeedClientAuth, |
| useAuthentication, loginConfigName, |
| passwordFileName, accessFileName, bindAddress); |
| cs = data.jmxConnectorServer; |
| url = data.jmxRemoteURL; |
| log.config("startRemoteConnectorServer", |
| Agent.getText("jmxremote.ConnectorBootstrap.ready", |
| url.toString())); |
| } catch (Exception e) { |
| throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString()); |
| } |
| try { |
| // Export remote connector address and associated configuration |
| // properties to the instrumentation buffer. |
| Map<String, String> properties = new HashMap<>(); |
| properties.put("remoteAddress", url.toString()); |
| properties.put("authenticate", useAuthenticationStr); |
| properties.put("ssl", useSslStr); |
| properties.put("sslRegistry", useRegistrySslStr); |
| properties.put("sslNeedClientAuth", sslNeedClientAuthStr); |
| ConnectorAddressLink.exportRemote(properties); |
| } catch (Exception e) { |
| // Remote connector server started but unable to export remote |
| // connector address and associated configuration properties to |
| // the instrumentation buffer - non-fatal error. |
| log.debug("startRemoteConnectorServer", e); |
| } |
| return cs; |
| } |
| |
| /* |
| * Creates and starts a RMI Connector Server for "local" monitoring |
| * and management. |
| */ |
| public static JMXConnectorServer startLocalConnectorServer() { |
| // Ensure cryptographically strong random number generater used |
| // to choose the object number - see java.rmi.server.ObjID |
| System.setProperty("java.rmi.server.randomIDs", "true"); |
| |
| // This RMI server should not keep the VM alive |
| Map<String, Object> env = new HashMap<>(); |
| env.put(RMIExporter.EXPORTER_ATTRIBUTE, new PermanentExporter()); |
| env.put(RMIConnectorServer.CREDENTIAL_TYPES, new String[]{ |
| String[].class.getName(), String.class.getName() |
| }); |
| |
| // The local connector server need only be available via the |
| // loopback connection. |
| String localhost = "localhost"; |
| InetAddress lh = null; |
| try { |
| lh = InetAddress.getByName(localhost); |
| localhost = lh.getHostAddress(); |
| } catch (UnknownHostException x) { |
| } |
| |
| // localhost unknown or (somehow) didn't resolve to |
| // a loopback address. |
| if (lh == null || !lh.isLoopbackAddress()) { |
| localhost = "127.0.0.1"; |
| } |
| |
| MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); |
| try { |
| JMXServiceURL url = new JMXServiceURL("rmi", localhost, 0); |
| // Do we accept connections from local interfaces only? |
| Properties props = Agent.getManagementProperties(); |
| if (props == null) { |
| props = new Properties(); |
| } |
| String useLocalOnlyStr = props.getProperty( |
| PropertyNames.USE_LOCAL_ONLY, DefaultValues.USE_LOCAL_ONLY); |
| boolean useLocalOnly = Boolean.valueOf(useLocalOnlyStr).booleanValue(); |
| if (useLocalOnly) { |
| env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, |
| new LocalRMIServerSocketFactory()); |
| } |
| JMXConnectorServer server = |
| JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); |
| server.start(); |
| return server; |
| } catch (Exception e) { |
| throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString()); |
| } |
| } |
| |
| private static void checkPasswordFile(String passwordFileName) { |
| if (passwordFileName == null || passwordFileName.length() == 0) { |
| throw new AgentConfigurationError(PASSWORD_FILE_NOT_SET); |
| } |
| File file = new File(passwordFileName); |
| if (!file.exists()) { |
| throw new AgentConfigurationError(PASSWORD_FILE_NOT_FOUND, passwordFileName); |
| } |
| |
| if (!file.canRead()) { |
| throw new AgentConfigurationError(PASSWORD_FILE_NOT_READABLE, passwordFileName); |
| } |
| |
| FileSystem fs = FileSystem.open(); |
| try { |
| if (fs.supportsFileSecurity(file)) { |
| if (!fs.isAccessUserOnly(file)) { |
| final String msg = Agent.getText("jmxremote.ConnectorBootstrap.password.readonly", |
| passwordFileName); |
| log.config("startRemoteConnectorServer", msg); |
| throw new AgentConfigurationError(PASSWORD_FILE_ACCESS_NOT_RESTRICTED, |
| passwordFileName); |
| } |
| } |
| } catch (IOException e) { |
| throw new AgentConfigurationError(PASSWORD_FILE_READ_FAILED, |
| e, passwordFileName); |
| } |
| } |
| |
| private static void checkAccessFile(String accessFileName) { |
| if (accessFileName == null || accessFileName.length() == 0) { |
| throw new AgentConfigurationError(ACCESS_FILE_NOT_SET); |
| } |
| File file = new File(accessFileName); |
| if (!file.exists()) { |
| throw new AgentConfigurationError(ACCESS_FILE_NOT_FOUND, accessFileName); |
| } |
| |
| if (!file.canRead()) { |
| throw new AgentConfigurationError(ACCESS_FILE_NOT_READABLE, accessFileName); |
| } |
| } |
| |
| private static void checkRestrictedFile(String restrictedFileName) { |
| if (restrictedFileName == null || restrictedFileName.length() == 0) { |
| throw new AgentConfigurationError(FILE_NOT_SET); |
| } |
| File file = new File(restrictedFileName); |
| if (!file.exists()) { |
| throw new AgentConfigurationError(FILE_NOT_FOUND, restrictedFileName); |
| } |
| if (!file.canRead()) { |
| throw new AgentConfigurationError(FILE_NOT_READABLE, restrictedFileName); |
| } |
| FileSystem fs = FileSystem.open(); |
| try { |
| if (fs.supportsFileSecurity(file)) { |
| if (!fs.isAccessUserOnly(file)) { |
| final String msg = Agent.getText( |
| "jmxremote.ConnectorBootstrap.file.readonly", |
| restrictedFileName); |
| log.config("startRemoteConnectorServer", msg); |
| throw new AgentConfigurationError( |
| FILE_ACCESS_NOT_RESTRICTED, restrictedFileName); |
| } |
| } |
| } catch (IOException e) { |
| throw new AgentConfigurationError( |
| FILE_READ_FAILED, e, restrictedFileName); |
| } |
| } |
| |
| /** |
| * Compute the full path name for a default file. |
| * @param basename basename (with extension) of the default file. |
| * @return ${JRE}/conf/management/${basename} |
| **/ |
| private static String getDefaultFileName(String basename) { |
| final String fileSeparator = File.separator; |
| return System.getProperty("java.home") + fileSeparator + "conf" + |
| fileSeparator + "management" + fileSeparator + |
| basename; |
| } |
| |
| private static SslRMIServerSocketFactory createSslRMIServerSocketFactory( |
| String sslConfigFileName, |
| String[] enabledCipherSuites, |
| String[] enabledProtocols, |
| boolean sslNeedClientAuth, |
| String bindAddress) { |
| if (sslConfigFileName == null) { |
| return new HostAwareSslSocketFactory( |
| enabledCipherSuites, |
| enabledProtocols, |
| sslNeedClientAuth, bindAddress); |
| } else { |
| checkRestrictedFile(sslConfigFileName); |
| try { |
| // Load the SSL keystore properties from the config file |
| Properties p = new Properties(); |
| try (InputStream in = new FileInputStream(sslConfigFileName)) { |
| BufferedInputStream bin = new BufferedInputStream(in); |
| p.load(bin); |
| } |
| String keyStore = |
| p.getProperty("javax.net.ssl.keyStore"); |
| String keyStorePassword = |
| p.getProperty("javax.net.ssl.keyStorePassword", ""); |
| String trustStore = |
| p.getProperty("javax.net.ssl.trustStore"); |
| String trustStorePassword = |
| p.getProperty("javax.net.ssl.trustStorePassword", ""); |
| |
| char[] keyStorePasswd = null; |
| if (keyStorePassword.length() != 0) { |
| keyStorePasswd = keyStorePassword.toCharArray(); |
| } |
| |
| char[] trustStorePasswd = null; |
| if (trustStorePassword.length() != 0) { |
| trustStorePasswd = trustStorePassword.toCharArray(); |
| } |
| |
| KeyStore ks = null; |
| if (keyStore != null) { |
| ks = KeyStore.getInstance(KeyStore.getDefaultType()); |
| try (FileInputStream ksfis = new FileInputStream(keyStore)) { |
| ks.load(ksfis, keyStorePasswd); |
| } |
| } |
| KeyManagerFactory kmf = KeyManagerFactory.getInstance( |
| KeyManagerFactory.getDefaultAlgorithm()); |
| kmf.init(ks, keyStorePasswd); |
| |
| KeyStore ts = null; |
| if (trustStore != null) { |
| ts = KeyStore.getInstance(KeyStore.getDefaultType()); |
| try (FileInputStream tsfis = new FileInputStream(trustStore)) { |
| ts.load(tsfis, trustStorePasswd); |
| } |
| } |
| TrustManagerFactory tmf = TrustManagerFactory.getInstance( |
| TrustManagerFactory.getDefaultAlgorithm()); |
| tmf.init(ts); |
| |
| SSLContext ctx = SSLContext.getInstance("SSL"); |
| ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); |
| |
| return new HostAwareSslSocketFactory( |
| ctx, |
| enabledCipherSuites, |
| enabledProtocols, |
| sslNeedClientAuth, bindAddress); |
| } catch (Exception e) { |
| throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString()); |
| } |
| } |
| } |
| |
| private static JMXConnectorServerData exportMBeanServer( |
| MBeanServer mbs, |
| int port, |
| int rmiPort, |
| boolean useSsl, |
| boolean useRegistrySsl, |
| String sslConfigFileName, |
| String[] enabledCipherSuites, |
| String[] enabledProtocols, |
| boolean sslNeedClientAuth, |
| boolean useAuthentication, |
| String loginConfigName, |
| String passwordFileName, |
| String accessFileName, |
| String bindAddress) |
| throws IOException, MalformedURLException { |
| |
| /* Make sure we use non-guessable RMI object IDs. Otherwise |
| * attackers could hijack open connections by guessing their |
| * IDs. */ |
| System.setProperty("java.rmi.server.randomIDs", "true"); |
| |
| JMXServiceURL url = new JMXServiceURL("rmi", bindAddress, rmiPort); |
| |
| Map<String, Object> env = new HashMap<>(); |
| |
| PermanentExporter exporter = new PermanentExporter(); |
| |
| env.put(RMIExporter.EXPORTER_ATTRIBUTE, exporter); |
| env.put(RMIConnectorServer.CREDENTIAL_TYPES, new String[]{ |
| String[].class.getName(), String.class.getName() |
| }); |
| |
| boolean useSocketFactory = bindAddress != null && !useSsl; |
| |
| if (useAuthentication) { |
| if (loginConfigName != null) { |
| env.put("jmx.remote.x.login.config", loginConfigName); |
| } |
| if (passwordFileName != null) { |
| env.put("jmx.remote.x.password.file", passwordFileName); |
| } |
| |
| env.put("jmx.remote.x.access.file", accessFileName); |
| |
| if (env.get("jmx.remote.x.password.file") != null || |
| env.get("jmx.remote.x.login.config") != null) { |
| env.put(JMXConnectorServer.AUTHENTICATOR, |
| new AccessFileCheckerAuthenticator(env)); |
| } |
| } |
| |
| RMIClientSocketFactory csf = null; |
| RMIServerSocketFactory ssf = null; |
| |
| if (useSsl || useRegistrySsl) { |
| csf = new SslRMIClientSocketFactory(); |
| ssf = createSslRMIServerSocketFactory( |
| sslConfigFileName, enabledCipherSuites, |
| enabledProtocols, sslNeedClientAuth, bindAddress); |
| } |
| |
| if (useSsl) { |
| env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, |
| csf); |
| env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, |
| ssf); |
| } |
| |
| if (useSocketFactory) { |
| ssf = new HostAwareSocketFactory(bindAddress); |
| env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, |
| ssf); |
| } |
| |
| JMXConnectorServer connServer = null; |
| try { |
| connServer = |
| JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); |
| connServer.start(); |
| } catch (IOException e) { |
| if (connServer == null || connServer.getAddress() == null) { |
| throw new AgentConfigurationError(CONNECTOR_SERVER_IO_ERROR, |
| e, url.toString()); |
| } else { |
| throw new AgentConfigurationError(CONNECTOR_SERVER_IO_ERROR, |
| e, connServer.getAddress().toString()); |
| } |
| } |
| |
| if (useRegistrySsl) { |
| registry = |
| new SingleEntryRegistry(port, csf, ssf, |
| "jmxrmi", exporter.firstExported); |
| } else if (useSocketFactory) { |
| registry = |
| new SingleEntryRegistry(port, csf, ssf, |
| "jmxrmi", exporter.firstExported); |
| } else { |
| registry = |
| new SingleEntryRegistry(port, |
| "jmxrmi", exporter.firstExported); |
| } |
| |
| |
| int registryPort = |
| ((UnicastRef) ((RemoteObject) registry).getRef()).getLiveRef().getPort(); |
| String jmxUrlStr = String.format("service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", |
| url.getHost(), registryPort); |
| JMXServiceURL remoteURL = new JMXServiceURL(jmxUrlStr); |
| |
| /* Our exporter remembers the first object it was asked to |
| export, which will be an RMIServerImpl appropriate for |
| publication in our special registry. We could |
| alternatively have constructed the RMIServerImpl explicitly |
| and then constructed an RMIConnectorServer passing it as a |
| parameter, but that's quite a bit more verbose and pulls in |
| lots of knowledge of the RMI connector. */ |
| |
| return new JMXConnectorServerData(connServer, remoteURL); |
| } |
| |
| /** |
| * This class cannot be instantiated. |
| **/ |
| private ConnectorBootstrap() { |
| } |
| |
| private static final ClassLogger log = |
| new ClassLogger(ConnectorBootstrap.class.getPackage().getName(), |
| "ConnectorBootstrap"); |
| |
| private static class HostAwareSocketFactory implements RMIServerSocketFactory { |
| |
| private final String bindAddress; |
| |
| private HostAwareSocketFactory(String bindAddress) { |
| this.bindAddress = bindAddress; |
| } |
| |
| @Override |
| public ServerSocket createServerSocket(int port) throws IOException { |
| if (bindAddress == null) { |
| return new ServerSocket(port); |
| } else { |
| try { |
| InetAddress addr = InetAddress.getByName(bindAddress); |
| return new ServerSocket(port, 0, addr); |
| } catch (UnknownHostException e) { |
| return new ServerSocket(port); |
| } |
| } |
| } |
| } |
| |
| private static class HostAwareSslSocketFactory extends SslRMIServerSocketFactory { |
| |
| private final String bindAddress; |
| private final String[] enabledCipherSuites; |
| private final String[] enabledProtocols; |
| private final boolean needClientAuth; |
| private final SSLContext context; |
| |
| private HostAwareSslSocketFactory(String[] enabledCipherSuites, |
| String[] enabledProtocols, |
| boolean sslNeedClientAuth, |
| String bindAddress) throws IllegalArgumentException { |
| this(null, enabledCipherSuites, enabledProtocols, sslNeedClientAuth, bindAddress); |
| } |
| |
| private HostAwareSslSocketFactory(SSLContext ctx, |
| String[] enabledCipherSuites, |
| String[] enabledProtocols, |
| boolean sslNeedClientAuth, |
| String bindAddress) throws IllegalArgumentException { |
| this.context = ctx; |
| this.bindAddress = bindAddress; |
| this.enabledProtocols = enabledProtocols; |
| this.enabledCipherSuites = enabledCipherSuites; |
| this.needClientAuth = sslNeedClientAuth; |
| checkValues(ctx, enabledCipherSuites, enabledProtocols); |
| } |
| |
| @Override |
| public ServerSocket createServerSocket(int port) throws IOException { |
| if (bindAddress != null) { |
| try { |
| InetAddress addr = InetAddress.getByName(bindAddress); |
| return new SslServerSocket(port, 0, addr, context, |
| enabledCipherSuites, enabledProtocols, needClientAuth); |
| } catch (UnknownHostException e) { |
| return new SslServerSocket(port, context, |
| enabledCipherSuites, enabledProtocols, needClientAuth); |
| } |
| } else { |
| return new SslServerSocket(port, context, |
| enabledCipherSuites, enabledProtocols, needClientAuth); |
| } |
| } |
| |
| private static void checkValues(SSLContext context, |
| String[] enabledCipherSuites, |
| String[] enabledProtocols) throws IllegalArgumentException { |
| // Force the initialization of the default at construction time, |
| // rather than delaying it to the first time createServerSocket() |
| // is called. |
| // |
| final SSLSocketFactory sslSocketFactory = |
| context == null ? |
| (SSLSocketFactory)SSLSocketFactory.getDefault() : context.getSocketFactory(); |
| SSLSocket sslSocket = null; |
| if (enabledCipherSuites != null || enabledProtocols != null) { |
| try { |
| sslSocket = (SSLSocket) sslSocketFactory.createSocket(); |
| } catch (Exception e) { |
| final String msg = "Unable to check if the cipher suites " + |
| "and protocols to enable are supported"; |
| throw (IllegalArgumentException) |
| new IllegalArgumentException(msg).initCause(e); |
| } |
| } |
| |
| // Check if all the cipher suites and protocol versions to enable |
| // are supported by the underlying SSL/TLS implementation and if |
| // true create lists from arrays. |
| // |
| if (enabledCipherSuites != null) { |
| sslSocket.setEnabledCipherSuites(enabledCipherSuites); |
| } |
| if (enabledProtocols != null) { |
| sslSocket.setEnabledProtocols(enabledProtocols); |
| } |
| } |
| } |
| |
| private static class SslServerSocket extends ServerSocket { |
| |
| private static SSLSocketFactory defaultSSLSocketFactory; |
| private final String[] enabledCipherSuites; |
| private final String[] enabledProtocols; |
| private final boolean needClientAuth; |
| private final SSLContext context; |
| |
| private SslServerSocket(int port, |
| SSLContext ctx, |
| String[] enabledCipherSuites, |
| String[] enabledProtocols, |
| boolean needClientAuth) throws IOException { |
| super(port); |
| this.enabledProtocols = enabledProtocols; |
| this.enabledCipherSuites = enabledCipherSuites; |
| this.needClientAuth = needClientAuth; |
| this.context = ctx; |
| } |
| |
| private SslServerSocket(int port, |
| int backlog, |
| InetAddress bindAddr, |
| SSLContext ctx, |
| String[] enabledCipherSuites, |
| String[] enabledProtocols, |
| boolean needClientAuth) throws IOException { |
| super(port, backlog, bindAddr); |
| this.enabledProtocols = enabledProtocols; |
| this.enabledCipherSuites = enabledCipherSuites; |
| this.needClientAuth = needClientAuth; |
| this.context = ctx; |
| } |
| |
| @Override |
| public Socket accept() throws IOException { |
| final SSLSocketFactory sslSocketFactory = |
| context == null ? |
| getDefaultSSLSocketFactory() : context.getSocketFactory(); |
| Socket socket = super.accept(); |
| SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket( |
| socket, socket.getInetAddress().getHostName(), |
| socket.getPort(), true); |
| sslSocket.setUseClientMode(false); |
| if (enabledCipherSuites != null) { |
| sslSocket.setEnabledCipherSuites(enabledCipherSuites); |
| } |
| if (enabledProtocols != null) { |
| sslSocket.setEnabledProtocols(enabledProtocols); |
| } |
| sslSocket.setNeedClientAuth(needClientAuth); |
| return sslSocket; |
| } |
| |
| private static synchronized SSLSocketFactory getDefaultSSLSocketFactory() { |
| if (defaultSSLSocketFactory == null) { |
| defaultSSLSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); |
| return defaultSSLSocketFactory; |
| } else { |
| return defaultSSLSocketFactory; |
| } |
| } |
| |
| } |
| } |