001/*
002 * Copyright 2020-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020-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) 2020-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.unboundidds;
037
038
039
040import java.io.File;
041import java.io.IOException;
042import java.io.Serializable;
043import java.net.InetAddress;
044import java.util.ArrayList;
045import java.util.Collections;
046import java.util.Comparator;
047import java.util.Iterator;
048import java.util.LinkedHashSet;
049import java.util.List;
050import java.util.Set;
051import java.util.SortedSet;
052import java.util.TreeSet;
053
054import com.unboundid.ldap.sdk.Entry;
055import com.unboundid.ldap.sdk.LDAPConnectionOptions;
056import com.unboundid.ldap.sdk.LDAPException;
057import com.unboundid.ldap.sdk.ResultCode;
058import com.unboundid.ldif.LDIFException;
059import com.unboundid.ldif.LDIFReader;
060import com.unboundid.util.Debug;
061import com.unboundid.util.NotMutable;
062import com.unboundid.util.NotNull;
063import com.unboundid.util.Nullable;
064import com.unboundid.util.StaticUtils;
065import com.unboundid.util.ThreadSafety;
066import com.unboundid.util.ThreadSafetyLevel;
067
068import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
069
070
071
072/**
073 * This class provides a data structure that holds information about an LDAP
074 * connection handler defined in the configuration of a Ping Identity Directory
075 * Server instance.  It also provides a utility method for reading a Directory
076 * Server configuration file to obtain information about the listener instances
077 * it contains, and it implements the {@code Comparable} interface for ranking
078 * connection handlers by relative preference for use.
079 * <BR>
080 * <BLOCKQUOTE>
081 *   <B>NOTE:</B>  This class, and other classes within the
082 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
083 *   supported for use against Ping Identity, UnboundID, and
084 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
085 *   for proprietary functionality or for external specifications that are not
086 *   considered stable or mature enough to be guaranteed to work in an
087 *   interoperable way with other types of LDAP servers.
088 * </BLOCKQUOTE>
089 */
090@NotMutable()
091@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
092public final class LDAPConnectionHandlerConfiguration
093       implements Comparable<LDAPConnectionHandlerConfiguration>,
094                  Comparator<LDAPConnectionHandlerConfiguration>,
095                  Serializable
096{
097  /**
098   * The name of the LDAP object class that will be used in LDAP connection
099   * handler configuration entries.
100   */
101  @NotNull private static final String OC_LDAP_CONN_HANDLER =
102       "ds-cfg-ldap-connection-handler";
103
104
105
106  /**
107   * The name for the LDAP attribute that indicates whether the connection
108   * handler allows StartTLS.
109   */
110  @NotNull private static final String ATTR_ALLOW_START_TLS =
111       "ds-cfg-allow-start-tls";
112
113
114
115  /**
116   * The name fo the LDAP attribute that indicates whether the connection
117   * handler is enabled.
118   */
119  @NotNull private static final String ATTR_ENABLED = "ds-cfg-enabled";
120
121
122
123  /**
124   * The name for the LDAP attribute that specifies the set of addresses on
125   * which the connection handler will listen.
126   */
127  @NotNull private static final String ATTR_LISTEN_ADDRESS =
128       "ds-cfg-listen-address";
129
130
131
132  /**
133   * The name for the LDAP attribute that specifies the port on which the
134   * connection handler will listen.
135   */
136  @NotNull private static final String ATTR_LISTEN_PORT = "ds-cfg-listen-port";
137
138
139
140  /**
141   * The name for the LDAP attribute that specifies the name of the connection
142   * handler.
143   */
144  @NotNull private static final String ATTR_NAME = "cn";
145
146
147
148  /**
149   * The name for the LDAP attribute that indicates whether the connection
150   * handler uses SSL.
151   */
152  @NotNull private static final String ATTR_USE_SSL = "ds-cfg-use-ssl";
153
154
155
156  /**
157   * The serial version UID for this serializable class.
158   */
159  private static final long serialVersionUID = 6824077978334156627L;
160
161
162
163  // Indicates whether the connection handler is enabled for use.
164  private final boolean isEnabled;
165
166  // Indicates whether the connection handler supports StartTLS for encrypting
167  // communication.
168  private final boolean supportsStartTLS;
169
170  // Indicates whether the connection handler uses SSL to encrypt communication.
171  private final boolean usesSSL;
172
173  // The port on which the connection handler accepts client connections.
174  private final int port;
175
176  // The set of addresses on which the connection handler accepts client
177  // connections.
178  @NotNull private final List<String> listenAddresses;
179
180  // The name for the connection handler.
181  @NotNull private final String name;
182
183
184
185  /**
186   * Creates a new LDAP connection handler configuration object with the
187   * provided information.
188   *
189   * @param  name              The name for the connection handler.
190   * @param  isEnabled         Indicates whether the connection handler is
191   *                           enabled for use.
192   * @param  listenAddresses   The set of addresses on which the connection
193   *                           handler accepts client connections.  It must not
194   *                           be {@code null} but may be empty.
195   * @param  port              The port on which the connection handler accepts
196   *                           client connections.
197   * @param  usesSSL           Indicates whether the connection handler uses
198   *                           SSL to encrypt communication.
199   * @param  supportsStartTLS  Indicates whether the connection handler supports
200   *                           StartTLS for encrypting communication.
201   */
202  LDAPConnectionHandlerConfiguration(@NotNull final String name,
203       final boolean isEnabled, @NotNull final List<String> listenAddresses,
204       final int port, final boolean usesSSL, final boolean supportsStartTLS)
205  {
206    this.name = name;
207    this.isEnabled = isEnabled;
208    this.listenAddresses = listenAddresses;
209    this.port = port;
210    this.usesSSL = usesSSL;
211    this.supportsStartTLS = supportsStartTLS;
212  }
213
214
215
216  /**
217   * Retrieves the name for the connection handler.
218   *
219   * @return  The name for the connection handler.
220   */
221  @NotNull()
222  public String getName()
223  {
224    return name;
225  }
226
227
228
229  /**
230   * Indicates whether the connection handler is enabled for use.
231   *
232   * @return  {@code true} if the connection handler is enabled, or
233   *          {@code false} if not.
234   */
235  public boolean isEnabled()
236  {
237    return isEnabled;
238  }
239
240
241
242  /**
243   * Retrieves the set of addresses on which the connection handler accepts
244   * client connections, if available.
245   *
246   * @return  The set of addresses on which the connection handler accepts
247   *          client connections, or an empty set if the connection handler
248   *          listens on all addresses on all interfaces.
249   */
250  @NotNull()
251  public List<String> getListenAddresses()
252  {
253    return listenAddresses;
254  }
255
256
257
258  /**
259   * Retrieves the port on which the connection handler accepts client
260   * connections.
261   *
262   * @return  The port on which the connection handler accepts client
263   *          connections.
264   */
265  public int getPort()
266  {
267    return port;
268  }
269
270
271
272  /**
273   * Indicates whether the connection handler uses SSL to encrypt communication.
274   *
275   * @return  {@code true} if the connection handler uses SSL to encrypt
276   *          communication, or {@code false} if not.
277   */
278  public boolean usesSSL()
279  {
280    return usesSSL;
281  }
282
283
284
285  /**
286   * Indicates whether the connection handler supports StartTLS for encrypting
287   * communication.
288   *
289   * @return  {@code true} if the connection handler supports StartTLS for
290   *          encrypting communication, or {@code false} if not.
291   */
292  public boolean supportsStartTLS()
293  {
294    return supportsStartTLS;
295  }
296
297
298
299  /**
300   * Retrieves the LDAP connection handler configuration objects from the
301   * specified configuration file.  The configuration objects will be ordered
302   * from most preferred to least preferred, using the logic described in the
303   * {@link #compareTo} method documentation.
304   *
305   * @param  configFile   The configuration file to examine.  It must not be
306   *                      {@code null}, and it must exist.
307   * @param  onlyEnabled  Indicates whether to only include information about
308   *                      connection handlers that are enabled.
309   *
310   * @return  A list of the LDAP connection handler configuration objects read
311   *          from the specified configuration file, or an empty set if no LDAP
312   *          connection handler configuration entries were found in the
313   *          configuration.
314   *
315   * @throws  LDAPException  If a problem interferes with reading the
316   *                         connection handler configuration objects from the
317   *                         configuration file.
318   */
319  @NotNull()
320  public static List<LDAPConnectionHandlerConfiguration> readConfiguration(
321                     @NotNull final File configFile, final boolean onlyEnabled)
322         throws LDAPException
323  {
324    try (LDIFReader ldifReader = new LDIFReader(configFile))
325    {
326      final List<LDAPConnectionHandlerConfiguration> configs =
327           new ArrayList<>();
328      while (true)
329      {
330        final Entry entry;
331        try
332        {
333          entry = ldifReader.readEntry();
334        }
335        catch (final LDIFException e)
336        {
337          Debug.debugException(e);
338          if (e.mayContinueReading())
339          {
340            continue;
341          }
342          else
343          {
344            throw new LDAPException(ResultCode.DECODING_ERROR,
345                 ERR_LDAP_HANDLER_CANNOT_READ_CONFIG.get(
346                      configFile.getAbsolutePath(),
347                      StaticUtils.getExceptionMessage(e)));
348          }
349        }
350
351        if (entry == null)
352        {
353          break;
354        }
355
356        if (! entry.hasObjectClass(OC_LDAP_CONN_HANDLER))
357        {
358          continue;
359        }
360
361        final String name = entry.getAttributeValue(ATTR_NAME);
362        if (name == null)
363        {
364          continue;
365        }
366
367        final boolean isEnabled =
368             entry.hasAttributeValue(ATTR_ENABLED, "true");
369        if ((! isEnabled) && onlyEnabled)
370        {
371          continue;
372        }
373
374        final Integer port = entry.getAttributeValueAsInteger(ATTR_LISTEN_PORT);
375        if ((port == null) || (port < 1) || (port > 65535))
376        {
377          continue;
378        }
379
380
381        final boolean usesSSL = entry.hasAttributeValue(ATTR_USE_SSL, "true");
382
383        final boolean supportsStartTLS;
384        if (usesSSL)
385        {
386          supportsStartTLS = false;
387        }
388        else
389        {
390          supportsStartTLS =
391               entry.hasAttributeValue(ATTR_ALLOW_START_TLS, "true");
392        }
393
394        final List<String> listenAddresses;
395        final String[] addressArray =
396             entry.getAttributeValues(ATTR_LISTEN_ADDRESS);
397        if (addressArray == null)
398        {
399          listenAddresses = Collections.emptyList();
400        }
401        else
402        {
403          final Set<String> s = new LinkedHashSet<>();
404          for (final String address : addressArray)
405          {
406            try
407            {
408              final InetAddress a = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER.
409                   getByName(address);
410              if (a.isAnyLocalAddress())
411              {
412                continue;
413              }
414            }
415            catch (final Exception e)
416            {
417              Debug.debugException(e);
418            }
419
420            s.add(address);
421          }
422
423          listenAddresses = Collections.unmodifiableList(new ArrayList<>(s));
424        }
425
426        configs.add(new LDAPConnectionHandlerConfiguration(name, isEnabled,
427             listenAddresses, port, usesSSL, supportsStartTLS));
428      }
429
430      if (configs.size() <= 1)
431      {
432        return Collections.unmodifiableList(configs);
433      }
434
435      final SortedSet<LDAPConnectionHandlerConfiguration> configSet =
436           new TreeSet<>(configs.get(0));
437      configSet.addAll(configs);
438      return Collections.unmodifiableList(new ArrayList<>(configSet));
439    }
440    catch (final IOException e)
441    {
442      Debug.debugException(e);
443      throw new LDAPException(ResultCode.LOCAL_ERROR,
444           ERR_LDAP_HANDLER_CANNOT_READ_CONFIG.get(configFile.getAbsolutePath(),
445                StaticUtils.getExceptionMessage(e)));
446    }
447  }
448
449
450
451  /**
452   * Retrieves a hash code for the connection handler configuration.
453   *
454   * @return  A hash code for the connection handler configuration.
455   */
456  @Override()
457  public int hashCode()
458  {
459    return name.toLowerCase().hashCode();
460  }
461
462
463
464  /**
465   * Indicates whether the provided object is considered logically equivalent to
466   * this LDAP connection handler configuration.
467   *
468   * @param  o  If the provided object is considered logically equivalent to
469   *            this LDAP connection handler configuration, or {@code false} if
470   *            not.
471   */
472  @Override()
473  public boolean equals(@Nullable final Object o)
474  {
475    if (o == null)
476    {
477      return false;
478    }
479
480    if (o == this)
481    {
482      return true;
483    }
484
485    if (! (o instanceof LDAPConnectionHandlerConfiguration))
486    {
487      return false;
488    }
489
490    final LDAPConnectionHandlerConfiguration c =
491         (LDAPConnectionHandlerConfiguration) o;
492    return (name.equalsIgnoreCase(c.name) &&
493         (isEnabled == c.isEnabled) &&
494         listenAddresses.equals(c.listenAddresses) &&
495         (port == c.port) &&
496         (usesSSL == c.usesSSL) &&
497         (supportsStartTLS == c.supportsStartTLS));
498  }
499
500
501
502  /**
503   * Compares the provided configuration to this configuration to determine the
504   * relative orders in which they should appear in a supported list.  Sorting
505   * will use the following criteria:
506   * <UL>
507   *   <LI>Connection handlers that are enabled will be ordered before those
508   *       that are disabled.</LI>
509   *   <LI>Connection handlers that use SSL will be ordered before those that
510   *       support StartTLS, and those that support StartTLS will be ordered
511   *       before those that do not support StartTLS.</LI>
512   *   <LI>An SSL-enabled connection handler named "LDAPS Connection Handler"
513   *       will be ordered before an SSL-enabled connection handler with some
514   *       other name.  A non-SSL-enabled connection handler named "LDAP
515   *       Connection Handler" will be ordered before a non-SSL-enabled
516   *       connection handler with some other name.</LI>
517   *   <LI>Connection handlers that do not use listen addresses (and therefore
518   *       listen on all interfaces) will be ordered before those that are
519   *       configured with one or more listen addresses.</LI>
520   *   <LI>Connection handlers with a lower port number will be ordered before
521   *       those with a higher port number.</LI>
522   *   <LI>As a last resort, then connection handlers will be ordered
523   *       lexicographically by name.</LI>
524   * </UL>
525   *
526   * @param  config  The LDAP connection handler configuration to compare
527   *                 against this configuration.
528   *
529   * @return  A negative value if this configuration should be ordered before
530   *          the provided configuration, a positive value if the provided
531   *          configuration should be ordered before this configuration, or
532   *          zero if the configurations are considered logically equivalent.
533   */
534  @Override()
535  public int compareTo(
536       @Nullable final LDAPConnectionHandlerConfiguration config)
537  {
538    if (config == null)
539    {
540      return -1;
541    }
542
543    if (isEnabled != config.isEnabled)
544    {
545      if (isEnabled)
546      {
547        return -1;
548      }
549      else
550      {
551        return 1;
552      }
553    }
554
555    if (usesSSL != config.usesSSL)
556    {
557      if (usesSSL)
558      {
559        return -1;
560      }
561      else
562      {
563        return 1;
564      }
565    }
566
567    if (supportsStartTLS != config.supportsStartTLS)
568    {
569      if (supportsStartTLS)
570      {
571        return -1;
572      }
573      else
574      {
575        return 1;
576      }
577    }
578
579    if (! name.equalsIgnoreCase(config.name))
580    {
581      if (usesSSL)
582      {
583        if (name.equalsIgnoreCase("LDAPS Connection Handler"))
584        {
585          return -1;
586        }
587
588        if (config.name.equalsIgnoreCase("LDAPS Connection Handler"))
589        {
590          return 1;
591        }
592      }
593      else
594      {
595        if (name.equalsIgnoreCase("LDAP Connection Handler"))
596        {
597          return -1;
598        }
599
600        if (config.name.equalsIgnoreCase("LDAP Connection Handler"))
601        {
602          return 1;
603        }
604      }
605    }
606
607
608    if (! listenAddresses.equals(config.listenAddresses))
609    {
610      if (listenAddresses.isEmpty())
611      {
612        return -1;
613      }
614      else if (config.listenAddresses.isEmpty())
615      {
616        return 1;
617      }
618    }
619
620    if (port != config.port)
621    {
622      if (port < config.port)
623      {
624        return -1;
625      }
626      else
627      {
628        return 1;
629      }
630    }
631
632    return name.toLowerCase().compareTo(config.name.toLowerCase());
633  }
634
635
636
637  /**
638   * Compares the provided configurations to determine their relative orders in
639   * which they should appear in a supported list.  Sorting will use the
640   * criteria described in the documentation for the {@link #compareTo} method.
641   *
642   * @param  c1  The first LDAP connection handler configuration to compare.
643   * @param  c2  The second LDAP connection handler configuration to compare.
644   *
645   * @return  A negative value if the first configuration should be ordered
646   *          before the second configuration, a positive value if the first
647   *          configuration should be ordered after the second configuration, or
648   *          zero if the configurations are considered logically equivalent.
649   */
650  @Override()
651  public int compare(@NotNull final LDAPConnectionHandlerConfiguration c1,
652                     @NotNull final LDAPConnectionHandlerConfiguration c2)
653  {
654    return c1.compareTo(c2);
655  }
656
657
658
659  /**
660   * Retrieves a string representation of this LDAP connection handler
661   * configuration.
662   *
663   * @return  A string representation of this LDAP connection handler
664   *          configuration.
665   */
666  @Override()
667  @NotNull()
668  public String toString()
669  {
670    final StringBuilder buffer = new StringBuilder();
671    toString(buffer);
672    return buffer.toString();
673  }
674
675
676
677  /**
678   * Appends a string representation of this LDAP connection handler
679   * configuration to the provided buffer.
680   *
681   * @param  buffer  The buffer to which the information should be appended.
682   */
683  public void toString(@NotNull final StringBuilder buffer)
684  {
685    buffer.append("LDAPConnectionHandlerConfiguration(name='");
686    buffer.append(name);
687    buffer.append("', isEnabled=");
688    buffer.append(isEnabled);
689    buffer.append(", usesSSL=");
690    buffer.append(usesSSL);
691    buffer.append(", supportsStartTLS=");
692    buffer.append(supportsStartTLS);
693    buffer.append(", listenAddresses={");
694
695    final Iterator<String> iterator = listenAddresses.iterator();
696    while (iterator.hasNext())
697    {
698      buffer.append(" '");
699      buffer.append(iterator.next());
700      buffer.append('\'');
701
702      if (iterator.hasNext())
703      {
704        buffer.append(',');
705      }
706    }
707
708    buffer.append(" }, listenPort=");
709    buffer.append(port);
710    buffer.append(')');
711  }
712}