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 }