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