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