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