001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2007-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.admin.ads; 018 019import static org.opends.admin.ads.util.ConnectionUtils.*; 020import static org.opends.quicksetup.util.Utils.*; 021 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.LinkedHashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030import javax.naming.NameAlreadyBoundException; 031import javax.naming.NameNotFoundException; 032import javax.naming.NamingEnumeration; 033import javax.naming.NamingException; 034import javax.naming.directory.Attribute; 035import javax.naming.directory.Attributes; 036import javax.naming.directory.BasicAttribute; 037import javax.naming.directory.BasicAttributes; 038import javax.naming.directory.SearchControls; 039import javax.naming.directory.SearchResult; 040import javax.naming.ldap.InitialLdapContext; 041import javax.naming.ldap.LdapName; 042import javax.naming.ldap.Rdn; 043 044import org.forgerock.i18n.LocalizableMessage; 045import org.forgerock.i18n.slf4j.LocalizedLogger; 046import org.opends.admin.ads.util.ConnectionUtils; 047import org.opends.quicksetup.Constants; 048import org.opends.server.config.ConfigConstants; 049import org.opends.server.types.HostPort; 050 051/** The object of this class represent an OpenDS server. */ 052public class ServerDescriptor 053{ 054 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 055 private static final String TRUSTSTORE_DN = "cn=ads-truststore"; 056 057 private final Map<ADSContext.ServerProperty, Object> adsProperties = new HashMap<>(); 058 private final Set<ReplicaDescriptor> replicas = new HashSet<>(); 059 private final Map<ServerProperty, Object> serverProperties = new HashMap<>(); 060 private TopologyCacheException lastException; 061 062 /** 063 * Enumeration containing the different server properties that we can keep in 064 * the ServerProperty object. 065 */ 066 public enum ServerProperty 067 { 068 /** The associated value is a String. */ 069 HOST_NAME, 070 /** The associated value is an ArrayList of Integer. */ 071 LDAP_PORT, 072 /** The associated value is an ArrayList of Integer. */ 073 LDAPS_PORT, 074 /** The associated value is an Integer. */ 075 ADMIN_PORT, 076 /** The associated value is an ArrayList of Boolean. */ 077 LDAP_ENABLED, 078 /** The associated value is an ArrayList of Boolean. */ 079 LDAPS_ENABLED, 080 /** The associated value is an ArrayList of Boolean. */ 081 ADMIN_ENABLED, 082 /** The associated value is an ArrayList of Boolean. */ 083 STARTTLS_ENABLED, 084 /** The associated value is an ArrayList of Integer. */ 085 JMX_PORT, 086 /** The associated value is an ArrayList of Integer. */ 087 JMXS_PORT, 088 /** The associated value is an ArrayList of Boolean. */ 089 JMX_ENABLED, 090 /** The associated value is an ArrayList of Boolean. */ 091 JMXS_ENABLED, 092 /** The associated value is an Integer. */ 093 REPLICATION_SERVER_PORT, 094 /** The associated value is a Boolean. */ 095 IS_REPLICATION_SERVER, 096 /** The associated value is a Boolean. */ 097 IS_REPLICATION_ENABLED, 098 /** The associated value is a Boolean. */ 099 IS_REPLICATION_SECURE, 100 /** List of servers specified in the Replication Server configuration. This is a Set of String. */ 101 EXTERNAL_REPLICATION_SERVERS, 102 /** The associated value is an Integer. */ 103 REPLICATION_SERVER_ID, 104 /** 105 * The instance key-pair public-key certificate. The associated value is a 106 * byte[] (ds-cfg-public-key-certificate;binary). 107 */ 108 INSTANCE_PUBLIC_KEY_CERTIFICATE, 109 /** The schema generation ID. */ 110 SCHEMA_GENERATION_ID 111 } 112 113 /** Default constructor. */ 114 protected ServerDescriptor() 115 { 116 } 117 118 /** 119 * Returns the replicas contained on the server. 120 * @return the replicas contained on the server. 121 */ 122 public Set<ReplicaDescriptor> getReplicas() 123 { 124 return new HashSet<>(replicas); 125 } 126 127 /** 128 * Sets the replicas contained on the server. 129 * @param replicas the replicas contained on the server. 130 */ 131 public void setReplicas(Set<ReplicaDescriptor> replicas) 132 { 133 this.replicas.clear(); 134 this.replicas.addAll(replicas); 135 } 136 137 /** 138 * Returns a Map containing the ADS properties of the server. 139 * @return a Map containing the ADS properties of the server. 140 */ 141 public Map<ADSContext.ServerProperty, Object> getAdsProperties() 142 { 143 return adsProperties; 144 } 145 146 /** 147 * Returns a Map containing the properties of the server. 148 * @return a Map containing the properties of the server. 149 */ 150 public Map<ServerProperty, Object> getServerProperties() 151 { 152 return serverProperties; 153 } 154 155 /** 156 * Tells whether this server is registered in the ADS or not. 157 * @return <CODE>true</CODE> if the server is registered in the ADS and 158 * <CODE>false</CODE> otherwise. 159 */ 160 public boolean isRegistered() 161 { 162 return !adsProperties.isEmpty(); 163 } 164 165 /** 166 * Tells whether this server is a replication server or not. 167 * @return <CODE>true</CODE> if the server is a replication server and 168 * <CODE>false</CODE> otherwise. 169 */ 170 public boolean isReplicationServer() 171 { 172 return Boolean.TRUE.equals( 173 serverProperties.get(ServerProperty.IS_REPLICATION_SERVER)); 174 } 175 176 /** 177 * Tells whether replication is enabled on this server or not. 178 * @return <CODE>true</CODE> if replication is enabled and 179 * <CODE>false</CODE> otherwise. 180 */ 181 public boolean isReplicationEnabled() 182 { 183 return Boolean.TRUE.equals( 184 serverProperties.get(ServerProperty.IS_REPLICATION_ENABLED)); 185 } 186 187 /** 188 * Returns the String representation of this replication server based 189 * on the information we have ("hostname":"replication port") and 190 * <CODE>null</CODE> if this is not a replication server. 191 * @return the String representation of this replication server based 192 * on the information we have ("hostname":"replication port") and 193 * <CODE>null</CODE> if this is not a replication server. 194 */ 195 public String getReplicationServerHostPort() 196 { 197 if (isReplicationServer()) 198 { 199 return getReplicationServer(getHostName(), getReplicationServerPort()); 200 } 201 return null; 202 } 203 204 /** 205 * Returns the replication server ID of this server and -1 if this is not a 206 * replications server. 207 * @return the replication server ID of this server and -1 if this is not a 208 * replications server. 209 */ 210 public int getReplicationServerId() 211 { 212 if (isReplicationServer()) 213 { 214 return (Integer) serverProperties.get(ServerProperty.REPLICATION_SERVER_ID); 215 } 216 return -1; 217 } 218 219 /** 220 * Returns the replication port of this server and -1 if this is not a 221 * replications server. 222 * @return the replication port of this server and -1 if this is not a 223 * replications server. 224 */ 225 public int getReplicationServerPort() 226 { 227 if (isReplicationServer()) 228 { 229 return (Integer) serverProperties.get( 230 ServerProperty.REPLICATION_SERVER_PORT); 231 } 232 return -1; 233 } 234 235 /** 236 * Returns whether the communication with the replication port on the server 237 * is encrypted or not. 238 * @return <CODE>true</CODE> if the communication with the replication port on 239 * the server is encrypted and <CODE>false</CODE> otherwise. 240 */ 241 public boolean isReplicationSecure() 242 { 243 return isReplicationServer() 244 && Boolean.TRUE.equals(serverProperties.get(ServerProperty.IS_REPLICATION_SECURE)); 245 } 246 247 /** 248 * Sets the ADS properties of the server. 249 * @param adsProperties a Map containing the ADS properties of the server. 250 */ 251 public void setAdsProperties( 252 Map<ADSContext.ServerProperty, Object> adsProperties) 253 { 254 this.adsProperties.clear(); 255 this.adsProperties.putAll(adsProperties); 256 } 257 258 /** 259 * Returns the host name of the server. 260 * @return the host name of the server. 261 */ 262 public String getHostName() 263 { 264 String host = (String)serverProperties.get(ServerProperty.HOST_NAME); 265 if (host != null) 266 { 267 return host; 268 } 269 return (String) adsProperties.get(ADSContext.ServerProperty.HOST_NAME); 270 } 271 272 /** 273 * Returns the URL to access this server using LDAP. Returns 274 * <CODE>null</CODE> if the server is not configured to listen on an LDAP 275 * port. 276 * @return the URL to access this server using LDAP. 277 */ 278 public String getLDAPURL() 279 { 280 return getLDAPUrl0(ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT, false); 281 } 282 283 /** 284 * Returns the URL to access this server using LDAPS. Returns 285 * <CODE>null</CODE> if the server is not configured to listen on an LDAPS 286 * port. 287 * @return the URL to access this server using LDAP. 288 */ 289 public String getLDAPsURL() 290 { 291 return getLDAPUrl0(ServerProperty.LDAPS_ENABLED, ServerProperty.LDAPS_PORT, true); 292 } 293 294 private String getLDAPUrl0(ServerProperty enabledProp, ServerProperty portProp, boolean useSSL) 295 { 296 int port = getPort(enabledProp, portProp); 297 if (port != -1) 298 { 299 String host = getHostName(); 300 return getLDAPUrl(host, port, useSSL); 301 } 302 return null; 303 } 304 305 private int getPort(ServerProperty enabledProp, ServerProperty portProp) 306 { 307 if (!serverProperties.isEmpty()) 308 { 309 return getPort(enabledProp, portProp, -1); 310 } 311 return -1; 312 } 313 314 /** 315 * Returns the URL to access this server using the administration connector. 316 * Returns <CODE>null</CODE> if the server cannot get the administration 317 * connector. 318 * @return the URL to access this server using the administration connector. 319 */ 320 public String getAdminConnectorURL() 321 { 322 return getLDAPUrl0(ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT, true); 323 } 324 325 /** 326 * Returns the list of enabled administration ports. 327 * @return the list of enabled administration ports. 328 */ 329 public List<Integer> getEnabledAdministrationPorts() 330 { 331 List<Integer> ports = new ArrayList<>(1); 332 ArrayList<?> s = (ArrayList<?>)serverProperties.get(ServerProperty.ADMIN_ENABLED); 333 ArrayList<?> p = (ArrayList<?>)serverProperties.get(ServerProperty.ADMIN_PORT); 334 if (s != null) 335 { 336 for (int i=0; i<s.size(); i++) 337 { 338 if (Boolean.TRUE.equals(s.get(i))) 339 { 340 ports.add((Integer)p.get(i)); 341 } 342 } 343 } 344 return ports; 345 } 346 347 /** 348 * Returns a String of type host-name:port-number for the server. If 349 * the provided securePreferred is set to true the port that will be used 350 * will be the administration connector port. 351 * @param securePreferred whether to try to use the secure port as part 352 * of the returning String or not. 353 * @return a String of type host-name:port-number for the server. 354 */ 355 public HostPort getHostPort(boolean securePreferred) 356 { 357 int port = -1; 358 359 if (!serverProperties.isEmpty()) 360 { 361 port = getPort(ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT, port); 362 if (securePreferred) 363 { 364 port = getPort(ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT, port); 365 } 366 } 367 else 368 { 369 ArrayList<ADSContext.ServerProperty> enabledAttrs = new ArrayList<>(); 370 371 if (securePreferred) 372 { 373 enabledAttrs.add(ADSContext.ServerProperty.ADMIN_ENABLED); 374 enabledAttrs.add(ADSContext.ServerProperty.LDAPS_ENABLED); 375 enabledAttrs.add(ADSContext.ServerProperty.LDAP_ENABLED); 376 } 377 else 378 { 379 enabledAttrs.add(ADSContext.ServerProperty.LDAP_ENABLED); 380 enabledAttrs.add(ADSContext.ServerProperty.ADMIN_ENABLED); 381 enabledAttrs.add(ADSContext.ServerProperty.LDAPS_ENABLED); 382 } 383 384 for (ADSContext.ServerProperty prop : enabledAttrs) 385 { 386 Object v = adsProperties.get(prop); 387 if (v != null && "true".equalsIgnoreCase(String.valueOf(v))) 388 { 389 ADSContext.ServerProperty portProp = getPortProperty(prop); 390 Object p = adsProperties.get(portProp); 391 if (p != null) 392 { 393 try 394 { 395 port = Integer.parseInt(String.valueOf(p)); 396 } 397 catch (Throwable t) 398 { 399 logger.warn(LocalizableMessage.raw("Error calculating host port: "+t+" in "+ 400 adsProperties, t)); 401 } 402 break; 403 } 404 else 405 { 406 logger.warn(LocalizableMessage.raw("Value for "+portProp+" is null in "+ 407 adsProperties)); 408 } 409 } 410 } 411 } 412 return new HostPort(getHostName(), port); 413 } 414 415 private ADSContext.ServerProperty getPortProperty(ADSContext.ServerProperty prop) 416 { 417 if (prop == ADSContext.ServerProperty.ADMIN_ENABLED) 418 { 419 return ADSContext.ServerProperty.ADMIN_PORT; 420 } 421 else if (prop == ADSContext.ServerProperty.LDAPS_ENABLED) 422 { 423 return ADSContext.ServerProperty.LDAPS_PORT; 424 } 425 else if (prop == ADSContext.ServerProperty.LDAP_ENABLED) 426 { 427 return ADSContext.ServerProperty.LDAP_PORT; 428 } 429 else 430 { 431 throw new IllegalStateException("Unexpected prop: "+prop); 432 } 433 } 434 435 private int getPort(ServerProperty enabledProp, ServerProperty portProp, int defaultValue) 436 { 437 List<?> s = (List<?>) serverProperties.get(enabledProp); 438 if (s != null) 439 { 440 List<?> p = (List<?>) serverProperties.get(portProp); 441 for (int i=0; i<s.size(); i++) 442 { 443 if (Boolean.TRUE.equals(s.get(i))) 444 { 445 return (Integer) p.get(i); 446 } 447 } 448 } 449 return defaultValue; 450 } 451 452 /** 453 * Returns an Id that is unique for this server. 454 * @return an Id that is unique for this server. 455 */ 456 public String getId() 457 { 458 StringBuilder buf = new StringBuilder(); 459 if (!serverProperties.isEmpty()) 460 { 461 buf.append(serverProperties.get(ServerProperty.HOST_NAME)); 462 ServerProperty [] props = 463 { 464 ServerProperty.LDAP_PORT, ServerProperty.LDAPS_PORT, 465 ServerProperty.ADMIN_PORT, 466 ServerProperty.LDAP_ENABLED, ServerProperty.LDAPS_ENABLED, 467 ServerProperty.ADMIN_ENABLED 468 }; 469 for (ServerProperty prop : props) { 470 ArrayList<?> s = (ArrayList<?>) serverProperties.get(prop); 471 for (Object o : s) { 472 buf.append(":").append(o); 473 } 474 } 475 } 476 else 477 { 478 ADSContext.ServerProperty[] props = 479 { 480 ADSContext.ServerProperty.HOST_NAME, 481 ADSContext.ServerProperty.LDAP_PORT, 482 ADSContext.ServerProperty.LDAPS_PORT, 483 ADSContext.ServerProperty.ADMIN_PORT, 484 ADSContext.ServerProperty.LDAP_ENABLED, 485 ADSContext.ServerProperty.LDAPS_ENABLED, 486 ADSContext.ServerProperty.ADMIN_ENABLED 487 }; 488 for (int i=0; i<props.length; i++) 489 { 490 if (i != 0) 491 { 492 buf.append(":"); 493 } 494 buf.append(adsProperties.get(props[i])); 495 } 496 } 497 return buf.toString(); 498 } 499 500 /** 501 * Returns the instance-key public-key certificate retrieved from the 502 * truststore backend of the instance referenced through this descriptor. 503 * 504 * @return The public-key certificate of the instance. 505 */ 506 public byte[] getInstancePublicKeyCertificate() 507 { 508 return (byte[]) serverProperties.get(ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE); 509 } 510 511 /** 512 * Returns the schema generation ID of the server. 513 * @return the schema generation ID of the server. 514 */ 515 public String getSchemaReplicationID() 516 { 517 return (String)serverProperties.get(ServerProperty.SCHEMA_GENERATION_ID); 518 } 519 520 /** 521 * Returns the last exception that was encountered reading the configuration 522 * of the server. Returns null if there was no problem loading the 523 * configuration of the server. 524 * @return the last exception that was encountered reading the configuration 525 * of the server. Returns null if there was no problem loading the 526 * configuration of the server. 527 */ 528 public TopologyCacheException getLastException() 529 { 530 return lastException; 531 } 532 533 /** 534 * Sets the last exception that occurred while reading the configuration of 535 * the server. 536 * @param lastException the last exception that occurred while reading the 537 * configuration of the server. 538 */ 539 public void setLastException(TopologyCacheException lastException) 540 { 541 this.lastException = lastException; 542 } 543 544 /** 545 * This methods updates the ADS properties (the ones that were read from 546 * the ADS) with the contents of the server properties (the ones that were 547 * read directly from the server). 548 */ 549 public void updateAdsPropertiesWithServerProperties() 550 { 551 adsProperties.put(ADSContext.ServerProperty.HOST_NAME, getHostName()); 552 ServerProperty[][] sProps = 553 { 554 {ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT}, 555 {ServerProperty.LDAPS_ENABLED, ServerProperty.LDAPS_PORT}, 556 {ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT}, 557 {ServerProperty.JMX_ENABLED, ServerProperty.JMX_PORT}, 558 {ServerProperty.JMXS_ENABLED, ServerProperty.JMXS_PORT} 559 }; 560 ADSContext.ServerProperty[][] adsProps = 561 { 562 {ADSContext.ServerProperty.LDAP_ENABLED, 563 ADSContext.ServerProperty.LDAP_PORT}, 564 {ADSContext.ServerProperty.LDAPS_ENABLED, 565 ADSContext.ServerProperty.LDAPS_PORT}, 566 {ADSContext.ServerProperty.ADMIN_ENABLED, 567 ADSContext.ServerProperty.ADMIN_PORT}, 568 {ADSContext.ServerProperty.JMX_ENABLED, 569 ADSContext.ServerProperty.JMX_PORT}, 570 {ADSContext.ServerProperty.JMXS_ENABLED, 571 ADSContext.ServerProperty.JMXS_PORT} 572 }; 573 574 for (int i=0; i<sProps.length; i++) 575 { 576 ArrayList<?> s = (ArrayList<?>)serverProperties.get(sProps[i][0]); 577 ArrayList<?> p = (ArrayList<?>)serverProperties.get(sProps[i][1]); 578 if (s != null) 579 { 580 int port = getPort(s, p); 581 if (port == -1) 582 { 583 adsProperties.put(adsProps[i][0], "false"); 584 if (!p.isEmpty()) 585 { 586 port = (Integer)p.iterator().next(); 587 } 588 } 589 else 590 { 591 adsProperties.put(adsProps[i][0], "true"); 592 } 593 adsProperties.put(adsProps[i][1], String.valueOf(port)); 594 } 595 } 596 597 ArrayList<?> array = (ArrayList<?>)serverProperties.get( 598 ServerProperty.STARTTLS_ENABLED); 599 boolean startTLSEnabled = false; 600 if (array != null && !array.isEmpty()) 601 { 602 startTLSEnabled = Boolean.TRUE.equals(array.get(array.size() -1)); 603 } 604 adsProperties.put(ADSContext.ServerProperty.STARTTLS_ENABLED, Boolean.toString(startTLSEnabled)); 605 adsProperties.put(ADSContext.ServerProperty.ID, getHostPort(true).toString()); 606 adsProperties.put(ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE, 607 getInstancePublicKeyCertificate()); 608 } 609 610 private int getPort(List<?> enabled, List<?> port) 611 { 612 for (int j = 0; j < enabled.size(); j++) 613 { 614 if (Boolean.TRUE.equals(enabled.get(j))) 615 { 616 return (Integer) port.get(j); 617 } 618 } 619 return -1; 620 } 621 622 /** 623 * Creates a ServerDescriptor object based on some ADS properties provided. 624 * @param adsProperties the ADS properties of the server. 625 * @return a ServerDescriptor object that corresponds to the provided ADS 626 * properties. 627 */ 628 public static ServerDescriptor createStandalone( 629 Map<ADSContext.ServerProperty, Object> adsProperties) 630 { 631 ServerDescriptor desc = new ServerDescriptor(); 632 desc.setAdsProperties(adsProperties); 633 return desc; 634 } 635 636 /** 637 * Creates a ServerDescriptor object based on the configuration that we read 638 * using the provided InitialLdapContext. 639 * @param ctx the InitialLdapContext that will be used to read the 640 * configuration of the server. 641 * @param filter the topology cache filter describing the information that 642 * must be retrieved. 643 * @return a ServerDescriptor object that corresponds to the read 644 * configuration. 645 * @throws NamingException if a problem occurred reading the server 646 * configuration. 647 */ 648 public static ServerDescriptor createStandalone(InitialLdapContext ctx, 649 TopologyCacheFilter filter) 650 throws NamingException 651 { 652 ServerDescriptor desc = new ServerDescriptor(); 653 654 updateLdapConfiguration(desc, ctx); 655 updateAdminConnectorConfiguration(desc, ctx); 656 updateJmxConfiguration(desc, ctx); 657 updateReplicas(desc, ctx, filter); 658 updateReplication(desc, ctx, filter); 659 updatePublicKeyCertificate(desc, ctx); 660 updateMiscellaneous(desc, ctx); 661 662 desc.serverProperties.put(ServerProperty.HOST_NAME, 663 ConnectionUtils.getHostName(ctx)); 664 665 return desc; 666 } 667 668 private static void updateLdapConfiguration(ServerDescriptor desc, InitialLdapContext ctx) 669 throws NamingException 670 { 671 SearchControls ctls = new SearchControls(); 672 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 673 ctls.setReturningAttributes( 674 new String[] { 675 "ds-cfg-enabled", 676 "ds-cfg-listen-address", 677 "ds-cfg-listen-port", 678 "ds-cfg-use-ssl", 679 "ds-cfg-allow-start-tls", 680 "objectclass" 681 }); 682 String filter = "(objectclass=ds-cfg-ldap-connection-handler)"; 683 684 LdapName jndiName = new LdapName("cn=config"); 685 NamingEnumeration<SearchResult> listeners = 686 ctx.search(jndiName, filter, ctls); 687 688 try 689 { 690 ArrayList<Integer> ldapPorts = new ArrayList<>(); 691 ArrayList<Integer> ldapsPorts = new ArrayList<>(); 692 ArrayList<Boolean> ldapEnabled = new ArrayList<>(); 693 ArrayList<Boolean> ldapsEnabled = new ArrayList<>(); 694 ArrayList<Boolean> startTLSEnabled = new ArrayList<>(); 695 696 desc.serverProperties.put(ServerProperty.LDAP_PORT, ldapPorts); 697 desc.serverProperties.put(ServerProperty.LDAPS_PORT, ldapsPorts); 698 desc.serverProperties.put(ServerProperty.LDAP_ENABLED, ldapEnabled); 699 desc.serverProperties.put(ServerProperty.LDAPS_ENABLED, ldapsEnabled); 700 desc.serverProperties.put(ServerProperty.STARTTLS_ENABLED, 701 startTLSEnabled); 702 703 while(listeners.hasMore()) 704 { 705 SearchResult sr = listeners.next(); 706 707 String port = getFirstValue(sr, "ds-cfg-listen-port"); 708 709 boolean isSecure = "true".equalsIgnoreCase( 710 getFirstValue(sr, "ds-cfg-use-ssl")); 711 712 boolean enabled = "true".equalsIgnoreCase( 713 getFirstValue(sr, "ds-cfg-enabled")); 714 final Integer portNumber = Integer.valueOf(port); 715 if (isSecure) 716 { 717 ldapsPorts.add(portNumber); 718 ldapsEnabled.add(enabled); 719 } 720 else 721 { 722 ldapPorts.add(portNumber); 723 ldapEnabled.add(enabled); 724 enabled = "true".equalsIgnoreCase( 725 getFirstValue(sr, "ds-cfg-allow-start-tls")); 726 startTLSEnabled.add(enabled); 727 } 728 } 729 } 730 finally 731 { 732 listeners.close(); 733 } 734 } 735 736 private static void updateAdminConnectorConfiguration(ServerDescriptor desc, InitialLdapContext ctx) 737 throws NamingException 738 { 739 SearchControls ctls = new SearchControls(); 740 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 741 ctls.setReturningAttributes( 742 new String[] { 743 "ds-cfg-listen-port", 744 "objectclass" 745 }); 746 String filter = "(objectclass=ds-cfg-administration-connector)"; 747 748 LdapName jndiName = new LdapName("cn=config"); 749 NamingEnumeration<SearchResult> listeners = 750 ctx.search(jndiName, filter, ctls); 751 752 try 753 { 754 Integer adminConnectorPort = null; 755 756 // we should have a single administration connector 757 while (listeners.hasMore()) { 758 SearchResult sr = listeners.next(); 759 String port = getFirstValue(sr, "ds-cfg-listen-port"); 760 adminConnectorPort = Integer.valueOf(port); 761 } 762 763 // Even if we have a single port, use an array to be consistent with 764 // other protocols. 765 ArrayList<Integer> adminPorts = new ArrayList<>(); 766 ArrayList<Boolean> adminEnabled = new ArrayList<>(); 767 if (adminConnectorPort != null) 768 { 769 adminPorts.add(adminConnectorPort); 770 adminEnabled.add(Boolean.TRUE); 771 } 772 desc.serverProperties.put(ServerProperty.ADMIN_PORT, adminPorts); 773 desc.serverProperties.put(ServerProperty.ADMIN_ENABLED, adminEnabled); 774 } 775 finally 776 { 777 listeners.close(); 778 } 779 } 780 781 private static void updateJmxConfiguration(ServerDescriptor desc, InitialLdapContext ctx) throws NamingException 782 { 783 SearchControls ctls = new SearchControls(); 784 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 785 ctls.setReturningAttributes( 786 new String[] { 787 "ds-cfg-enabled", 788 "ds-cfg-listen-address", 789 "ds-cfg-listen-port", 790 "ds-cfg-use-ssl", 791 "objectclass" 792 }); 793 String filter = "(objectclass=ds-cfg-jmx-connection-handler)"; 794 795 LdapName jndiName = new LdapName("cn=config"); 796 NamingEnumeration<SearchResult> listeners = 797 ctx.search(jndiName, filter, ctls); 798 799 ArrayList<Integer> jmxPorts = new ArrayList<>(); 800 ArrayList<Integer> jmxsPorts = new ArrayList<>(); 801 ArrayList<Boolean> jmxEnabled = new ArrayList<>(); 802 ArrayList<Boolean> jmxsEnabled = new ArrayList<>(); 803 804 desc.serverProperties.put(ServerProperty.JMX_PORT, jmxPorts); 805 desc.serverProperties.put(ServerProperty.JMXS_PORT, jmxsPorts); 806 desc.serverProperties.put(ServerProperty.JMX_ENABLED, jmxEnabled); 807 desc.serverProperties.put(ServerProperty.JMXS_ENABLED, jmxsEnabled); 808 809 try 810 { 811 while(listeners.hasMore()) 812 { 813 SearchResult sr = listeners.next(); 814 815 String port = getFirstValue(sr, "ds-cfg-listen-port"); 816 817 boolean isSecure = "true".equalsIgnoreCase( 818 getFirstValue(sr, "ds-cfg-use-ssl")); 819 820 boolean enabled = "true".equalsIgnoreCase( 821 getFirstValue(sr, "ds-cfg-enabled")); 822 Integer portNumber = Integer.valueOf(port); 823 if (isSecure) 824 { 825 jmxsPorts.add(portNumber); 826 jmxsEnabled.add(enabled); 827 } 828 else 829 { 830 jmxPorts.add(portNumber); 831 jmxEnabled.add(enabled); 832 } 833 } 834 } 835 finally 836 { 837 listeners.close(); 838 } 839 } 840 841 private static void updateReplicas(ServerDescriptor desc, 842 InitialLdapContext ctx, TopologyCacheFilter cacheFilter) 843 throws NamingException 844 { 845 if (!cacheFilter.searchBaseDNInformation()) 846 { 847 return; 848 } 849 SearchControls ctls = new SearchControls(); 850 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 851 ctls.setReturningAttributes( 852 new String[] { 853 "ds-cfg-base-dn", 854 "ds-cfg-backend-id", 855 ConfigConstants.ATTR_OBJECTCLASS 856 }); 857 String filter = "(objectclass=ds-cfg-backend)"; 858 859 LdapName jndiName = new LdapName("cn=config"); 860 NamingEnumeration<SearchResult> databases = 861 ctx.search(jndiName, filter, ctls); 862 863 try 864 { 865 while(databases.hasMore()) 866 { 867 SearchResult sr = databases.next(); 868 869 String id = getFirstValue(sr, "ds-cfg-backend-id"); 870 871 if (!isConfigBackend(id) || isSchemaBackend(id)) 872 { 873 Set<String> baseDns = getValues(sr, "ds-cfg-base-dn"); 874 875 Set<String> entries; 876 if (cacheFilter.searchMonitoringInformation()) 877 { 878 entries = getBaseDNEntryCount(ctx, id); 879 } 880 else 881 { 882 entries = new HashSet<>(); 883 } 884 885 Set<ReplicaDescriptor> replicas = desc.getReplicas(); 886 for (String baseDn : baseDns) 887 { 888 if (isAddReplica(cacheFilter, baseDn)) 889 { 890 SuffixDescriptor suffix = new SuffixDescriptor(); 891 suffix.setDN(baseDn); 892 ReplicaDescriptor replica = new ReplicaDescriptor(); 893 replica.setServer(desc); 894 replica.setObjectClasses(getValues(sr, ConfigConstants.ATTR_OBJECTCLASS)); 895 replica.setBackendName(id); 896 replicas.add(replica); 897 HashSet<ReplicaDescriptor> r = new HashSet<>(); 898 r.add(replica); 899 suffix.setReplicas(r); 900 replica.setSuffix(suffix); 901 int nEntries = -1; 902 for (String s : entries) 903 { 904 int index = s.indexOf(" "); 905 if (index != -1) 906 { 907 String dn = s.substring(index + 1); 908 if (areDnsEqual(baseDn, dn)) 909 { 910 try 911 { 912 nEntries = Integer.parseInt(s.substring(0, index)); 913 } 914 catch (Throwable t) 915 { 916 /* Ignore */ 917 } 918 break; 919 } 920 } 921 } 922 replica.setEntries(nEntries); 923 } 924 } 925 desc.setReplicas(replicas); 926 } 927 } 928 } 929 finally 930 { 931 databases.close(); 932 } 933 } 934 935 private static boolean isAddReplica(TopologyCacheFilter cacheFilter, String baseDn) 936 { 937 if (cacheFilter.searchAllBaseDNs()) 938 { 939 return true; 940 } 941 942 for (String dn : cacheFilter.getBaseDNsToSearch()) 943 { 944 if (areDnsEqual(dn, baseDn)) 945 { 946 return true; 947 } 948 } 949 return false; 950 } 951 952 private static void updateReplication(ServerDescriptor desc, 953 InitialLdapContext ctx, TopologyCacheFilter cacheFilter) 954 throws NamingException 955 { 956 boolean replicationEnabled = false; 957 SearchControls ctls = new SearchControls(); 958 ctls.setSearchScope(SearchControls.OBJECT_SCOPE); 959 ctls.setReturningAttributes( 960 new String[] { 961 "ds-cfg-enabled" 962 }); 963 String filter = "(objectclass=ds-cfg-synchronization-provider)"; 964 965 LdapName jndiName = new LdapName( 966 "cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config"); 967 NamingEnumeration<SearchResult> syncProviders = null; 968 969 try 970 { 971 syncProviders = ctx.search(jndiName, filter, ctls); 972 973 while(syncProviders.hasMore()) 974 { 975 SearchResult sr = syncProviders.next(); 976 977 if ("true".equalsIgnoreCase(getFirstValue(sr, 978 "ds-cfg-enabled"))) 979 { 980 replicationEnabled = true; 981 } 982 } 983 } 984 catch (NameNotFoundException nse) 985 { 986 /* ignore */ 987 } 988 finally 989 { 990 if (syncProviders != null) 991 { 992 syncProviders.close(); 993 } 994 } 995 desc.serverProperties.put(ServerProperty.IS_REPLICATION_ENABLED, 996 Boolean.valueOf(replicationEnabled)); 997 998 Set<String> allReplicationServers = new LinkedHashSet<>(); 999 1000 if (cacheFilter.searchBaseDNInformation()) 1001 { 1002 ctls = new SearchControls(); 1003 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 1004 ctls.setReturningAttributes( 1005 new String[] { 1006 "ds-cfg-base-dn", 1007 "ds-cfg-replication-server", 1008 "ds-cfg-server-id" 1009 }); 1010 filter = "(objectclass=ds-cfg-replication-domain)"; 1011 1012 jndiName = new LdapName( 1013 "cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config"); 1014 1015 syncProviders = null; 1016 try 1017 { 1018 syncProviders = ctx.search(jndiName, filter, ctls); 1019 1020 while(syncProviders.hasMore()) 1021 { 1022 SearchResult sr = syncProviders.next(); 1023 1024 int id = Integer.parseInt( 1025 getFirstValue(sr, "ds-cfg-server-id")); 1026 Set<String> replicationServers = getValues(sr, 1027 "ds-cfg-replication-server"); 1028 Set<String> dns = getValues(sr, "ds-cfg-base-dn"); 1029 for (String dn : dns) 1030 { 1031 for (ReplicaDescriptor replica : desc.getReplicas()) 1032 { 1033 if (areDnsEqual(replica.getSuffix().getDN(), dn)) 1034 { 1035 replica.setReplicationId(id); 1036 // Keep the values of the replication servers in lower case 1037 // to make use of Sets as String simpler. 1038 LinkedHashSet<String> repServers = new LinkedHashSet<>(); 1039 for (String s: replicationServers) 1040 { 1041 repServers.add(s.toLowerCase()); 1042 } 1043 replica.setReplicationServers(repServers); 1044 allReplicationServers.addAll(repServers); 1045 } 1046 } 1047 } 1048 } 1049 } 1050 catch (NameNotFoundException nse) 1051 { 1052 /* ignore */ 1053 } 1054 finally 1055 { 1056 if (syncProviders != null) 1057 { 1058 syncProviders.close(); 1059 } 1060 } 1061 } 1062 1063 ctls = new SearchControls(); 1064 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 1065 ctls.setReturningAttributes( 1066 new String[] { 1067 "ds-cfg-replication-port", "ds-cfg-replication-server", 1068 "ds-cfg-replication-server-id" 1069 }); 1070 filter = "(objectclass=ds-cfg-replication-server)"; 1071 1072 jndiName = new LdapName("cn=Multimaster "+ 1073 "Synchronization,cn=Synchronization Providers,cn=config"); 1074 1075 desc.serverProperties.put(ServerProperty.IS_REPLICATION_SERVER, 1076 Boolean.FALSE); 1077 NamingEnumeration<SearchResult> entries = null; 1078 try 1079 { 1080 entries = ctx.search(jndiName, filter, ctls); 1081 1082 while (entries.hasMore()) 1083 { 1084 SearchResult sr = entries.next(); 1085 1086 desc.serverProperties.put(ServerProperty.IS_REPLICATION_SERVER, 1087 Boolean.TRUE); 1088 String v = getFirstValue(sr, "ds-cfg-replication-port"); 1089 desc.serverProperties.put(ServerProperty.REPLICATION_SERVER_PORT, 1090 Integer.parseInt(v)); 1091 v = getFirstValue(sr, "ds-cfg-replication-server-id"); 1092 desc.serverProperties.put(ServerProperty.REPLICATION_SERVER_ID, 1093 Integer.parseInt(v)); 1094 Set<String> values = getValues(sr, "ds-cfg-replication-server"); 1095 // Keep the values of the replication servers in lower case 1096 // to make use of Sets as String simpler. 1097 LinkedHashSet<String> repServers = new LinkedHashSet<>(); 1098 for (String s: values) 1099 { 1100 repServers.add(s.toLowerCase()); 1101 } 1102 allReplicationServers.addAll(repServers); 1103 desc.serverProperties.put(ServerProperty.EXTERNAL_REPLICATION_SERVERS, 1104 allReplicationServers); 1105 } 1106 } 1107 catch (NameNotFoundException nse) 1108 { 1109 /* ignore */ 1110 } 1111 finally 1112 { 1113 if (entries != null) 1114 { 1115 entries.close(); 1116 } 1117 } 1118 1119 boolean replicationSecure = false; 1120 if (replicationEnabled) 1121 { 1122 ctls = new SearchControls(); 1123 ctls.setSearchScope(SearchControls.OBJECT_SCOPE); 1124 ctls.setReturningAttributes( 1125 new String[] {"ds-cfg-ssl-encryption"}); 1126 filter = "(objectclass=ds-cfg-crypto-manager)"; 1127 1128 jndiName = new LdapName("cn=Crypto Manager,cn=config"); 1129 1130 entries = ctx.search(jndiName, filter, ctls); 1131 1132 try 1133 { 1134 while (entries.hasMore()) 1135 { 1136 SearchResult sr = entries.next(); 1137 1138 String v = getFirstValue(sr, "ds-cfg-ssl-encryption"); 1139 replicationSecure = "true".equalsIgnoreCase(v); 1140 } 1141 } 1142 finally 1143 { 1144 entries.close(); 1145 } 1146 } 1147 desc.serverProperties.put(ServerProperty.IS_REPLICATION_SECURE, 1148 Boolean.valueOf(replicationSecure)); 1149 } 1150 1151 /** 1152 Updates the instance key public-key certificate value of this context from 1153 the local truststore of the instance bound by this context. Any current 1154 value of the certificate is overwritten. The intent of this method is to 1155 retrieve the instance-key public-key certificate when this context is bound 1156 to an instance, and cache it for later use in registering the instance into 1157 ADS. 1158 @param desc The map to update with the instance key-pair public-key 1159 certificate. 1160 @param ctx The bound server instance. 1161 @throws NamingException if unable to retrieve certificate from bound 1162 instance. 1163 */ 1164 private static void updatePublicKeyCertificate(ServerDescriptor desc, InitialLdapContext ctx) throws NamingException 1165 { 1166 /* TODO: this DN is declared in some core constants file. Create a constants 1167 file for the installer and import it into the core. */ 1168 final String dnStr = "ds-cfg-key-id=ads-certificate,cn=ads-truststore"; 1169 final LdapName dn = new LdapName(dnStr); 1170 for (int i = 0; i < 2 ; ++i) { 1171 /* If the entry does not exist in the instance's truststore backend, add 1172 it (which induces the CryptoManager to create the public-key 1173 certificate attribute), then repeat the search. */ 1174 try { 1175 final SearchControls searchControls = new SearchControls(); 1176 searchControls.setSearchScope(SearchControls.OBJECT_SCOPE); 1177 final String attrIDs[] = { "ds-cfg-public-key-certificate;binary" }; 1178 searchControls.setReturningAttributes(attrIDs); 1179 final SearchResult certEntry = ctx.search(dn, 1180 "(objectclass=ds-cfg-instance-key)", searchControls).next(); 1181 final Attribute certAttr = certEntry.getAttributes().get(attrIDs[0]); 1182 if (null != certAttr) { 1183 /* attribute ds-cfg-public-key-certificate is a MUST in the schema */ 1184 desc.serverProperties.put( 1185 ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE, 1186 certAttr.get()); 1187 } 1188 break; 1189 } 1190 catch (NameNotFoundException x) { 1191 if (0 == i) { 1192 // Poke CryptoManager to initialize truststore. Note the special attribute in the request. 1193 final Attributes attrs = new BasicAttributes(); 1194 final Attribute oc = new BasicAttribute("objectclass"); 1195 oc.add("top"); 1196 oc.add("ds-cfg-self-signed-cert-request"); 1197 attrs.put(oc); 1198 ctx.createSubcontext(dn, attrs).close(); 1199 } 1200 else { 1201 throw x; 1202 } 1203 } 1204 } 1205 } 1206 1207 private static void updateMiscellaneous(ServerDescriptor desc, InitialLdapContext ctx) throws NamingException 1208 { 1209 SearchControls ctls = new SearchControls(); 1210 ctls.setSearchScope(SearchControls.OBJECT_SCOPE); 1211 ctls.setReturningAttributes( 1212 new String[] { 1213 "ds-sync-generation-id" 1214 }); 1215 String filter = "(|(objectclass=*)(objectclass=ldapsubentry))"; 1216 1217 LdapName jndiName = new LdapName("cn=schema"); 1218 NamingEnumeration<SearchResult> listeners = 1219 ctx.search(jndiName, filter, ctls); 1220 1221 try 1222 { 1223 while(listeners.hasMore()) 1224 { 1225 SearchResult sr = listeners.next(); 1226 1227 desc.serverProperties.put(ServerProperty.SCHEMA_GENERATION_ID, 1228 getFirstValue(sr, "ds-sync-generation-id")); 1229 } 1230 } 1231 finally 1232 { 1233 listeners.close(); 1234 } 1235 } 1236 1237 /** 1238 Seeds the bound instance's local ads-truststore with a set of instance 1239 key-pair public key certificates. The result is the instance will trust any 1240 instance possessing the private key corresponding to one of the public-key 1241 certificates. This trust is necessary at least to initialize replication, 1242 which uses the trusted certificate entries in the ads-truststore for server 1243 authentication. 1244 @param ctx The bound instance. 1245 @param keyEntryMap The set of valid (i.e., not tagged as compromised) 1246 instance key-pair public-key certificate entries in ADS represented as a map 1247 from keyID to public-key certificate (binary). 1248 @throws NamingException in case an error occurs while updating the instance's 1249 ads-truststore via LDAP. 1250 */ 1251 public static void seedAdsTrustStore( 1252 InitialLdapContext ctx, 1253 Map<String, byte[]> keyEntryMap) 1254 throws NamingException 1255 { 1256 /* TODO: this DN is declared in some core constants file. Create a 1257 constants file for the installer and import it into the core. */ 1258 final Attribute oc = new BasicAttribute("objectclass"); 1259 oc.add("top"); 1260 oc.add("ds-cfg-instance-key"); 1261 for (Map.Entry<String, byte[]> keyEntry : keyEntryMap.entrySet()){ 1262 final BasicAttributes keyAttrs = new BasicAttributes(); 1263 keyAttrs.put(oc); 1264 final Attribute rdnAttr = new BasicAttribute( 1265 ADSContext.ServerProperty.INSTANCE_KEY_ID.getAttributeName(), 1266 keyEntry.getKey()); 1267 keyAttrs.put(rdnAttr); 1268 keyAttrs.put(new BasicAttribute( 1269 ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE. 1270 getAttributeName() + ";binary", keyEntry.getValue())); 1271 final LdapName keyDn = new LdapName(rdnAttr.getID() + "=" + Rdn.escapeValue(rdnAttr.get()) + "," + TRUSTSTORE_DN); 1272 try { 1273 ctx.createSubcontext(keyDn, keyAttrs).close(); 1274 } 1275 catch(NameAlreadyBoundException x){ 1276 ctx.destroySubcontext(keyDn); 1277 ctx.createSubcontext(keyDn, keyAttrs).close(); 1278 } 1279 } 1280 } 1281 1282 /** 1283 * Returns the values of the ds-base-dn-entry count attributes for the given 1284 * backend monitor entry using the provided InitialLdapContext. 1285 * @param ctx the InitialLdapContext to use to update the configuration. 1286 * @param backendID the id of the backend. 1287 * @return the values of the ds-base-dn-entry count attribute. 1288 * @throws NamingException if there was an error. 1289 */ 1290 private static Set<String> getBaseDNEntryCount(InitialLdapContext ctx, 1291 String backendID) throws NamingException 1292 { 1293 LinkedHashSet<String> v = new LinkedHashSet<>(); 1294 SearchControls ctls = new SearchControls(); 1295 ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE); 1296 ctls.setReturningAttributes( 1297 new String[] { 1298 "ds-base-dn-entry-count" 1299 }); 1300 String filter = "(ds-backend-id="+backendID+")"; 1301 1302 LdapName jndiName = new LdapName("cn=monitor"); 1303 NamingEnumeration<SearchResult> listeners = 1304 ctx.search(jndiName, filter, ctls); 1305 1306 try 1307 { 1308 while(listeners.hasMore()) 1309 { 1310 SearchResult sr = listeners.next(); 1311 1312 v.addAll(getValues(sr, "ds-base-dn-entry-count")); 1313 } 1314 } 1315 finally 1316 { 1317 listeners.close(); 1318 } 1319 return v; 1320 } 1321 1322 /** 1323 * An convenience method to know if the provided ID corresponds to a 1324 * configuration backend or not. 1325 * @param id the backend ID to analyze 1326 * @return <CODE>true</CODE> if the the id corresponds to a configuration 1327 * backend and <CODE>false</CODE> otherwise. 1328 */ 1329 private static boolean isConfigBackend(String id) 1330 { 1331 return "tasks".equalsIgnoreCase(id) || 1332 "schema".equalsIgnoreCase(id) || 1333 "config".equalsIgnoreCase(id) || 1334 "monitor".equalsIgnoreCase(id) || 1335 "backup".equalsIgnoreCase(id) || 1336 "ads-truststore".equalsIgnoreCase(id); 1337 } 1338 1339 /** 1340 * An convenience method to know if the provided ID corresponds to the schema 1341 * backend or not. 1342 * @param id the backend ID to analyze 1343 * @return <CODE>true</CODE> if the the id corresponds to the schema backend 1344 * and <CODE>false</CODE> otherwise. 1345 */ 1346 private static boolean isSchemaBackend(String id) 1347 { 1348 return "schema".equalsIgnoreCase(id); 1349 } 1350 1351 /** 1352 * Returns the replication server normalized String for a given host name 1353 * and replication port. 1354 * @param hostName the host name. 1355 * @param replicationPort the replication port. 1356 * @return the replication server normalized String for a given host name 1357 * and replication port. 1358 */ 1359 public static String getReplicationServer(String hostName, int replicationPort) 1360 { 1361 return HostPort.toString(hostName, replicationPort); 1362 } 1363 1364 /** 1365 * Returns a representation of a base DN for a set of servers. 1366 * @param baseDN the base DN. 1367 * @param servers the servers. 1368 * @return a representation of a base DN for a set of servers. 1369 */ 1370 public static String getSuffixDisplay(String baseDN, 1371 Set<ServerDescriptor> servers) 1372 { 1373 StringBuilder sb = new StringBuilder(); 1374 sb.append(baseDN); 1375 for (ServerDescriptor server : servers) 1376 { 1377 sb.append(Constants.LINE_SEPARATOR).append(" "); 1378 sb.append(server.getHostPort(true)); 1379 } 1380 return sb.toString(); 1381 } 1382 1383 /** 1384 * Tells whether the provided server descriptor represents the same server 1385 * as this object. 1386 * @param server the server to make the comparison. 1387 * @return whether the provided server descriptor represents the same server 1388 * as this object or not. 1389 */ 1390 public boolean isSameServer(ServerDescriptor server) 1391 { 1392 return getId().equals(server.getId()); 1393 } 1394}