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}