001/* 002 * Copyright 2020-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2020-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2020-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds; 037 038 039 040import java.io.File; 041import java.io.IOException; 042import java.io.Serializable; 043import java.net.InetAddress; 044import java.util.ArrayList; 045import java.util.Collections; 046import java.util.Comparator; 047import java.util.Iterator; 048import java.util.LinkedHashSet; 049import java.util.List; 050import java.util.Set; 051import java.util.SortedSet; 052import java.util.TreeSet; 053 054import com.unboundid.ldap.sdk.Entry; 055import com.unboundid.ldap.sdk.LDAPConnectionOptions; 056import com.unboundid.ldap.sdk.LDAPException; 057import com.unboundid.ldap.sdk.ResultCode; 058import com.unboundid.ldif.LDIFException; 059import com.unboundid.ldif.LDIFReader; 060import com.unboundid.util.Debug; 061import com.unboundid.util.NotMutable; 062import com.unboundid.util.NotNull; 063import com.unboundid.util.Nullable; 064import com.unboundid.util.StaticUtils; 065import com.unboundid.util.ThreadSafety; 066import com.unboundid.util.ThreadSafetyLevel; 067 068import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*; 069 070 071 072/** 073 * This class provides a data structure that holds information about an LDAP 074 * connection handler defined in the configuration of a Ping Identity Directory 075 * Server instance. It also provides a utility method for reading a Directory 076 * Server configuration file to obtain information about the listener instances 077 * it contains, and it implements the {@code Comparable} interface for ranking 078 * connection handlers by relative preference for use. 079 * <BR> 080 * <BLOCKQUOTE> 081 * <B>NOTE:</B> This class, and other classes within the 082 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 083 * supported for use against Ping Identity, UnboundID, and 084 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 085 * for proprietary functionality or for external specifications that are not 086 * considered stable or mature enough to be guaranteed to work in an 087 * interoperable way with other types of LDAP servers. 088 * </BLOCKQUOTE> 089 */ 090@NotMutable() 091@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 092public final class LDAPConnectionHandlerConfiguration 093 implements Comparable<LDAPConnectionHandlerConfiguration>, 094 Comparator<LDAPConnectionHandlerConfiguration>, 095 Serializable 096{ 097 /** 098 * The name of the LDAP object class that will be used in LDAP connection 099 * handler configuration entries. 100 */ 101 @NotNull private static final String OC_LDAP_CONN_HANDLER = 102 "ds-cfg-ldap-connection-handler"; 103 104 105 106 /** 107 * The name for the LDAP attribute that indicates whether the connection 108 * handler allows StartTLS. 109 */ 110 @NotNull private static final String ATTR_ALLOW_START_TLS = 111 "ds-cfg-allow-start-tls"; 112 113 114 115 /** 116 * The name fo the LDAP attribute that indicates whether the connection 117 * handler is enabled. 118 */ 119 @NotNull private static final String ATTR_ENABLED = "ds-cfg-enabled"; 120 121 122 123 /** 124 * The name for the LDAP attribute that specifies the set of addresses on 125 * which the connection handler will listen. 126 */ 127 @NotNull private static final String ATTR_LISTEN_ADDRESS = 128 "ds-cfg-listen-address"; 129 130 131 132 /** 133 * The name for the LDAP attribute that specifies the port on which the 134 * connection handler will listen. 135 */ 136 @NotNull private static final String ATTR_LISTEN_PORT = "ds-cfg-listen-port"; 137 138 139 140 /** 141 * The name for the LDAP attribute that specifies the name of the connection 142 * handler. 143 */ 144 @NotNull private static final String ATTR_NAME = "cn"; 145 146 147 148 /** 149 * The name for the LDAP attribute that indicates whether the connection 150 * handler uses SSL. 151 */ 152 @NotNull private static final String ATTR_USE_SSL = "ds-cfg-use-ssl"; 153 154 155 156 /** 157 * The serial version UID for this serializable class. 158 */ 159 private static final long serialVersionUID = 6824077978334156627L; 160 161 162 163 // Indicates whether the connection handler is enabled for use. 164 private final boolean isEnabled; 165 166 // Indicates whether the connection handler supports StartTLS for encrypting 167 // communication. 168 private final boolean supportsStartTLS; 169 170 // Indicates whether the connection handler uses SSL to encrypt communication. 171 private final boolean usesSSL; 172 173 // The port on which the connection handler accepts client connections. 174 private final int port; 175 176 // The set of addresses on which the connection handler accepts client 177 // connections. 178 @NotNull private final List<String> listenAddresses; 179 180 // The name for the connection handler. 181 @NotNull private final String name; 182 183 184 185 /** 186 * Creates a new LDAP connection handler configuration object with the 187 * provided information. 188 * 189 * @param name The name for the connection handler. 190 * @param isEnabled Indicates whether the connection handler is 191 * enabled for use. 192 * @param listenAddresses The set of addresses on which the connection 193 * handler accepts client connections. It must not 194 * be {@code null} but may be empty. 195 * @param port The port on which the connection handler accepts 196 * client connections. 197 * @param usesSSL Indicates whether the connection handler uses 198 * SSL to encrypt communication. 199 * @param supportsStartTLS Indicates whether the connection handler supports 200 * StartTLS for encrypting communication. 201 */ 202 LDAPConnectionHandlerConfiguration(@NotNull final String name, 203 final boolean isEnabled, @NotNull final List<String> listenAddresses, 204 final int port, final boolean usesSSL, final boolean supportsStartTLS) 205 { 206 this.name = name; 207 this.isEnabled = isEnabled; 208 this.listenAddresses = listenAddresses; 209 this.port = port; 210 this.usesSSL = usesSSL; 211 this.supportsStartTLS = supportsStartTLS; 212 } 213 214 215 216 /** 217 * Retrieves the name for the connection handler. 218 * 219 * @return The name for the connection handler. 220 */ 221 @NotNull() 222 public String getName() 223 { 224 return name; 225 } 226 227 228 229 /** 230 * Indicates whether the connection handler is enabled for use. 231 * 232 * @return {@code true} if the connection handler is enabled, or 233 * {@code false} if not. 234 */ 235 public boolean isEnabled() 236 { 237 return isEnabled; 238 } 239 240 241 242 /** 243 * Retrieves the set of addresses on which the connection handler accepts 244 * client connections, if available. 245 * 246 * @return The set of addresses on which the connection handler accepts 247 * client connections, or an empty set if the connection handler 248 * listens on all addresses on all interfaces. 249 */ 250 @NotNull() 251 public List<String> getListenAddresses() 252 { 253 return listenAddresses; 254 } 255 256 257 258 /** 259 * Retrieves the port on which the connection handler accepts client 260 * connections. 261 * 262 * @return The port on which the connection handler accepts client 263 * connections. 264 */ 265 public int getPort() 266 { 267 return port; 268 } 269 270 271 272 /** 273 * Indicates whether the connection handler uses SSL to encrypt communication. 274 * 275 * @return {@code true} if the connection handler uses SSL to encrypt 276 * communication, or {@code false} if not. 277 */ 278 public boolean usesSSL() 279 { 280 return usesSSL; 281 } 282 283 284 285 /** 286 * Indicates whether the connection handler supports StartTLS for encrypting 287 * communication. 288 * 289 * @return {@code true} if the connection handler supports StartTLS for 290 * encrypting communication, or {@code false} if not. 291 */ 292 public boolean supportsStartTLS() 293 { 294 return supportsStartTLS; 295 } 296 297 298 299 /** 300 * Retrieves the LDAP connection handler configuration objects from the 301 * specified configuration file. The configuration objects will be ordered 302 * from most preferred to least preferred, using the logic described in the 303 * {@link #compareTo} method documentation. 304 * 305 * @param configFile The configuration file to examine. It must not be 306 * {@code null}, and it must exist. 307 * @param onlyEnabled Indicates whether to only include information about 308 * connection handlers that are enabled. 309 * 310 * @return A list of the LDAP connection handler configuration objects read 311 * from the specified configuration file, or an empty set if no LDAP 312 * connection handler configuration entries were found in the 313 * configuration. 314 * 315 * @throws LDAPException If a problem interferes with reading the 316 * connection handler configuration objects from the 317 * configuration file. 318 */ 319 @NotNull() 320 public static List<LDAPConnectionHandlerConfiguration> readConfiguration( 321 @NotNull final File configFile, final boolean onlyEnabled) 322 throws LDAPException 323 { 324 try (LDIFReader ldifReader = new LDIFReader(configFile)) 325 { 326 final List<LDAPConnectionHandlerConfiguration> configs = 327 new ArrayList<>(); 328 while (true) 329 { 330 final Entry entry; 331 try 332 { 333 entry = ldifReader.readEntry(); 334 } 335 catch (final LDIFException e) 336 { 337 Debug.debugException(e); 338 if (e.mayContinueReading()) 339 { 340 continue; 341 } 342 else 343 { 344 throw new LDAPException(ResultCode.DECODING_ERROR, 345 ERR_LDAP_HANDLER_CANNOT_READ_CONFIG.get( 346 configFile.getAbsolutePath(), 347 StaticUtils.getExceptionMessage(e))); 348 } 349 } 350 351 if (entry == null) 352 { 353 break; 354 } 355 356 if (! entry.hasObjectClass(OC_LDAP_CONN_HANDLER)) 357 { 358 continue; 359 } 360 361 final String name = entry.getAttributeValue(ATTR_NAME); 362 if (name == null) 363 { 364 continue; 365 } 366 367 final boolean isEnabled = 368 entry.hasAttributeValue(ATTR_ENABLED, "true"); 369 if ((! isEnabled) && onlyEnabled) 370 { 371 continue; 372 } 373 374 final Integer port = entry.getAttributeValueAsInteger(ATTR_LISTEN_PORT); 375 if ((port == null) || (port < 1) || (port > 65535)) 376 { 377 continue; 378 } 379 380 381 final boolean usesSSL = entry.hasAttributeValue(ATTR_USE_SSL, "true"); 382 383 final boolean supportsStartTLS; 384 if (usesSSL) 385 { 386 supportsStartTLS = false; 387 } 388 else 389 { 390 supportsStartTLS = 391 entry.hasAttributeValue(ATTR_ALLOW_START_TLS, "true"); 392 } 393 394 final List<String> listenAddresses; 395 final String[] addressArray = 396 entry.getAttributeValues(ATTR_LISTEN_ADDRESS); 397 if (addressArray == null) 398 { 399 listenAddresses = Collections.emptyList(); 400 } 401 else 402 { 403 final Set<String> s = new LinkedHashSet<>(); 404 for (final String address : addressArray) 405 { 406 try 407 { 408 final InetAddress a = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER. 409 getByName(address); 410 if (a.isAnyLocalAddress()) 411 { 412 continue; 413 } 414 } 415 catch (final Exception e) 416 { 417 Debug.debugException(e); 418 } 419 420 s.add(address); 421 } 422 423 listenAddresses = Collections.unmodifiableList(new ArrayList<>(s)); 424 } 425 426 configs.add(new LDAPConnectionHandlerConfiguration(name, isEnabled, 427 listenAddresses, port, usesSSL, supportsStartTLS)); 428 } 429 430 if (configs.size() <= 1) 431 { 432 return Collections.unmodifiableList(configs); 433 } 434 435 final SortedSet<LDAPConnectionHandlerConfiguration> configSet = 436 new TreeSet<>(configs.get(0)); 437 configSet.addAll(configs); 438 return Collections.unmodifiableList(new ArrayList<>(configSet)); 439 } 440 catch (final IOException e) 441 { 442 Debug.debugException(e); 443 throw new LDAPException(ResultCode.LOCAL_ERROR, 444 ERR_LDAP_HANDLER_CANNOT_READ_CONFIG.get(configFile.getAbsolutePath(), 445 StaticUtils.getExceptionMessage(e))); 446 } 447 } 448 449 450 451 /** 452 * Retrieves a hash code for the connection handler configuration. 453 * 454 * @return A hash code for the connection handler configuration. 455 */ 456 @Override() 457 public int hashCode() 458 { 459 return name.toLowerCase().hashCode(); 460 } 461 462 463 464 /** 465 * Indicates whether the provided object is considered logically equivalent to 466 * this LDAP connection handler configuration. 467 * 468 * @param o If the provided object is considered logically equivalent to 469 * this LDAP connection handler configuration, or {@code false} if 470 * not. 471 */ 472 @Override() 473 public boolean equals(@Nullable final Object o) 474 { 475 if (o == null) 476 { 477 return false; 478 } 479 480 if (o == this) 481 { 482 return true; 483 } 484 485 if (! (o instanceof LDAPConnectionHandlerConfiguration)) 486 { 487 return false; 488 } 489 490 final LDAPConnectionHandlerConfiguration c = 491 (LDAPConnectionHandlerConfiguration) o; 492 return (name.equalsIgnoreCase(c.name) && 493 (isEnabled == c.isEnabled) && 494 listenAddresses.equals(c.listenAddresses) && 495 (port == c.port) && 496 (usesSSL == c.usesSSL) && 497 (supportsStartTLS == c.supportsStartTLS)); 498 } 499 500 501 502 /** 503 * Compares the provided configuration to this configuration to determine the 504 * relative orders in which they should appear in a supported list. Sorting 505 * will use the following criteria: 506 * <UL> 507 * <LI>Connection handlers that are enabled will be ordered before those 508 * that are disabled.</LI> 509 * <LI>Connection handlers that use SSL will be ordered before those that 510 * support StartTLS, and those that support StartTLS will be ordered 511 * before those that do not support StartTLS.</LI> 512 * <LI>An SSL-enabled connection handler named "LDAPS Connection Handler" 513 * will be ordered before an SSL-enabled connection handler with some 514 * other name. A non-SSL-enabled connection handler named "LDAP 515 * Connection Handler" will be ordered before a non-SSL-enabled 516 * connection handler with some other name.</LI> 517 * <LI>Connection handlers that do not use listen addresses (and therefore 518 * listen on all interfaces) will be ordered before those that are 519 * configured with one or more listen addresses.</LI> 520 * <LI>Connection handlers with a lower port number will be ordered before 521 * those with a higher port number.</LI> 522 * <LI>As a last resort, then connection handlers will be ordered 523 * lexicographically by name.</LI> 524 * </UL> 525 * 526 * @param config The LDAP connection handler configuration to compare 527 * against this configuration. 528 * 529 * @return A negative value if this configuration should be ordered before 530 * the provided configuration, a positive value if the provided 531 * configuration should be ordered before this configuration, or 532 * zero if the configurations are considered logically equivalent. 533 */ 534 @Override() 535 public int compareTo( 536 @Nullable final LDAPConnectionHandlerConfiguration config) 537 { 538 if (config == null) 539 { 540 return -1; 541 } 542 543 if (isEnabled != config.isEnabled) 544 { 545 if (isEnabled) 546 { 547 return -1; 548 } 549 else 550 { 551 return 1; 552 } 553 } 554 555 if (usesSSL != config.usesSSL) 556 { 557 if (usesSSL) 558 { 559 return -1; 560 } 561 else 562 { 563 return 1; 564 } 565 } 566 567 if (supportsStartTLS != config.supportsStartTLS) 568 { 569 if (supportsStartTLS) 570 { 571 return -1; 572 } 573 else 574 { 575 return 1; 576 } 577 } 578 579 if (! name.equalsIgnoreCase(config.name)) 580 { 581 if (usesSSL) 582 { 583 if (name.equalsIgnoreCase("LDAPS Connection Handler")) 584 { 585 return -1; 586 } 587 588 if (config.name.equalsIgnoreCase("LDAPS Connection Handler")) 589 { 590 return 1; 591 } 592 } 593 else 594 { 595 if (name.equalsIgnoreCase("LDAP Connection Handler")) 596 { 597 return -1; 598 } 599 600 if (config.name.equalsIgnoreCase("LDAP Connection Handler")) 601 { 602 return 1; 603 } 604 } 605 } 606 607 608 if (! listenAddresses.equals(config.listenAddresses)) 609 { 610 if (listenAddresses.isEmpty()) 611 { 612 return -1; 613 } 614 else if (config.listenAddresses.isEmpty()) 615 { 616 return 1; 617 } 618 } 619 620 if (port != config.port) 621 { 622 if (port < config.port) 623 { 624 return -1; 625 } 626 else 627 { 628 return 1; 629 } 630 } 631 632 return name.toLowerCase().compareTo(config.name.toLowerCase()); 633 } 634 635 636 637 /** 638 * Compares the provided configurations to determine their relative orders in 639 * which they should appear in a supported list. Sorting will use the 640 * criteria described in the documentation for the {@link #compareTo} method. 641 * 642 * @param c1 The first LDAP connection handler configuration to compare. 643 * @param c2 The second LDAP connection handler configuration to compare. 644 * 645 * @return A negative value if the first configuration should be ordered 646 * before the second configuration, a positive value if the first 647 * configuration should be ordered after the second configuration, or 648 * zero if the configurations are considered logically equivalent. 649 */ 650 @Override() 651 public int compare(@NotNull final LDAPConnectionHandlerConfiguration c1, 652 @NotNull final LDAPConnectionHandlerConfiguration c2) 653 { 654 return c1.compareTo(c2); 655 } 656 657 658 659 /** 660 * Retrieves a string representation of this LDAP connection handler 661 * configuration. 662 * 663 * @return A string representation of this LDAP connection handler 664 * configuration. 665 */ 666 @Override() 667 @NotNull() 668 public String toString() 669 { 670 final StringBuilder buffer = new StringBuilder(); 671 toString(buffer); 672 return buffer.toString(); 673 } 674 675 676 677 /** 678 * Appends a string representation of this LDAP connection handler 679 * configuration to the provided buffer. 680 * 681 * @param buffer The buffer to which the information should be appended. 682 */ 683 public void toString(@NotNull final StringBuilder buffer) 684 { 685 buffer.append("LDAPConnectionHandlerConfiguration(name='"); 686 buffer.append(name); 687 buffer.append("', isEnabled="); 688 buffer.append(isEnabled); 689 buffer.append(", usesSSL="); 690 buffer.append(usesSSL); 691 buffer.append(", supportsStartTLS="); 692 buffer.append(supportsStartTLS); 693 buffer.append(", listenAddresses={"); 694 695 final Iterator<String> iterator = listenAddresses.iterator(); 696 while (iterator.hasNext()) 697 { 698 buffer.append(" '"); 699 buffer.append(iterator.next()); 700 buffer.append('\''); 701 702 if (iterator.hasNext()) 703 { 704 buffer.append(','); 705 } 706 } 707 708 buffer.append(" }, listenPort="); 709 buffer.append(port); 710 buffer.append(')'); 711 } 712}