|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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.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 com.sun.jmx.remote.util.EnvHelp; |
|
|
|
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; |
|
|
|
|
|
|
|
|
|
**/ |
|
public final class ConnectorBootstrap { |
|
|
|
|
|
|
|
**/ |
|
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"; |
|
} |
|
|
|
|
|
|
|
**/ |
|
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"; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static class JMXConnectorServerData { |
|
|
|
public JMXConnectorServerData( |
|
JMXConnectorServer jmxConnectorServer, |
|
JMXServiceURL jmxRemoteURL) { |
|
this.jmxConnectorServer = jmxConnectorServer; |
|
this.jmxRemoteURL = jmxRemoteURL; |
|
} |
|
JMXConnectorServer jmxConnectorServer; |
|
JMXServiceURL jmxRemoteURL; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
|
|
|
|
public boolean unexportObject(Remote obj, boolean force) |
|
throws NoSuchObjectException { |
|
return UnicastRemoteObject.unexportObject(obj, force); |
|
} |
|
Remote firstExported; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
private static Registry registry = null; |
|
|
|
public static void unexportRegistry() { |
|
|
|
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. |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**/ |
|
public static synchronized JMXConnectorServer initialize() { |
|
|
|
|
|
final Properties props = Agent.loadManagementProperties(); |
|
if (props == null) { |
|
return null; |
|
} |
|
|
|
final String portStr = props.getProperty(PropertyNames.PORT); |
|
return startRemoteConnectorServer(portStr, props); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static synchronized JMXConnectorServer initialize(String portStr, Properties props) { |
|
return startRemoteConnectorServer(portStr, props); |
|
} |
|
|
|
|
|
|
|
|
|
**/ |
|
public static synchronized JMXConnectorServer startRemoteConnectorServer(String portStr, Properties props) { |
|
|
|
|
|
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 |
|
|
|
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); |
|
} |
|
|
|
|
|
final String useAuthenticationStr = |
|
props.getProperty(PropertyNames.USE_AUTHENTICATION, |
|
DefaultValues.USE_AUTHENTICATION); |
|
final boolean useAuthentication = |
|
Boolean.valueOf(useAuthenticationStr).booleanValue(); |
|
|
|
|
|
final String useSslStr = |
|
props.getProperty(PropertyNames.USE_SSL, |
|
DefaultValues.USE_SSL); |
|
final boolean useSsl = |
|
Boolean.valueOf(useSslStr).booleanValue(); |
|
|
|
|
|
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(); |
|
|
|
|
|
final String sslConfigFileName = |
|
props.getProperty(PropertyNames.SSL_CONFIG_FILE_NAME); |
|
|
|
String loginConfigName = null; |
|
String passwordFileName = null; |
|
String accessFileName = null; |
|
|
|
|
|
if (useAuthentication) { |
|
|
|
|
|
loginConfigName = |
|
props.getProperty(PropertyNames.LOGIN_CONFIG_NAME); |
|
|
|
if (loginConfigName == null) { |
|
|
|
passwordFileName = |
|
props.getProperty(PropertyNames.PASSWORD_FILE_NAME, |
|
getDefaultFileName(DefaultValues.PASSWORD_FILE_NAME)); |
|
checkPasswordFile(passwordFileName); |
|
} |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
log.debug("startRemoteConnectorServer", e); |
|
} |
|
return cs; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static JMXConnectorServer startLocalConnectorServer() { |
|
// Ensure cryptographically strong random number generater used |
|
|
|
System.setProperty("java.rmi.server.randomIDs", "true"); |
|
|
|
|
|
Map<String, Object> env = new HashMap<>(); |
|
env.put(RMIExporter.EXPORTER_ATTRIBUTE, new PermanentExporter()); |
|
env.put(EnvHelp.CREDENTIAL_TYPES, new String[]{ |
|
String[].class.getName(), String.class.getName() |
|
}); |
|
|
|
// The local connector server need only be available via the |
|
|
|
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 |
|
|
|
if (lh == null || !lh.isLoopbackAddress()) { |
|
localhost = "127.0.0.1"; |
|
} |
|
|
|
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); |
|
try { |
|
JMXServiceURL url = new JMXServiceURL("rmi", localhost, 0); |
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
**/ |
|
private static String getDefaultFileName(String basename) { |
|
final String fileSeparator = File.separator; |
|
return System.getProperty("java.home") + fileSeparator + "lib" + |
|
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 { |
|
|
|
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 { |
|
|
|
|
|
|
|
* 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(EnvHelp.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); |
|
} |
|
|
|
|
|
|
|
**/ |
|
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; |
|
} |
|
} |
|
|
|
} |
|
} |