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