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