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