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].setArgumentGroupName(groupName);
313          parser.addArgument(bindPassword[i]);
314    
315          bindPasswordFile[i] = new FileArgument(null,
316               genArgName(i, "bindPasswordFile"), false, 1,
317               INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
318               INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
319               false);
320          bindPasswordFile[i].setArgumentGroupName(groupName);
321          parser.addArgument(bindPasswordFile[i]);
322    
323          useSSL[i] = new BooleanArgument(null, genArgName(i, "useSSL"), 1,
324               INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get());
325          useSSL[i].setArgumentGroupName(groupName);
326          parser.addArgument(useSSL[i]);
327    
328          useStartTLS[i] = new BooleanArgument(null, genArgName(i, "useStartTLS"),
329               1, INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get());
330          useStartTLS[i].setArgumentGroupName(groupName);
331          parser.addArgument(useStartTLS[i]);
332    
333          trustAll[i] = new BooleanArgument(null, genArgName(i, "trustAll"), 1,
334               INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get());
335          trustAll[i].setArgumentGroupName(groupName);
336          parser.addArgument(trustAll[i]);
337    
338          keyStorePath[i] = new StringArgument(null, genArgName(i, "keyStorePath"),
339               false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
340               INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get());
341          keyStorePath[i].setArgumentGroupName(groupName);
342          parser.addArgument(keyStorePath[i]);
343    
344          keyStorePassword[i] = new StringArgument(null,
345               genArgName(i, "keyStorePassword"), false, 1,
346               INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
347               INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get());
348          keyStorePassword[i].setArgumentGroupName(groupName);
349          parser.addArgument(keyStorePassword[i]);
350    
351          keyStorePasswordFile[i] = new FileArgument(null,
352               genArgName(i, "keyStorePasswordFile"), false, 1,
353               INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
354               INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get(), true,
355               true, true, false);
356          keyStorePasswordFile[i].setArgumentGroupName(groupName);
357          parser.addArgument(keyStorePasswordFile[i]);
358    
359          keyStoreFormat[i] = new StringArgument(null,
360               genArgName(i, "keyStoreFormat"), false, 1,
361               INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
362               INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
363          keyStoreFormat[i].setArgumentGroupName(groupName);
364          parser.addArgument(keyStoreFormat[i]);
365    
366          trustStorePath[i] = new StringArgument(null,
367               genArgName(i, "trustStorePath"), false, 1,
368               INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
369               INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
370          trustStorePath[i].setArgumentGroupName(groupName);
371          parser.addArgument(trustStorePath[i]);
372    
373          trustStorePassword[i] = new StringArgument(null,
374               genArgName(i, "trustStorePassword"), false, 1,
375               INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
376               INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
377          trustStorePassword[i].setArgumentGroupName(groupName);
378          parser.addArgument(trustStorePassword[i]);
379    
380          trustStorePasswordFile[i] = new FileArgument(null,
381               genArgName(i, "trustStorePasswordFile"), false, 1,
382               INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
383               INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get(), true,
384               true, true, false);
385          trustStorePasswordFile[i].setArgumentGroupName(groupName);
386          parser.addArgument(trustStorePasswordFile[i]);
387    
388          trustStoreFormat[i] = new StringArgument(null,
389               genArgName(i, "trustStoreFormat"), false, 1,
390               INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
391               INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
392          trustStoreFormat[i].setArgumentGroupName(groupName);
393          parser.addArgument(trustStoreFormat[i]);
394    
395          certificateNickname[i] = new StringArgument(null,
396               genArgName(i, "certNickname"), false, 1,
397               INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
398               INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
399          certificateNickname[i].setArgumentGroupName(groupName);
400          parser.addArgument(certificateNickname[i]);
401    
402          saslOption[i] = new StringArgument(null, genArgName(i, "saslOption"),
403               false, 0, INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
404               INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
405          saslOption[i].setArgumentGroupName(groupName);
406          parser.addArgument(saslOption[i]);
407    
408          parser.addDependentArgumentSet(bindDN[i], bindPassword[i],
409               bindPasswordFile[i]);
410    
411          parser.addExclusiveArgumentSet(useSSL[i], useStartTLS[i]);
412          parser.addExclusiveArgumentSet(bindPassword[i], bindPasswordFile[i]);
413          parser.addExclusiveArgumentSet(keyStorePassword[i],
414               keyStorePasswordFile[i]);
415          parser.addExclusiveArgumentSet(trustStorePassword[i],
416               trustStorePasswordFile[i]);
417          parser.addExclusiveArgumentSet(trustAll[i], trustStorePath[i]);
418        }
419    
420        addNonLDAPArguments(parser);
421      }
422    
423    
424    
425      /**
426       * Constructs the name to use for an argument from the given base and the
427       * appropriate prefix and suffix.
428       *
429       * @param  index  The index into the set of prefixes and suffixes.
430       * @param  base   The base name for the argument.
431       *
432       * @return  The constructed argument name.
433       */
434      private String genArgName(final int index, final String base)
435      {
436        final StringBuilder buffer = new StringBuilder();
437    
438        if (serverNamePrefixes != null)
439        {
440          buffer.append(serverNamePrefixes[index]);
441    
442          if (base.equals("saslOption"))
443          {
444            buffer.append("SASLOption");
445          }
446          else
447          {
448            buffer.append(StaticUtils.capitalize(base));
449          }
450        }
451        else
452        {
453          buffer.append(base);
454        }
455    
456        if (serverNameSuffixes != null)
457        {
458          buffer.append(serverNameSuffixes[index]);
459        }
460    
461        return buffer.toString();
462      }
463    
464    
465    
466      /**
467       * Adds the arguments needed by this command-line tool to the provided
468       * argument parser which are not related to connecting or authenticating to
469       * the directory server.
470       *
471       * @param  parser  The argument parser to which the arguments should be added.
472       *
473       * @throws  ArgumentException  If a problem occurs while adding the arguments.
474       */
475      public abstract void addNonLDAPArguments(final ArgumentParser parser)
476             throws ArgumentException;
477    
478    
479    
480      /**
481       * {@inheritDoc}
482       */
483      @Override()
484      public final void doExtendedArgumentValidation()
485             throws ArgumentException
486      {
487        doExtendedNonLDAPArgumentValidation();
488      }
489    
490    
491    
492      /**
493       * Performs any necessary processing that should be done to ensure that the
494       * provided set of command-line arguments were valid.  This method will be
495       * called after the basic argument parsing has been performed and after all
496       * LDAP-specific argument validation has been processed, and immediately
497       * before the {@link CommandLineTool#doToolProcessing} method is invoked.
498       *
499       * @throws  ArgumentException  If there was a problem with the command-line
500       *                             arguments provided to this program.
501       */
502      public void doExtendedNonLDAPArgumentValidation()
503             throws ArgumentException
504      {
505        // No processing will be performed by default.
506      }
507    
508    
509    
510      /**
511       * Retrieves the connection options that should be used for connections that
512       * are created with this command line tool.  Subclasses may override this
513       * method to use a custom set of connection options.
514       *
515       * @return  The connection options that should be used for connections that
516       *          are created with this command line tool.
517       */
518      public LDAPConnectionOptions getConnectionOptions()
519      {
520        return new LDAPConnectionOptions();
521      }
522    
523    
524    
525      /**
526       * Retrieves a connection that may be used to communicate with the indicated
527       * directory server.
528       * <BR><BR>
529       * Note that this method is threadsafe and may be invoked by multiple threads
530       * accessing the same instance only while that instance is in the process of
531       * invoking the {@link #doToolProcessing} method.
532       *
533       * @param  serverIndex  The zero-based index of the server to which the
534       *                      connection should be established.
535       *
536       * @return  A connection that may be used to communicate with the indicated
537       *          directory server.
538       *
539       * @throws  LDAPException  If a problem occurs while creating the connection.
540       */
541      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
542      public final LDAPConnection getConnection(final int serverIndex)
543             throws LDAPException
544      {
545        final LDAPConnection connection = getUnauthenticatedConnection(serverIndex);
546    
547        try
548        {
549          if (bindRequest[serverIndex] != null)
550          {
551            connection.bind(bindRequest[serverIndex]);
552          }
553        }
554        catch (LDAPException le)
555        {
556          Debug.debugException(le);
557          connection.close();
558          throw le;
559        }
560    
561        return connection;
562      }
563    
564    
565    
566      /**
567       * Retrieves an unauthenticated connection that may be used to communicate
568       * with the indicated directory server.
569       * <BR><BR>
570       * Note that this method is threadsafe and may be invoked by multiple threads
571       * accessing the same instance only while that instance is in the process of
572       * invoking the {@link #doToolProcessing} method.
573       *
574       * @param  serverIndex  The zero-based index of the server to which the
575       *                      connection should be established.
576       *
577       * @return  An unauthenticated connection that may be used to communicate with
578       *          the indicated directory server.
579       *
580       * @throws  LDAPException  If a problem occurs while creating the connection.
581       */
582      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
583      public final LDAPConnection getUnauthenticatedConnection(
584                                       final int serverIndex)
585             throws LDAPException
586      {
587        if (serverSet[serverIndex] == null)
588        {
589          serverSet[serverIndex]   = createServerSet(serverIndex);
590          bindRequest[serverIndex] = createBindRequest(serverIndex);
591        }
592    
593        final LDAPConnection connection = serverSet[serverIndex].getConnection();
594    
595        if (useStartTLS[serverIndex].isPresent())
596        {
597          try
598          {
599            final ExtendedResult extendedResult =
600                 connection.processExtendedOperation(new StartTLSExtendedRequest(
601                      startTLSSocketFactory[serverIndex]));
602            if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
603            {
604              throw new LDAPException(extendedResult.getResultCode(),
605                   ERR_LDAP_TOOL_START_TLS_FAILED.get(
606                        extendedResult.getDiagnosticMessage()));
607            }
608          }
609          catch (LDAPException le)
610          {
611            Debug.debugException(le);
612            connection.close();
613            throw le;
614          }
615        }
616    
617        return connection;
618      }
619    
620    
621    
622      /**
623       * Retrieves a connection pool that may be used to communicate with the
624       * indicated directory server.
625       * <BR><BR>
626       * Note that this method is threadsafe and may be invoked by multiple threads
627       * accessing the same instance only while that instance is in the process of
628       * invoking the {@link #doToolProcessing} method.
629       *
630       * @param  serverIndex         The zero-based index of the server to which the
631       *                             connection should be established.
632       * @param  initialConnections  The number of connections that should be
633       *                             initially established in the pool.
634       * @param  maxConnections      The maximum number of connections to maintain
635       *                             in the pool.
636       *
637       * @return  A connection that may be used to communicate with the indicated
638       *          directory server.
639       *
640       * @throws  LDAPException  If a problem occurs while creating the connection
641       *                         pool.
642       */
643      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
644      public final LDAPConnectionPool getConnectionPool(
645                                           final int serverIndex,
646                                           final int initialConnections,
647                                           final int maxConnections)
648                throws LDAPException
649      {
650        if (serverSet[serverIndex] == null)
651        {
652          serverSet[serverIndex]   = createServerSet(serverIndex);
653          bindRequest[serverIndex] = createBindRequest(serverIndex);
654        }
655    
656        PostConnectProcessor postConnectProcessor = null;
657        if (useStartTLS[serverIndex].isPresent())
658        {
659          postConnectProcessor = new StartTLSPostConnectProcessor(
660               startTLSSocketFactory[serverIndex]);
661        }
662    
663        return new LDAPConnectionPool(serverSet[serverIndex],
664             bindRequest[serverIndex], initialConnections, maxConnections,
665             postConnectProcessor);
666      }
667    
668    
669    
670      /**
671       * Creates the server set to use when creating connections or connection
672       * pools.
673       *
674       * @param  serverIndex  The zero-based index of the server to which the
675       *                      connection should be established.
676       *
677       * @return  The server set to use when creating connections or connection
678       *          pools.
679       *
680       * @throws  LDAPException  If a problem occurs while creating the server set.
681       */
682      public final ServerSet createServerSet(final int serverIndex)
683             throws LDAPException
684      {
685        final SSLUtil sslUtil = createSSLUtil(serverIndex);
686    
687        SocketFactory socketFactory = null;
688        if (useSSL[serverIndex].isPresent())
689        {
690          try
691          {
692            socketFactory = sslUtil.createSSLSocketFactory();
693          }
694          catch (Exception e)
695          {
696            Debug.debugException(e);
697            throw new LDAPException(ResultCode.LOCAL_ERROR,
698                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
699                      StaticUtils.getExceptionMessage(e)), e);
700          }
701        }
702        else if (useStartTLS[serverIndex].isPresent())
703        {
704          try
705          {
706            startTLSSocketFactory[serverIndex] = sslUtil.createSSLSocketFactory();
707          }
708          catch (Exception e)
709          {
710            Debug.debugException(e);
711            throw new LDAPException(ResultCode.LOCAL_ERROR,
712                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
713                      StaticUtils.getExceptionMessage(e)), e);
714          }
715        }
716    
717        return new SingleServerSet(host[serverIndex].getValue(),
718             port[serverIndex].getValue(), socketFactory, getConnectionOptions());
719      }
720    
721    
722    
723      /**
724       * Creates the SSLUtil instance to use for secure communication.
725       *
726       * @param  serverIndex  The zero-based index of the server to which the
727       *                      connection should be established.
728       *
729       * @return  The SSLUtil instance to use for secure communication, or
730       *          {@code null} if secure communication is not needed.
731       *
732       * @throws  LDAPException  If a problem occurs while creating the SSLUtil
733       *                         instance.
734       */
735      public final SSLUtil createSSLUtil(final int serverIndex)
736             throws LDAPException
737      {
738        if (useSSL[serverIndex].isPresent() || useStartTLS[serverIndex].isPresent())
739        {
740          KeyManager keyManager = null;
741          if (keyStorePath[serverIndex].isPresent())
742          {
743            char[] pw = null;
744            if (keyStorePassword[serverIndex].isPresent())
745            {
746              pw = keyStorePassword[serverIndex].getValue().toCharArray();
747            }
748            else if (keyStorePasswordFile[serverIndex].isPresent())
749            {
750              try
751              {
752                pw = keyStorePasswordFile[serverIndex].getNonBlankFileLines().
753                     get(0).toCharArray();
754              }
755              catch (Exception e)
756              {
757                Debug.debugException(e);
758                throw new LDAPException(ResultCode.LOCAL_ERROR,
759                     ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
760                          StaticUtils.getExceptionMessage(e)), e);
761              }
762            }
763    
764            try
765            {
766              keyManager = new KeyStoreKeyManager(
767                   keyStorePath[serverIndex].getValue(), pw,
768                   keyStoreFormat[serverIndex].getValue(),
769                   certificateNickname[serverIndex].getValue());
770            }
771            catch (Exception e)
772            {
773              Debug.debugException(e);
774              throw new LDAPException(ResultCode.LOCAL_ERROR,
775                   ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
776                        StaticUtils.getExceptionMessage(e)), e);
777            }
778          }
779    
780          TrustManager trustManager;
781          if (trustAll[serverIndex].isPresent())
782          {
783            trustManager = new TrustAllTrustManager(false);
784          }
785          else if (trustStorePath[serverIndex].isPresent())
786          {
787            char[] pw = null;
788            if (trustStorePassword[serverIndex].isPresent())
789            {
790              pw = trustStorePassword[serverIndex].getValue().toCharArray();
791            }
792            else if (trustStorePasswordFile[serverIndex].isPresent())
793            {
794              try
795              {
796                pw = trustStorePasswordFile[serverIndex].getNonBlankFileLines().
797                     get(0).toCharArray();
798              }
799              catch (Exception e)
800              {
801                Debug.debugException(e);
802                throw new LDAPException(ResultCode.LOCAL_ERROR,
803                     ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
804                          StaticUtils.getExceptionMessage(e)), e);
805              }
806            }
807    
808            trustManager = new TrustStoreTrustManager(
809                 trustStorePath[serverIndex].getValue(), pw,
810                 trustStoreFormat[serverIndex].getValue(), true);
811          }
812          else
813          {
814            trustManager = promptTrustManager.get();
815            if (trustManager == null)
816            {
817              final PromptTrustManager m = new PromptTrustManager();
818              promptTrustManager.compareAndSet(null, m);
819              trustManager = promptTrustManager.get();
820            }
821          }
822    
823          return new SSLUtil(keyManager, trustManager);
824        }
825        else
826        {
827          return null;
828        }
829      }
830    
831    
832    
833      /**
834       * Creates the bind request to use to authenticate to the indicated server.
835       *
836       * @param  serverIndex  The zero-based index of the server to which the
837       *                      connection should be established.
838       *
839       * @return  The bind request to use to authenticate to the indicated server,
840       *          or {@code null} if no bind should be performed.
841       *
842       * @throws  LDAPException  If a problem occurs while creating the bind
843       *                         request.
844       */
845      public final BindRequest createBindRequest(final int serverIndex)
846             throws LDAPException
847      {
848        final String pw;
849        if (bindPassword[serverIndex].isPresent())
850        {
851          pw = bindPassword[serverIndex].getValue();
852        }
853        else if (bindPasswordFile[serverIndex].isPresent())
854        {
855          try
856          {
857            pw = bindPasswordFile[serverIndex].getNonBlankFileLines().get(0);
858          }
859          catch (Exception e)
860          {
861            Debug.debugException(e);
862            throw new LDAPException(ResultCode.LOCAL_ERROR,
863                 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
864                      StaticUtils.getExceptionMessage(e)), e);
865          }
866        }
867        else
868        {
869          pw = null;
870        }
871    
872        if (saslOption[serverIndex].isPresent())
873        {
874          final String dnStr;
875          if (bindDN[serverIndex].isPresent())
876          {
877            dnStr = bindDN[serverIndex].getValue().toString();
878          }
879          else
880          {
881            dnStr = null;
882          }
883    
884          return SASLUtils.createBindRequest(dnStr, pw, null,
885               saslOption[serverIndex].getValues());
886        }
887        else if (bindDN[serverIndex].isPresent())
888        {
889          return new SimpleBindRequest(bindDN[serverIndex].getValue(), pw);
890        }
891        else
892        {
893          return null;
894        }
895      }
896    }