001/*
002 * Copyright 2011-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2011-2018 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        return connection;
536      }
537      catch (final LDAPException le)
538      {
539        Debug.debugException(le);
540        if (firstException == null)
541        {
542          firstException = le;
543        }
544      }
545    }
546
547    // If we've gotten here, then we couldn't connect to any of the servers.
548    // Throw the first exception that we encountered.
549    throw firstException;
550  }
551
552
553
554  /**
555   * {@inheritDoc}
556   */
557  @Override()
558  public void toString(final StringBuilder buffer)
559  {
560    buffer.append("DNSSRVRecordServerSet(recordName='");
561    buffer.append(recordName);
562    buffer.append("', providerURL='");
563    buffer.append(providerURL);
564    buffer.append("', ttlMillis=");
565    buffer.append(ttlMillis);
566
567    if (socketFactory != null)
568    {
569      buffer.append(", socketFactoryClass='");
570      buffer.append(socketFactory.getClass().getName());
571      buffer.append('\'');
572    }
573
574    if (connectionOptions != null)
575    {
576      buffer.append(", connectionOptions");
577      connectionOptions.toString(buffer);
578    }
579
580    buffer.append(", includesAuthentication=");
581    buffer.append(bindRequest != null);
582    buffer.append(", includesPostConnectProcessing=");
583    buffer.append(postConnectProcessor != null);
584    buffer.append(')');
585  }
586}