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 }