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