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