001/*
002 * Copyright 2012-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2012-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) 2012-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.util;
037
038
039
040import java.io.OutputStream;
041import java.util.concurrent.atomic.AtomicReference;
042import javax.net.SocketFactory;
043import javax.net.ssl.KeyManager;
044import javax.net.ssl.SSLSocketFactory;
045import javax.net.ssl.TrustManager;
046
047import com.unboundid.ldap.sdk.BindRequest;
048import com.unboundid.ldap.sdk.ExtendedResult;
049import com.unboundid.ldap.sdk.InternalSDKHelper;
050import com.unboundid.ldap.sdk.LDAPConnection;
051import com.unboundid.ldap.sdk.LDAPConnectionOptions;
052import com.unboundid.ldap.sdk.LDAPConnectionPool;
053import com.unboundid.ldap.sdk.LDAPException;
054import com.unboundid.ldap.sdk.PostConnectProcessor;
055import com.unboundid.ldap.sdk.ResultCode;
056import com.unboundid.ldap.sdk.ServerSet;
057import com.unboundid.ldap.sdk.SimpleBindRequest;
058import com.unboundid.ldap.sdk.SingleServerSet;
059import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
060import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
061import com.unboundid.util.args.ArgumentException;
062import com.unboundid.util.args.ArgumentParser;
063import com.unboundid.util.args.BooleanArgument;
064import com.unboundid.util.args.DNArgument;
065import com.unboundid.util.args.FileArgument;
066import com.unboundid.util.args.IntegerArgument;
067import com.unboundid.util.args.StringArgument;
068import com.unboundid.util.ssl.AggregateTrustManager;
069import com.unboundid.util.ssl.KeyStoreKeyManager;
070import com.unboundid.util.ssl.SSLUtil;
071import com.unboundid.util.ssl.TrustAllTrustManager;
072import com.unboundid.util.ssl.TrustStoreTrustManager;
073
074import static com.unboundid.util.UtilityMessages.*;
075
076
077
078/**
079 * This class provides a basis for developing command-line tools that have the
080 * ability to communicate with multiple directory servers, potentially with
081 * very different settings for each.  For example, it may be used to help create
082 * tools that move or compare data from one server to another.
083 * <BR><BR>
084 * Each server will be identified by a prefix and/or suffix that will be added
085 * to the argument name (e.g., if the first server has a prefix of "source",
086 * then the "hostname" argument will actually be "sourceHostname").  The
087 * base names for the arguments this class supports include:
088 * <UL>
089 *   <LI>hostname -- Specifies the address of the directory server.  If this
090 *       isn't specified, then a default of "localhost" will be used.</LI>
091 *   <LI>port -- specifies the port number of the directory server.  If this
092 *       isn't specified, then a default port of 389 will be used.</LI>
093 *   <LI>bindDN -- Specifies the DN to use to bind to the directory server using
094 *       simple authentication.  If this isn't specified, then simple
095 *       authentication will not be performed.</LI>
096 *   <LI>bindPassword -- Specifies the password to use when binding with simple
097 *       authentication or a password-based SASL mechanism.</LI>
098 *   <LI>bindPasswordFile -- Specifies the path to a file containing the
099 *       password to use when binding with simple authentication or a
100 *       password-based SASL mechanism.</LI>
101 *   <LI>useSSL -- Indicates that communication with the server should be
102 *       secured using SSL.</LI>
103 *   <LI>useStartTLS -- Indicates that communication with the server should be
104 *       secured using StartTLS.</LI>
105 *   <LI>trustAll -- Indicates that the client should trust any certificate
106 *       that the server presents to it.</LI>
107 *   <LI>keyStorePath -- Specifies the path to the key store to use to obtain
108 *       client certificates.</LI>
109 *   <LI>keyStorePassword -- Specifies the password to use to access the
110 *       contents of the key store.</LI>
111 *   <LI>keyStorePasswordFile -- Specifies the path ot a file containing the
112 *       password to use to access the contents of the key store.</LI>
113 *   <LI>keyStoreFormat -- Specifies the format to use for the key store
114 *       file.</LI>
115 *   <LI>trustStorePath -- Specifies the path to the trust store to use to
116 *       obtain client certificates.</LI>
117 *   <LI>trustStorePassword -- Specifies the password to use to access the
118 *       contents of the trust store.</LI>
119 *   <LI>trustStorePasswordFile -- Specifies the path ot a file containing the
120 *       password to use to access the contents of the trust store.</LI>
121 *   <LI>trustStoreFormat -- Specifies the format to use for the trust store
122 *       file.</LI>
123 *   <LI>certNickname -- Specifies the nickname of the client certificate to
124 *       use when performing SSL client authentication.</LI>
125 *   <LI>saslOption -- Specifies a SASL option to use when performing SASL
126 *       authentication.</LI>
127 * </UL>
128 * If SASL authentication is to be used, then a "mech" SASL option must be
129 * provided to specify the name of the SASL mechanism to use.  Depending on the
130 * SASL mechanism, additional SASL options may be required or optional.
131 */
132@Extensible()
133@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
134public abstract class MultiServerLDAPCommandLineTool
135       extends CommandLineTool
136{
137  // The set of prefixes and suffixes that will be used for server names.
138  private final int numServers;
139  @Nullable private final String[] serverNamePrefixes;
140  @Nullable private final String[] serverNameSuffixes;
141
142  // The set of arguments used to hold information about connection properties.
143  @NotNull private final BooleanArgument[] trustAll;
144  @NotNull private final BooleanArgument[] useSSL;
145  @NotNull private final BooleanArgument[] useStartTLS;
146  @NotNull private final DNArgument[]      bindDN;
147  @NotNull private final FileArgument[]    bindPasswordFile;
148  @NotNull private final FileArgument[]    keyStorePasswordFile;
149  @NotNull private final FileArgument[]    trustStorePasswordFile;
150  @NotNull private final IntegerArgument[] port;
151  @NotNull private final StringArgument[]  bindPassword;
152  @NotNull private final StringArgument[]  certificateNickname;
153  @NotNull private final StringArgument[]  host;
154  @NotNull private final StringArgument[]  keyStoreFormat;
155  @NotNull private final StringArgument[]  keyStorePath;
156  @NotNull private final StringArgument[]  keyStorePassword;
157  @NotNull private final StringArgument[]  saslOption;
158  @NotNull private final StringArgument[]  trustStoreFormat;
159  @NotNull private final StringArgument[]  trustStorePath;
160  @NotNull private final StringArgument[]  trustStorePassword;
161
162  // Variables used when creating and authenticating connections.
163  @NotNull private final BindRequest[]      bindRequest;
164  @NotNull private final ServerSet[]        serverSet;
165  @NotNull private final SSLSocketFactory[] startTLSSocketFactory;
166
167  // An atomic reference to an aggregate trust manager that will check a
168  // JVM-default set of trusted issuers, and then its own cache, before
169  // prompting the user about whether to trust the presented certificate chain.
170  // Re-using this trust manager will allow the tool to benefit from a common
171  // cache if multiple connections are needed.
172  @NotNull private final AtomicReference<AggregateTrustManager>
173       promptTrustManager;
174
175
176
177  /**
178   * Creates a new instance of this multi-server LDAP command-line tool.  At
179   * least one of the set of server name prefixes and suffixes must be
180   * non-{@code null}.  If both are non-{@code null}, then they must have the
181   * same number of elements.
182   *
183   * @param  outStream           The output stream to use for standard output.
184   *                             It may be {@code System.out} for the JVM's
185   *                             default standard output stream, {@code null} if
186   *                             no output should be generated, or a custom
187   *                             output stream if the output should be sent to
188   *                             an alternate location.
189   * @param  errStream           The output stream to use for standard error.
190   *                             It may be {@code System.err} for the JVM's
191   *                             default standard error stream, {@code null} if
192   *                             no output should be generated, or a custom
193   *                             output stream if the output should be sent to
194   *                             an alternate location.
195   * @param  serverNamePrefixes  The prefixes to include before the names of
196   *                             each of the parameters to identify each server.
197   *                             It may be {@code null} if only suffixes should
198   *                             be used.
199   * @param  serverNameSuffixes  The suffixes to include after the names of each
200   *                             of the parameters to identify each server.  It
201   *                             may be {@code null} if only prefixes should be
202   *                             used.
203   *
204   * @throws  LDAPSDKUsageException  If both the sets of server name prefixes
205   *                                 and suffixes are {@code null} or empty, or
206   *                                 if both sets are non-{@code null} but have
207   *                                 different numbers of elements.
208   */
209  public MultiServerLDAPCommandLineTool(@Nullable final OutputStream outStream,
210              @Nullable final OutputStream errStream,
211              @Nullable final String[] serverNamePrefixes,
212              @Nullable final String[] serverNameSuffixes)
213         throws LDAPSDKUsageException
214  {
215    super(outStream, errStream);
216
217    promptTrustManager = new AtomicReference<>();
218
219    this.serverNamePrefixes = serverNamePrefixes;
220    this.serverNameSuffixes = serverNameSuffixes;
221
222    if (serverNamePrefixes == null)
223    {
224      if (serverNameSuffixes == null)
225      {
226        throw new LDAPSDKUsageException(
227             ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_NULL.get());
228      }
229      else
230      {
231        numServers = serverNameSuffixes.length;
232      }
233    }
234    else
235    {
236      numServers = serverNamePrefixes.length;
237
238      if ((serverNameSuffixes != null) &&
239          (serverNamePrefixes.length != serverNameSuffixes.length))
240      {
241        throw new LDAPSDKUsageException(
242             ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_MISMATCH.get());
243      }
244    }
245
246    if (numServers == 0)
247    {
248      throw new LDAPSDKUsageException(
249           ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_EMPTY.get());
250    }
251
252    trustAll               = new BooleanArgument[numServers];
253    useSSL                 = new BooleanArgument[numServers];
254    useStartTLS            = new BooleanArgument[numServers];
255    bindDN                 = new DNArgument[numServers];
256    bindPasswordFile       = new FileArgument[numServers];
257    keyStorePasswordFile   = new FileArgument[numServers];
258    trustStorePasswordFile = new FileArgument[numServers];
259    port                   = new IntegerArgument[numServers];
260    bindPassword           = new StringArgument[numServers];
261    certificateNickname    = new StringArgument[numServers];
262    host                   = new StringArgument[numServers];
263    keyStoreFormat         = new StringArgument[numServers];
264    keyStorePath           = new StringArgument[numServers];
265    keyStorePassword       = new StringArgument[numServers];
266    saslOption             = new StringArgument[numServers];
267    trustStoreFormat       = new StringArgument[numServers];
268    trustStorePath         = new StringArgument[numServers];
269    trustStorePassword     = new StringArgument[numServers];
270
271    bindRequest           = new BindRequest[numServers];
272    serverSet             = new ServerSet[numServers];
273    startTLSSocketFactory = new SSLSocketFactory[numServers];
274  }
275
276
277
278  /**
279   * {@inheritDoc}
280   */
281  @Override()
282  public final void addToolArguments(@NotNull final ArgumentParser parser)
283         throws ArgumentException
284  {
285    for (int i=0; i < numServers; i++)
286    {
287      final StringBuilder groupNameBuffer = new StringBuilder();
288      if (serverNamePrefixes != null)
289      {
290        final String prefix = serverNamePrefixes[i].replace('-', ' ').trim();
291        groupNameBuffer.append(StaticUtils.capitalize(prefix, true));
292      }
293
294      if (serverNameSuffixes != null)
295      {
296        if (groupNameBuffer.length() > 0)
297        {
298          groupNameBuffer.append(' ');
299        }
300
301        final String suffix = serverNameSuffixes[i].replace('-', ' ').trim();
302        groupNameBuffer.append(StaticUtils.capitalize(suffix, true));
303      }
304
305      groupNameBuffer.append(' ');
306      groupNameBuffer.append(INFO_MULTI_LDAP_TOOL_GROUP_CONN_AND_AUTH.get());
307      final String groupName = groupNameBuffer.toString();
308
309
310      host[i] = new StringArgument(null, genArgName(i, "hostname"), true, 1,
311           INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
312           INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
313      host[i].setArgumentGroupName(groupName);
314      parser.addArgument(host[i]);
315
316      port[i] = new IntegerArgument(null, genArgName(i, "port"), true, 1,
317           INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
318           INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65_535, 389);
319      port[i].setArgumentGroupName(groupName);
320      parser.addArgument(port[i]);
321
322      bindDN[i] = new DNArgument(null, genArgName(i, "bindDN"), false, 1,
323           INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
324           INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
325      bindDN[i].setArgumentGroupName(groupName);
326      parser.addArgument(bindDN[i]);
327
328      bindPassword[i] = new StringArgument(null, genArgName(i, "bindPassword"),
329           false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
330           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
331      bindPassword[i].setSensitive(true);
332      bindPassword[i].setArgumentGroupName(groupName);
333      parser.addArgument(bindPassword[i]);
334
335      bindPasswordFile[i] = new FileArgument(null,
336           genArgName(i, "bindPasswordFile"), false, 1,
337           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
338           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
339           false);
340      bindPasswordFile[i].setArgumentGroupName(groupName);
341      parser.addArgument(bindPasswordFile[i]);
342
343      useSSL[i] = new BooleanArgument(null, genArgName(i, "useSSL"), 1,
344           INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get());
345      useSSL[i].setArgumentGroupName(groupName);
346      parser.addArgument(useSSL[i]);
347
348      useStartTLS[i] = new BooleanArgument(null, genArgName(i, "useStartTLS"),
349           1, INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get());
350      useStartTLS[i].setArgumentGroupName(groupName);
351      parser.addArgument(useStartTLS[i]);
352
353      trustAll[i] = new BooleanArgument(null, genArgName(i, "trustAll"), 1,
354           INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get());
355      trustAll[i].setArgumentGroupName(groupName);
356      parser.addArgument(trustAll[i]);
357
358      keyStorePath[i] = new StringArgument(null, genArgName(i, "keyStorePath"),
359           false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
360           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get());
361      keyStorePath[i].setArgumentGroupName(groupName);
362      parser.addArgument(keyStorePath[i]);
363
364      keyStorePassword[i] = new StringArgument(null,
365           genArgName(i, "keyStorePassword"), false, 1,
366           INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
367           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get());
368      keyStorePassword[i].setSensitive(true);
369      keyStorePassword[i].setArgumentGroupName(groupName);
370      parser.addArgument(keyStorePassword[i]);
371
372      keyStorePasswordFile[i] = new FileArgument(null,
373           genArgName(i, "keyStorePasswordFile"), false, 1,
374           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
375           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get(), true,
376           true, true, false);
377      keyStorePasswordFile[i].setArgumentGroupName(groupName);
378      parser.addArgument(keyStorePasswordFile[i]);
379
380      keyStoreFormat[i] = new StringArgument(null,
381           genArgName(i, "keyStoreFormat"), false, 1,
382           INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
383           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
384      keyStoreFormat[i].setArgumentGroupName(groupName);
385      parser.addArgument(keyStoreFormat[i]);
386
387      trustStorePath[i] = new StringArgument(null,
388           genArgName(i, "trustStorePath"), false, 1,
389           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
390           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
391      trustStorePath[i].setArgumentGroupName(groupName);
392      parser.addArgument(trustStorePath[i]);
393
394      trustStorePassword[i] = new StringArgument(null,
395           genArgName(i, "trustStorePassword"), false, 1,
396           INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
397           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
398      trustStorePassword[i].setSensitive(true);
399      trustStorePassword[i].setArgumentGroupName(groupName);
400      parser.addArgument(trustStorePassword[i]);
401
402      trustStorePasswordFile[i] = new FileArgument(null,
403           genArgName(i, "trustStorePasswordFile"), false, 1,
404           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
405           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get(), true,
406           true, true, false);
407      trustStorePasswordFile[i].setArgumentGroupName(groupName);
408      parser.addArgument(trustStorePasswordFile[i]);
409
410      trustStoreFormat[i] = new StringArgument(null,
411           genArgName(i, "trustStoreFormat"), false, 1,
412           INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
413           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
414      trustStoreFormat[i].setArgumentGroupName(groupName);
415      parser.addArgument(trustStoreFormat[i]);
416
417      certificateNickname[i] = new StringArgument(null,
418           genArgName(i, "certNickname"), false, 1,
419           INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
420           INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
421      certificateNickname[i].setArgumentGroupName(groupName);
422      parser.addArgument(certificateNickname[i]);
423
424      saslOption[i] = new StringArgument(null, genArgName(i, "saslOption"),
425           false, 0, INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
426           INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
427      saslOption[i].setArgumentGroupName(groupName);
428      parser.addArgument(saslOption[i]);
429
430      parser.addDependentArgumentSet(bindDN[i], bindPassword[i],
431           bindPasswordFile[i]);
432
433      parser.addExclusiveArgumentSet(useSSL[i], useStartTLS[i]);
434      parser.addExclusiveArgumentSet(bindPassword[i], bindPasswordFile[i]);
435      parser.addExclusiveArgumentSet(keyStorePassword[i],
436           keyStorePasswordFile[i]);
437      parser.addExclusiveArgumentSet(trustStorePassword[i],
438           trustStorePasswordFile[i]);
439      parser.addExclusiveArgumentSet(trustAll[i], trustStorePath[i]);
440    }
441
442    addNonLDAPArguments(parser);
443  }
444
445
446
447  /**
448   * Constructs the name to use for an argument from the given base and the
449   * appropriate prefix and suffix.
450   *
451   * @param  index  The index into the set of prefixes and suffixes.
452   * @param  base   The base name for the argument.
453   *
454   * @return  The constructed argument name.
455   */
456  @NotNull()
457  private String genArgName(final int index, @NotNull final String base)
458  {
459    final StringBuilder buffer = new StringBuilder();
460
461    if (serverNamePrefixes != null)
462    {
463      buffer.append(serverNamePrefixes[index]);
464
465      if (base.equals("saslOption"))
466      {
467        buffer.append("SASLOption");
468      }
469      else
470      {
471        buffer.append(StaticUtils.capitalize(base));
472      }
473    }
474    else
475    {
476      buffer.append(base);
477    }
478
479    if (serverNameSuffixes != null)
480    {
481      buffer.append(serverNameSuffixes[index]);
482    }
483
484    return buffer.toString();
485  }
486
487
488
489  /**
490   * Adds the arguments needed by this command-line tool to the provided
491   * argument parser which are not related to connecting or authenticating to
492   * the directory server.
493   *
494   * @param  parser  The argument parser to which the arguments should be added.
495   *
496   * @throws  ArgumentException  If a problem occurs while adding the arguments.
497   */
498  public abstract void addNonLDAPArguments(@NotNull ArgumentParser parser)
499         throws ArgumentException;
500
501
502
503  /**
504   * {@inheritDoc}
505   */
506  @Override()
507  public final void doExtendedArgumentValidation()
508         throws ArgumentException
509  {
510    doExtendedNonLDAPArgumentValidation();
511  }
512
513
514
515  /**
516   * Performs any necessary processing that should be done to ensure that the
517   * provided set of command-line arguments were valid.  This method will be
518   * called after the basic argument parsing has been performed and after all
519   * LDAP-specific argument validation has been processed, and immediately
520   * before the {@link CommandLineTool#doToolProcessing} method is invoked.
521   *
522   * @throws  ArgumentException  If there was a problem with the command-line
523   *                             arguments provided to this program.
524   */
525  public void doExtendedNonLDAPArgumentValidation()
526         throws ArgumentException
527  {
528    // No processing will be performed by default.
529  }
530
531
532
533  /**
534   * Retrieves the connection options that should be used for connections that
535   * are created with this command line tool.  Subclasses may override this
536   * method to use a custom set of connection options.
537   *
538   * @return  The connection options that should be used for connections that
539   *          are created with this command line tool.
540   */
541  @NotNull()
542  public LDAPConnectionOptions getConnectionOptions()
543  {
544    return new LDAPConnectionOptions();
545  }
546
547
548
549  /**
550   * Retrieves a connection that may be used to communicate with the indicated
551   * directory server.
552   * <BR><BR>
553   * Note that this method is threadsafe and may be invoked by multiple threads
554   * accessing the same instance only while that instance is in the process of
555   * invoking the {@link #doToolProcessing} method.
556   *
557   * @param  serverIndex  The zero-based index of the server to which the
558   *                      connection should be established.
559   *
560   * @return  A connection that may be used to communicate with the indicated
561   *          directory server.
562   *
563   * @throws  LDAPException  If a problem occurs while creating the connection.
564   */
565  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
566  @NotNull()
567  public final LDAPConnection getConnection(final int serverIndex)
568         throws LDAPException
569  {
570    final LDAPConnection connection = getUnauthenticatedConnection(serverIndex);
571
572    try
573    {
574      if (bindRequest[serverIndex] != null)
575      {
576        connection.bind(bindRequest[serverIndex]);
577      }
578    }
579    catch (final LDAPException le)
580    {
581      Debug.debugException(le);
582      connection.close();
583      throw le;
584    }
585
586    return connection;
587  }
588
589
590
591  /**
592   * Retrieves an unauthenticated connection that may be used to communicate
593   * with the indicated directory server.
594   * <BR><BR>
595   * Note that this method is threadsafe and may be invoked by multiple threads
596   * accessing the same instance only while that instance is in the process of
597   * invoking the {@link #doToolProcessing} method.
598   *
599   * @param  serverIndex  The zero-based index of the server to which the
600   *                      connection should be established.
601   *
602   * @return  An unauthenticated connection that may be used to communicate with
603   *          the indicated directory server.
604   *
605   * @throws  LDAPException  If a problem occurs while creating the connection.
606   */
607  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
608  @NotNull()
609  public final LDAPConnection getUnauthenticatedConnection(
610                                   final int serverIndex)
611         throws LDAPException
612  {
613    if (serverSet[serverIndex] == null)
614    {
615      serverSet[serverIndex]   = createServerSet(serverIndex);
616      bindRequest[serverIndex] = createBindRequest(serverIndex);
617    }
618
619    final LDAPConnection connection = serverSet[serverIndex].getConnection();
620
621    if (useStartTLS[serverIndex].isPresent())
622    {
623      try
624      {
625        final ExtendedResult extendedResult =
626             connection.processExtendedOperation(new StartTLSExtendedRequest(
627                  startTLSSocketFactory[serverIndex]));
628        if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
629        {
630          throw new LDAPException(extendedResult.getResultCode(),
631               ERR_LDAP_TOOL_START_TLS_FAILED.get(
632                    extendedResult.getDiagnosticMessage()));
633        }
634      }
635      catch (final LDAPException le)
636      {
637        Debug.debugException(le);
638        connection.close();
639        throw le;
640      }
641    }
642
643    return connection;
644  }
645
646
647
648  /**
649   * Retrieves a connection pool that may be used to communicate with the
650   * indicated directory server.
651   * <BR><BR>
652   * Note that this method is threadsafe and may be invoked by multiple threads
653   * accessing the same instance only while that instance is in the process of
654   * invoking the {@link #doToolProcessing} method.
655   *
656   * @param  serverIndex         The zero-based index of the server to which the
657   *                             connection should be established.
658   * @param  initialConnections  The number of connections that should be
659   *                             initially established in the pool.
660   * @param  maxConnections      The maximum number of connections to maintain
661   *                             in the pool.
662   *
663   * @return  A connection that may be used to communicate with the indicated
664   *          directory server.
665   *
666   * @throws  LDAPException  If a problem occurs while creating the connection
667   *                         pool.
668   */
669  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
670  @NotNull()
671  public final LDAPConnectionPool getConnectionPool(
672                                       final int serverIndex,
673                                       final int initialConnections,
674                                       final int maxConnections)
675            throws LDAPException
676  {
677    if (serverSet[serverIndex] == null)
678    {
679      serverSet[serverIndex]   = createServerSet(serverIndex);
680      bindRequest[serverIndex] = createBindRequest(serverIndex);
681    }
682
683    PostConnectProcessor postConnectProcessor = null;
684    if (useStartTLS[serverIndex].isPresent())
685    {
686      postConnectProcessor = new StartTLSPostConnectProcessor(
687           startTLSSocketFactory[serverIndex]);
688    }
689
690    return new LDAPConnectionPool(serverSet[serverIndex],
691         bindRequest[serverIndex], initialConnections, maxConnections,
692         postConnectProcessor);
693  }
694
695
696
697  /**
698   * Creates the server set to use when creating connections or connection
699   * pools.
700   *
701   * @param  serverIndex  The zero-based index of the server to which the
702   *                      connection should be established.
703   *
704   * @return  The server set to use when creating connections or connection
705   *          pools.
706   *
707   * @throws  LDAPException  If a problem occurs while creating the server set.
708   */
709  @NotNull()
710  public final ServerSet createServerSet(final int serverIndex)
711         throws LDAPException
712  {
713    final SSLUtil sslUtil = createSSLUtil(serverIndex);
714
715    SocketFactory socketFactory = null;
716    if (useSSL[serverIndex].isPresent())
717    {
718      try
719      {
720        socketFactory = sslUtil.createSSLSocketFactory();
721      }
722      catch (final Exception e)
723      {
724        Debug.debugException(e);
725        throw new LDAPException(ResultCode.LOCAL_ERROR,
726             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
727                  StaticUtils.getExceptionMessage(e)), e);
728      }
729    }
730    else if (useStartTLS[serverIndex].isPresent())
731    {
732      try
733      {
734        startTLSSocketFactory[serverIndex] = sslUtil.createSSLSocketFactory();
735      }
736      catch (final Exception e)
737      {
738        Debug.debugException(e);
739        throw new LDAPException(ResultCode.LOCAL_ERROR,
740             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
741                  StaticUtils.getExceptionMessage(e)), e);
742      }
743    }
744
745    return new SingleServerSet(host[serverIndex].getValue(),
746         port[serverIndex].getValue(), socketFactory, getConnectionOptions());
747  }
748
749
750
751  /**
752   * Creates the SSLUtil instance to use for secure communication.
753   *
754   * @param  serverIndex  The zero-based index of the server to which the
755   *                      connection should be established.
756   *
757   * @return  The SSLUtil instance to use for secure communication, or
758   *          {@code null} if secure communication is not needed.
759   *
760   * @throws  LDAPException  If a problem occurs while creating the SSLUtil
761   *                         instance.
762   */
763  @Nullable()
764  public final SSLUtil createSSLUtil(final int serverIndex)
765         throws LDAPException
766  {
767    if (useSSL[serverIndex].isPresent() || useStartTLS[serverIndex].isPresent())
768    {
769      KeyManager keyManager = null;
770      if (keyStorePath[serverIndex].isPresent())
771      {
772        char[] pw = null;
773        if (keyStorePassword[serverIndex].isPresent())
774        {
775          pw = keyStorePassword[serverIndex].getValue().toCharArray();
776        }
777        else if (keyStorePasswordFile[serverIndex].isPresent())
778        {
779          try
780          {
781            pw = getPasswordFileReader().readPassword(
782                 keyStorePasswordFile[serverIndex].getValue());
783          }
784          catch (final Exception e)
785          {
786            Debug.debugException(e);
787            throw new LDAPException(ResultCode.LOCAL_ERROR,
788                 ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
789                      StaticUtils.getExceptionMessage(e)), e);
790          }
791        }
792
793        try
794        {
795          keyManager = new KeyStoreKeyManager(
796               keyStorePath[serverIndex].getValue(), pw,
797               keyStoreFormat[serverIndex].getValue(),
798               certificateNickname[serverIndex].getValue(), true);
799        }
800        catch (final Exception e)
801        {
802          Debug.debugException(e);
803          throw new LDAPException(ResultCode.LOCAL_ERROR,
804               ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
805                    StaticUtils.getExceptionMessage(e)), e);
806        }
807      }
808
809      TrustManager tm;
810      if (trustAll[serverIndex].isPresent())
811      {
812        tm = new TrustAllTrustManager(false);
813      }
814      else if (trustStorePath[serverIndex].isPresent())
815      {
816        char[] pw = null;
817        if (trustStorePassword[serverIndex].isPresent())
818        {
819          pw = trustStorePassword[serverIndex].getValue().toCharArray();
820        }
821        else if (trustStorePasswordFile[serverIndex].isPresent())
822        {
823          try
824          {
825            pw = getPasswordFileReader().readPassword(
826                 trustStorePasswordFile[serverIndex].getValue());
827          }
828          catch (final Exception e)
829          {
830            Debug.debugException(e);
831            throw new LDAPException(ResultCode.LOCAL_ERROR,
832                 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
833                      StaticUtils.getExceptionMessage(e)), e);
834          }
835        }
836
837        tm = new TrustStoreTrustManager(
838             trustStorePath[serverIndex].getValue(), pw,
839             trustStoreFormat[serverIndex].getValue(), true);
840      }
841      else
842      {
843        tm = promptTrustManager.get();
844        if (tm == null)
845        {
846          final AggregateTrustManager atm =
847               InternalSDKHelper.getPreferredPromptTrustManagerChain(null);
848          if (promptTrustManager.compareAndSet(null, atm))
849          {
850            tm = atm;
851          }
852          else
853          {
854            tm = promptTrustManager.get();
855          }
856        }
857      }
858
859      return new SSLUtil(keyManager, tm);
860    }
861    else
862    {
863      return null;
864    }
865  }
866
867
868
869  /**
870   * Creates the bind request to use to authenticate to the indicated server.
871   *
872   * @param  serverIndex  The zero-based index of the server to which the
873   *                      connection should be established.
874   *
875   * @return  The bind request to use to authenticate to the indicated server,
876   *          or {@code null} if no bind should be performed.
877   *
878   * @throws  LDAPException  If a problem occurs while creating the bind
879   *                         request.
880   */
881  @Nullable()
882  public final BindRequest createBindRequest(final int serverIndex)
883         throws LDAPException
884  {
885    final String pw;
886    if (bindPassword[serverIndex].isPresent())
887    {
888      pw = bindPassword[serverIndex].getValue();
889    }
890    else if (bindPasswordFile[serverIndex].isPresent())
891    {
892      try
893      {
894        pw = new String(getPasswordFileReader().readPassword(
895             bindPasswordFile[serverIndex].getValue()));
896      }
897      catch (final Exception e)
898      {
899        Debug.debugException(e);
900        throw new LDAPException(ResultCode.LOCAL_ERROR,
901             ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
902                  StaticUtils.getExceptionMessage(e)), e);
903      }
904    }
905    else
906    {
907      pw = null;
908    }
909
910    if (saslOption[serverIndex].isPresent())
911    {
912      final String dnStr;
913      if (bindDN[serverIndex].isPresent())
914      {
915        dnStr = bindDN[serverIndex].getValue().toString();
916      }
917      else
918      {
919        dnStr = null;
920      }
921
922      return SASLUtils.createBindRequest(dnStr, pw, null,
923           saslOption[serverIndex].getValues());
924    }
925    else if (bindDN[serverIndex].isPresent())
926    {
927      return new SimpleBindRequest(bindDN[serverIndex].getValue(), pw);
928    }
929    else
930    {
931      return null;
932    }
933  }
934}