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