001/* 002 * Copyright 2011-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2011-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) 2011-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; 037 038 039 040import java.util.Collections; 041import java.util.Hashtable; 042import java.util.Map; 043import java.util.Properties; 044import javax.naming.Context; 045import javax.net.SocketFactory; 046 047import com.unboundid.util.Debug; 048import com.unboundid.util.NotMutable; 049import com.unboundid.util.NotNull; 050import com.unboundid.util.Nullable; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053 054 055 056/** 057 * This class provides a server set implementation that can discover information 058 * about available directory servers through DNS SRV records as described in 059 * <A HREF="http://www.ietf.org/rfc/rfc2782.txt">RFC 2782</A>. DNS SRV records 060 * make it possible for clients to use the domain name system to discover 061 * information about the systems that provide a given service, which can help 062 * avoid the need to explicitly configure clients with the addresses of the 063 * appropriate set of directory servers. 064 * <BR><BR> 065 * The standard service name used to reference LDAP directory servers is 066 * "_ldap._tcp". If client systems have DNS configured properly with an 067 * appropriate search domain, then this may be all that is needed to discover 068 * any available directory servers. Alternately, a record name of 069 * "_ldap._tcp.example.com" may be used to request DNS information about LDAP 070 * servers for the example.com domain. However, there is no technical 071 * requirement that "_ldap._tcp" must be used for this purpose, and it may make 072 * sense to use a different name if there is something special about the way 073 * clients should interact with the servers (e.g., "_ldaps._tcp" would be more 074 * appropriate if LDAP clients need to use SSL when communicating with the 075 * server). 076 * <BR><BR> 077 * DNS SRV records contain a number of components, including: 078 * <UL> 079 * <LI>The address of the system providing the service.</LI> 080 * <LI>The port to which connections should be established to access the 081 * service.</LI> 082 * <LI>The priority assigned to the service record. If there are multiple 083 * servers that provide the associated service, then the priority can be 084 * used to specify the order in which they should be contacted. Records 085 * with a lower priority value wil be used before those with a higher 086 * priority value.</LI> 087 * <LI>The weight assigned to the service record. The weight will be used if 088 * there are multiple service records with the same priority, and it 089 * controls how likely each record is to be chosen. A record with a 090 * weight of 2 is twice as likely to be chosen as a record with the same 091 * priority and a weight of 1.</LI> 092 * </UL> 093 * In the event that multiple SRV records exist for the target service, then the 094 * priorities and weights of those records will be used to determine the order 095 * in which the servers will be tried. Records with a lower priority value will 096 * always be tried before those with a higher priority value. For records with 097 * equal priority values and nonzero weights, then the ratio of those weight 098 * values will be used to control how likely one of those records is to be tried 099 * before another. Records with a weight of zero will always be tried after 100 * records with the same priority and nonzero weights. 101 * <BR><BR> 102 * This server set implementation uses JNDI to communicate with DNS servers in 103 * order to obtain the requested SRV records (although it does not use JNDI for 104 * any LDAP communication). In order to specify which DNS server(s) to query, a 105 * JNDI provider URL must be used. In many cases, a URL of "dns:", which 106 * indicates that the client should use the DNS servers configured for use by 107 * the underlying system, should be sufficient. However, if you wish to use a 108 * specific DNS server then you may explicitly specify it in the URL (e.g., 109 * "dns://1.2.3.4:53" would attempt to communicate with the DNS server listening 110 * on IP address 1.2.3.4 and port 53). If you wish to specify multiple DNS 111 * servers, you may provide multiple URLs separated with spaces and they will be 112 * tried in the order in which they were included in the list until a response 113 * can be retrieved (e.g., for a provider URL of "dns://1.2.3.4 dns://1.2.3.5", 114 * it will first try to use the DNS server running on system with IP address 115 * "1.2.3.4", but if that is not successful then it will try the DNS server 116 * running on the system with IP address "1.2.3.5"). See the <A HREF= 117 *"http://download.oracle.com/javase/6/docs/technotes/guides/jndi/jndi-dns.html" 118 * > JNDI DNS service provider documentation</A> for more details on acceptable 119 * formats for the provider URL. 120 */ 121@NotMutable() 122@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 123public final class DNSSRVRecordServerSet 124 extends ServerSet 125{ 126 /** 127 * The default SRV record name that will be retrieved if none is specified. 128 */ 129 @NotNull private static final String DEFAULT_RECORD_NAME = "_ldap._tcp"; 130 131 132 133 /** 134 * The default time-to-live value (1 hour, represented in milliseconds) that 135 * will be used if no alternate value is specified. 136 */ 137 private static final long DEFAULT_TTL_MILLIS = 60L * 60L * 1000L; 138 139 140 141 /** 142 * The default provider URL that will be used for specifying which DNS 143 * server(s) to query. The default behavior will be to attempt to determine 144 * which DNS server(s) to use from the underlying system configuration. 145 */ 146 @NotNull private static final String DEFAULT_DNS_PROVIDER_URL = "dns:"; 147 148 149 150 // The bind request to use to authenticate connections created by this 151 // server set. 152 @Nullable private final BindRequest bindRequest; 153 154 // The properties that will be used to initialize the JNDI context. 155 @NotNull private final Hashtable<String,String> jndiProperties; 156 157 // The connection options to use for newly-created connections. 158 @Nullable private final LDAPConnectionOptions connectionOptions; 159 160 // The maximum length of time in milliseconds that previously-retrieved 161 // information should be considered valid. 162 private final long ttlMillis; 163 164 // The post-connect processor to invoke against connections created by this 165 // server set. 166 @Nullable private final PostConnectProcessor postConnectProcessor; 167 168 // The socket factory that should be used to create connections. 169 @Nullable private final SocketFactory socketFactory; 170 171 // The cached set of SRV records. 172 @Nullable private volatile SRVRecordSet recordSet; 173 174 // The name of the DNS SRV record to retrieve. 175 @NotNull private final String recordName; 176 177 // The DNS provider URL to use. 178 @NotNull private final String providerURL; 179 180 181 182 /** 183 * Creates a new instance of this server set that will use the specified DNS 184 * record name, a default DNS provider URL that will attempt to determine DNS 185 * servers from the underlying system configuration, a default TTL of one 186 * hour, round-robin ordering for servers with the same priority, and default 187 * socket factory and connection options. 188 * 189 * @param recordName The name of the DNS SRV record to retrieve. If this is 190 * {@code null}, then a default record name of 191 * "_ldap._tcp" will be used. 192 */ 193 public DNSSRVRecordServerSet(@Nullable final String recordName) 194 { 195 this(recordName, null, DEFAULT_TTL_MILLIS, null, null); 196 } 197 198 199 200 /** 201 * Creates a new instance of this server set that will use the provided 202 * settings. 203 * 204 * @param recordName The name of the DNS SRV record to retrieve. If 205 * this is {@code null}, then a default record name 206 * of "_ldap._tcp" will be used. 207 * @param providerURL The JNDI provider URL that may be used to 208 * specify the DNS server(s) to use. If this is 209 * not specified, then a default URL of "dns:" will 210 * be used, which will attempt to determine the 211 * appropriate servers from the underlying system 212 * configuration. 213 * @param ttlMillis Specifies the maximum length of time in 214 * milliseconds that DNS information should be 215 * cached before it needs to be retrieved again. A 216 * value less than or equal to zero will use the 217 * default TTL of one hour. 218 * @param socketFactory The socket factory that will be used when 219 * creating connections. It may be {@code null} if 220 * the JVM-default socket factory should be used. 221 * @param connectionOptions The set of connection options that should be 222 * used for the connections that are created. It 223 * may be {@code null} if the default connection 224 * options should be used. 225 */ 226 public DNSSRVRecordServerSet(@Nullable final String recordName, 227 @Nullable final String providerURL, 228 final long ttlMillis, 229 @Nullable final SocketFactory socketFactory, 230 @Nullable final LDAPConnectionOptions connectionOptions) 231 { 232 this(recordName, providerURL, null, ttlMillis, socketFactory, 233 connectionOptions); 234 } 235 236 237 238 /** 239 * Creates a new instance of this server set that will use the provided 240 * settings. 241 * 242 * @param recordName The name of the DNS SRV record to retrieve. If 243 * this is {@code null}, then a default record name 244 * of "_ldap._tcp" will be used. 245 * @param providerURL The JNDI provider URL that may be used to 246 * specify the DNS server(s) to use. If this is 247 * not specified, then a default URL of "dns:" will 248 * be used, which will attempt to determine the 249 * appropriate servers from the underlying system 250 * configuration. 251 * @param jndiProperties A set of JNDI-related properties that should be 252 * be used when initializing the context for 253 * interacting with the DNS server via JNDI. If 254 * this is {@code null}, then a default set of 255 * properties will be used. 256 * @param ttlMillis Specifies the maximum length of time in 257 * milliseconds that DNS information should be 258 * cached before it needs to be retrieved again. A 259 * value less than or equal to zero will use the 260 * default TTL of one hour. 261 * @param socketFactory The socket factory that will be used when 262 * creating connections. It may be {@code null} if 263 * the JVM-default socket factory should be used. 264 * @param connectionOptions The set of connection options that should be 265 * used for the connections that are created. It 266 * may be {@code null} if the default connection 267 * options should be used. 268 */ 269 public DNSSRVRecordServerSet(@Nullable final String recordName, 270 @Nullable final String providerURL, 271 @Nullable final Properties jndiProperties, 272 final long ttlMillis, 273 @Nullable final SocketFactory socketFactory, 274 @Nullable final LDAPConnectionOptions connectionOptions) 275 { 276 this(recordName, providerURL, jndiProperties, ttlMillis, socketFactory, 277 connectionOptions, null, null); 278 } 279 280 281 282 /** 283 * Creates a new instance of this server set that will use the provided 284 * settings. 285 * 286 * @param recordName The name of the DNS SRV record to retrieve. 287 * If this is {@code null}, then a default 288 * record name of "_ldap._tcp" will be used. 289 * @param providerURL The JNDI provider URL that may be used to 290 * specify the DNS server(s) to use. If this is 291 * not specified, then a default URL of 292 * "dns:" will be used, which will attempt to 293 * determine the appropriate servers from the 294 * underlying system configuration. 295 * @param jndiProperties A set of JNDI-related properties that should 296 * be be used when initializing the context for 297 * interacting with the DNS server via JNDI. 298 * If this is {@code null}, then a default set 299 * of properties will be used. 300 * @param ttlMillis Specifies the maximum length of time in 301 * milliseconds that DNS information should be 302 * cached before it needs to be retrieved 303 * again. A value less than or equal to zero 304 * will use the default TTL of one hour. 305 * @param socketFactory The socket factory that will be used when 306 * creating connections. It may be 307 * {@code null} if the JVM-default socket 308 * factory should be used. 309 * @param connectionOptions The set of connection options that should be 310 * used for the connections that are created. 311 * It may be {@code null} if the default 312 * connection options should be used. 313 * @param bindRequest The bind request that should be used to 314 * authenticate newly-established connections. 315 * It may be {@code null} if this server set 316 * should not perform any authentication. 317 * @param postConnectProcessor The post-connect processor that should be 318 * invoked on newly-established connections. It 319 * may be {@code null} if this server set should 320 * not perform any post-connect processing. 321 */ 322 public DNSSRVRecordServerSet(@Nullable final String recordName, 323 @Nullable final String providerURL, 324 @Nullable final Properties jndiProperties, 325 final long ttlMillis, 326 @Nullable final SocketFactory socketFactory, 327 @Nullable final LDAPConnectionOptions connectionOptions, 328 @Nullable final BindRequest bindRequest, 329 @Nullable final PostConnectProcessor postConnectProcessor) 330 { 331 this.socketFactory = socketFactory; 332 this.connectionOptions = connectionOptions; 333 this.bindRequest = bindRequest; 334 this.postConnectProcessor = postConnectProcessor; 335 336 recordSet = null; 337 338 if (recordName == null) 339 { 340 this.recordName = DEFAULT_RECORD_NAME; 341 } 342 else 343 { 344 this.recordName = recordName; 345 } 346 347 if (providerURL == null) 348 { 349 this.providerURL = DEFAULT_DNS_PROVIDER_URL; 350 } 351 else 352 { 353 this.providerURL = providerURL; 354 } 355 356 this.jndiProperties = new Hashtable<>(10); 357 if (jndiProperties != null) 358 { 359 for (final Map.Entry<Object,Object> e : jndiProperties.entrySet()) 360 { 361 this.jndiProperties.put(String.valueOf(e.getKey()), 362 String.valueOf(e.getValue())); 363 } 364 } 365 366 if (! this.jndiProperties.containsKey(Context.INITIAL_CONTEXT_FACTORY)) 367 { 368 this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, 369 "com.sun.jndi.dns.DnsContextFactory"); 370 } 371 372 if (! this.jndiProperties.containsKey(Context.PROVIDER_URL)) 373 { 374 this.jndiProperties.put(Context.PROVIDER_URL, this.providerURL); 375 } 376 377 if (ttlMillis <= 0L) 378 { 379 this.ttlMillis = DEFAULT_TTL_MILLIS; 380 } 381 else 382 { 383 this.ttlMillis = ttlMillis; 384 } 385 } 386 387 388 389 /** 390 * Retrieves the name of the DNS SRV record to retrieve. 391 * 392 * @return The name of the DNS SRV record to retrieve. 393 */ 394 @NotNull() 395 public String getRecordName() 396 { 397 return recordName; 398 } 399 400 401 402 /** 403 * Retrieves the JNDI provider URL that specifies the DNS server(s) to use. 404 * 405 * @return The JNDI provider URL that specifies the DNS server(s) to use. 406 */ 407 @NotNull() 408 public String getProviderURL() 409 { 410 return providerURL; 411 } 412 413 414 415 /** 416 * Retrieves an unmodifiable map of properties that will be used to initialize 417 * the JNDI context used to interact with DNS. Note that the map returned 418 * will reflect the actual properties that will be used, and may not exactly 419 * match the properties provided when creating this server set. 420 * 421 * @return An unmodifiable map of properties that will be used to initialize 422 * the JNDI context used to interact with DNS. 423 */ 424 @NotNull() 425 public Map<String,String> getJNDIProperties() 426 { 427 return Collections.unmodifiableMap(jndiProperties); 428 } 429 430 431 432 /** 433 * Retrieves the maximum length of time in milliseconds that 434 * previously-retrieved DNS information should be cached before it needs to be 435 * refreshed. 436 * 437 * @return The maximum length of time in milliseconds that 438 * previously-retrieved DNS information should be cached before it 439 * needs to be refreshed. 440 */ 441 public long getTTLMillis() 442 { 443 return ttlMillis; 444 } 445 446 447 448 /** 449 * Retrieves the socket factory that will be used when creating connections, 450 * if any. 451 * 452 * @return The socket factory that will be used when creating connections, or 453 * {@code null} if the JVM-default socket factory will be used. 454 */ 455 @Nullable() 456 public SocketFactory getSocketFactory() 457 { 458 return socketFactory; 459 } 460 461 462 463 /** 464 * Retrieves the set of connection options to use for connections that are 465 * created, if any. 466 * 467 * @return The set of connection options to use for connections that are 468 * created, or {@code null} if a default set of options should be 469 * used. 470 */ 471 @Nullable() 472 public LDAPConnectionOptions getConnectionOptions() 473 { 474 return connectionOptions; 475 } 476 477 478 479 /** 480 * {@inheritDoc} 481 */ 482 @Override() 483 public boolean includesAuthentication() 484 { 485 return (bindRequest != null); 486 } 487 488 489 490 /** 491 * {@inheritDoc} 492 */ 493 @Override() 494 public boolean includesPostConnectProcessing() 495 { 496 return (postConnectProcessor != null); 497 } 498 499 500 501 /** 502 * {@inheritDoc} 503 */ 504 @Override() 505 @NotNull() 506 public LDAPConnection getConnection() 507 throws LDAPException 508 { 509 return getConnection(null); 510 } 511 512 513 514 /** 515 * {@inheritDoc} 516 */ 517 @Override() 518 @NotNull() 519 public LDAPConnection getConnection( 520 @Nullable final LDAPConnectionPoolHealthCheck healthCheck) 521 throws LDAPException 522 { 523 // If there is no cached record set, or if the cached set is expired, then 524 // try to get a new one. 525 if ((recordSet == null) || recordSet.isExpired()) 526 { 527 try 528 { 529 recordSet = SRVRecordSet.getRecordSet(recordName, jndiProperties, 530 ttlMillis); 531 } 532 catch (final LDAPException le) 533 { 534 Debug.debugException(le); 535 536 // We couldn't get a new record set. If we have an existing one, then 537 // it's expired but we'll keep using it anyway because it's better than 538 // nothing. But if we don't have an existing set, then we can't 539 // continue. 540 if (recordSet == null) 541 { 542 throw le; 543 } 544 } 545 } 546 547 548 // Iterate through the record set in an order based on priority and weight. 549 // Take the first one that we can connect to and that satisfies the health 550 // check (if any). 551 LDAPException firstException = null; 552 for (final SRVRecord r : recordSet.getOrderedRecords()) 553 { 554 try 555 { 556 final LDAPConnection connection = new LDAPConnection(socketFactory, 557 connectionOptions, r.getAddress(), r.getPort()); 558 doBindPostConnectAndHealthCheckProcessing(connection, bindRequest, 559 postConnectProcessor, healthCheck); 560 associateConnectionWithThisServerSet(connection); 561 return connection; 562 } 563 catch (final LDAPException le) 564 { 565 Debug.debugException(le); 566 if (firstException == null) 567 { 568 firstException = le; 569 } 570 } 571 } 572 573 // If we've gotten here, then we couldn't connect to any of the servers. 574 // Throw the first exception that we encountered. 575 throw firstException; 576 } 577 578 579 580 /** 581 * {@inheritDoc} 582 */ 583 @Override() 584 public void toString(@NotNull final StringBuilder buffer) 585 { 586 buffer.append("DNSSRVRecordServerSet(recordName='"); 587 buffer.append(recordName); 588 buffer.append("', providerURL='"); 589 buffer.append(providerURL); 590 buffer.append("', ttlMillis="); 591 buffer.append(ttlMillis); 592 593 if (socketFactory != null) 594 { 595 buffer.append(", socketFactoryClass='"); 596 buffer.append(socketFactory.getClass().getName()); 597 buffer.append('\''); 598 } 599 600 if (connectionOptions != null) 601 { 602 buffer.append(", connectionOptions"); 603 connectionOptions.toString(buffer); 604 } 605 606 buffer.append(", includesAuthentication="); 607 buffer.append(bindRequest != null); 608 buffer.append(", includesPostConnectProcessing="); 609 buffer.append(postConnectProcessor != null); 610 buffer.append(')'); 611 } 612}