001 /*
002 * Copyright 2008-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2017 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.ArrayList;
027 import java.util.Collections;
028 import java.util.LinkedHashSet;
029 import java.util.List;
030 import java.util.Set;
031 import java.util.concurrent.atomic.AtomicReference;
032 import javax.net.SocketFactory;
033 import javax.net.ssl.KeyManager;
034 import javax.net.ssl.SSLSocketFactory;
035 import javax.net.ssl.TrustManager;
036
037 import com.unboundid.ldap.sdk.AggregatePostConnectProcessor;
038 import com.unboundid.ldap.sdk.BindRequest;
039 import com.unboundid.ldap.sdk.Control;
040 import com.unboundid.ldap.sdk.EXTERNALBindRequest;
041 import com.unboundid.ldap.sdk.ExtendedResult;
042 import com.unboundid.ldap.sdk.LDAPConnection;
043 import com.unboundid.ldap.sdk.LDAPConnectionOptions;
044 import com.unboundid.ldap.sdk.LDAPConnectionPool;
045 import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck;
046 import com.unboundid.ldap.sdk.LDAPException;
047 import com.unboundid.ldap.sdk.PostConnectProcessor;
048 import com.unboundid.ldap.sdk.ResultCode;
049 import com.unboundid.ldap.sdk.RoundRobinServerSet;
050 import com.unboundid.ldap.sdk.ServerSet;
051 import com.unboundid.ldap.sdk.SimpleBindRequest;
052 import com.unboundid.ldap.sdk.SingleServerSet;
053 import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
054 import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
055 import com.unboundid.util.args.ArgumentException;
056 import com.unboundid.util.args.ArgumentParser;
057 import com.unboundid.util.args.BooleanArgument;
058 import com.unboundid.util.args.DNArgument;
059 import com.unboundid.util.args.FileArgument;
060 import com.unboundid.util.args.IntegerArgument;
061 import com.unboundid.util.args.StringArgument;
062 import com.unboundid.util.ssl.KeyStoreKeyManager;
063 import com.unboundid.util.ssl.PromptTrustManager;
064 import com.unboundid.util.ssl.SSLUtil;
065 import com.unboundid.util.ssl.TrustAllTrustManager;
066 import com.unboundid.util.ssl.TrustStoreTrustManager;
067
068 import static com.unboundid.util.Debug.*;
069 import static com.unboundid.util.StaticUtils.*;
070 import static com.unboundid.util.UtilityMessages.*;
071
072
073
074 /**
075 * This class provides a basis for developing command-line tools that
076 * communicate with an LDAP directory server. It provides a common set of
077 * options for connecting and authenticating to a directory server, and then
078 * provides a mechanism for obtaining connections and connection pools to use
079 * when communicating with that server.
080 * <BR><BR>
081 * The arguments that this class supports include:
082 * <UL>
083 * <LI>"-h {address}" or "--hostname {address}" -- Specifies the address of
084 * the directory server. If this isn't specified, then a default of
085 * "localhost" will be used.</LI>
086 * <LI>"-p {port}" or "--port {port}" -- Specifies the port number of the
087 * directory server. If this isn't specified, then a default port of 389
088 * will be used.</LI>
089 * <LI>"-D {bindDN}" or "--bindDN {bindDN}" -- Specifies the DN to use to bind
090 * to the directory server using simple authentication. If this isn't
091 * specified, then simple authentication will not be performed.</LI>
092 * <LI>"-w {password}" or "--bindPassword {password}" -- Specifies the
093 * password to use when binding with simple authentication or a
094 * password-based SASL mechanism.</LI>
095 * <LI>"-j {path}" or "--bindPasswordFile {path}" -- Specifies the path to the
096 * file containing the password to use when binding with simple
097 * authentication or a password-based SASL mechanism.</LI>
098 * <LI>"--promptForBindPassword" -- Indicates that the tool should
099 * interactively prompt the user for the bind password.</LI>
100 * <LI>"-Z" or "--useSSL" -- Indicates that the communication with the server
101 * should be secured using SSL.</LI>
102 * <LI>"-q" or "--useStartTLS" -- Indicates that the communication with the
103 * server should be secured using StartTLS.</LI>
104 * <LI>"-X" or "--trustAll" -- Indicates that the client should trust any
105 * certificate that the server presents to it.</LI>
106 * <LI>"-K {path}" or "--keyStorePath {path}" -- Specifies the path to the
107 * key store to use to obtain client certificates.</LI>
108 * <LI>"-W {password}" or "--keyStorePassword {password}" -- Specifies the
109 * password to use to access the contents of the key store.</LI>
110 * <LI>"-u {path}" or "--keyStorePasswordFile {path}" -- Specifies the path to
111 * the file containing the password to use to access the contents of the
112 * key store.</LI>
113 * <LI>"--promptForKeyStorePassword" -- Indicates that the tool should
114 * interactively prompt the user for the key store password.</LI>
115 * <LI>"--keyStoreFormat {format}" -- Specifies the format to use for the key
116 * store file.</LI>
117 * <LI>"-P {path}" or "--trustStorePath {path}" -- Specifies the path to the
118 * trust store to use when determining whether to trust server
119 * certificates.</LI>
120 * <LI>"-T {password}" or "--trustStorePassword {password}" -- Specifies the
121 * password to use to access the contents of the trust store.</LI>
122 * <LI>"-U {path}" or "--trustStorePasswordFile {path}" -- Specifies the path
123 * to the file containing the password to use to access the contents of
124 * the trust store.</LI>
125 * <LI>"--promptForTrustStorePassword" -- Indicates that the tool should
126 * interactively prompt the user for the trust store password.</LI>
127 * <LI>"--trustStoreFormat {format}" -- Specifies the format to use for the
128 * trust store file.</LI>
129 * <LI>"-N {nickname}" or "--certNickname {nickname}" -- Specifies the
130 * nickname of the client certificate to use when performing SSL client
131 * authentication.</LI>
132 * <LI>"-o {name=value}" or "--saslOption {name=value}" -- Specifies a SASL
133 * option to use when performing SASL authentication.</LI>
134 * </UL>
135 * If SASL authentication is to be used, then a "mech" SASL option must be
136 * provided to specify the name of the SASL mechanism to use (e.g.,
137 * "--saslOption mech=EXTERNAL" indicates that the EXTERNAL mechanism should be
138 * used). Depending on the SASL mechanism, additional SASL options may be
139 * required or optional. They include:
140 * <UL>
141 * <LI>
142 * mech=ANONYMOUS
143 * <UL>
144 * <LI>Required SASL options: </LI>
145 * <LI>Optional SASL options: trace</LI>
146 * </UL>
147 * </LI>
148 * <LI>
149 * mech=CRAM-MD5
150 * <UL>
151 * <LI>Required SASL options: authID</LI>
152 * <LI>Optional SASL options: </LI>
153 * </UL>
154 * </LI>
155 * <LI>
156 * mech=DIGEST-MD5
157 * <UL>
158 * <LI>Required SASL options: authID</LI>
159 * <LI>Optional SASL options: authzID, realm</LI>
160 * </UL>
161 * </LI>
162 * <LI>
163 * mech=EXTERNAL
164 * <UL>
165 * <LI>Required SASL options: </LI>
166 * <LI>Optional SASL options: </LI>
167 * </UL>
168 * </LI>
169 * <LI>
170 * mech=GSSAPI
171 * <UL>
172 * <LI>Required SASL options: authID</LI>
173 * <LI>Optional SASL options: authzID, configFile, debug, protocol,
174 * realm, kdcAddress, useTicketCache, requireCache,
175 * renewTGT, ticketCachePath</LI>
176 * </UL>
177 * </LI>
178 * <LI>
179 * mech=PLAIN
180 * <UL>
181 * <LI>Required SASL options: authID</LI>
182 * <LI>Optional SASL options: authzID</LI>
183 * </UL>
184 * </LI>
185 * </UL>
186 * <BR><BR>
187 * Note that in general, methods in this class are not threadsafe. However, the
188 * {@link #getConnection()} and {@link #getConnectionPool(int,int)} methods may
189 * be invoked concurrently by multiple threads accessing the same instance only
190 * while that instance is in the process of invoking the
191 * {@link #doToolProcessing()} method.
192 */
193 @Extensible()
194 @ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
195 public abstract class LDAPCommandLineTool
196 extends CommandLineTool
197 {
198 // Arguments used to communicate with an LDAP directory server.
199 private BooleanArgument helpSASL = null;
200 private BooleanArgument promptForBindPassword = null;
201 private BooleanArgument promptForKeyStorePassword = null;
202 private BooleanArgument promptForTrustStorePassword = null;
203 private BooleanArgument trustAll = null;
204 private BooleanArgument useSASLExternal = null;
205 private BooleanArgument useSSL = null;
206 private BooleanArgument useStartTLS = null;
207 private DNArgument bindDN = null;
208 private FileArgument bindPasswordFile = null;
209 private FileArgument keyStorePasswordFile = null;
210 private FileArgument trustStorePasswordFile = null;
211 private IntegerArgument port = null;
212 private StringArgument bindPassword = null;
213 private StringArgument certificateNickname = null;
214 private StringArgument host = null;
215 private StringArgument keyStoreFormat = null;
216 private StringArgument keyStorePath = null;
217 private StringArgument keyStorePassword = null;
218 private StringArgument saslOption = null;
219 private StringArgument trustStoreFormat = null;
220 private StringArgument trustStorePath = null;
221 private StringArgument trustStorePassword = null;
222
223 // Variables used when creating and authenticating connections.
224 private BindRequest bindRequest = null;
225 private ServerSet serverSet = null;
226 private SSLSocketFactory startTLSSocketFactory = null;
227
228 // The prompt trust manager that will be shared by all connections created
229 // for which it is appropriate. This will allow them to benefit from the
230 // common cache.
231 private final AtomicReference<PromptTrustManager> promptTrustManager;
232
233
234
235 /**
236 * Creates a new instance of this LDAP-enabled command-line tool with the
237 * provided information.
238 *
239 * @param outStream The output stream to use for standard output. It may be
240 * {@code System.out} for the JVM's default standard output
241 * stream, {@code null} if no output should be generated,
242 * or a custom output stream if the output should be sent
243 * to an alternate location.
244 * @param errStream The output stream to use for standard error. It may be
245 * {@code System.err} for the JVM's default standard error
246 * stream, {@code null} if no output should be generated,
247 * or a custom output stream if the output should be sent
248 * to an alternate location.
249 */
250 public LDAPCommandLineTool(final OutputStream outStream,
251 final OutputStream errStream)
252 {
253 super(outStream, errStream);
254
255 promptTrustManager = new AtomicReference<PromptTrustManager>();
256 }
257
258
259
260 /**
261 * Retrieves a set containing the long identifiers used for LDAP-related
262 * arguments injected by this class.
263 *
264 * @param tool The tool to use to help make the determination.
265 *
266 * @return A set containing the long identifiers used for LDAP-related
267 * arguments injected by this class.
268 */
269 static Set<String> getLongLDAPArgumentIdentifiers(
270 final LDAPCommandLineTool tool)
271 {
272 final LinkedHashSet<String> ids = new LinkedHashSet<String>(21);
273
274 ids.add("hostname");
275 ids.add("port");
276
277 if (tool.supportsAuthentication())
278 {
279 ids.add("bindDN");
280 ids.add("bindPassword");
281 ids.add("bindPasswordFile");
282 ids.add("promptForBindPassword");
283 }
284
285 ids.add("useSSL");
286 ids.add("useStartTLS");
287 ids.add("trustAll");
288 ids.add("keyStorePath");
289 ids.add("keyStorePassword");
290 ids.add("keyStorePasswordFile");
291 ids.add("promptForKeyStorePassword");
292 ids.add("keyStoreFormat");
293 ids.add("trustStorePath");
294 ids.add("trustStorePassword");
295 ids.add("trustStorePasswordFile");
296 ids.add("promptForTrustStorePassword");
297 ids.add("trustStoreFormat");
298 ids.add("certNickname");
299
300 if (tool.supportsAuthentication())
301 {
302 ids.add("saslOption");
303 ids.add("useSASLExternal");
304 ids.add("helpSASL");
305 }
306
307 return Collections.unmodifiableSet(ids);
308 }
309
310
311
312 /**
313 * {@inheritDoc}
314 */
315 @Override()
316 public final void addToolArguments(final ArgumentParser parser)
317 throws ArgumentException
318 {
319 final String argumentGroup;
320 final boolean supportsAuthentication = supportsAuthentication();
321 if (supportsAuthentication)
322 {
323 argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT_AND_AUTH.get();
324 }
325 else
326 {
327 argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT.get();
328 }
329
330
331 host = new StringArgument('h', "hostname", true,
332 (supportsMultipleServers() ? 0 : 1),
333 INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
334 INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
335 host.setArgumentGroupName(argumentGroup);
336 parser.addArgument(host);
337
338 port = new IntegerArgument('p', "port", true,
339 (supportsMultipleServers() ? 0 : 1),
340 INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
341 INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389);
342 port.setArgumentGroupName(argumentGroup);
343 parser.addArgument(port);
344
345 if (supportsAuthentication)
346 {
347 bindDN = new DNArgument('D', "bindDN", false, 1,
348 INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
349 INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
350 bindDN.setArgumentGroupName(argumentGroup);
351 if (includeAlternateLongIdentifiers())
352 {
353 bindDN.addLongIdentifier("bind-dn");
354 }
355 parser.addArgument(bindDN);
356
357 bindPassword = new StringArgument('w', "bindPassword", false, 1,
358 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
359 INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
360 bindPassword.setSensitive(true);
361 bindPassword.setArgumentGroupName(argumentGroup);
362 if (includeAlternateLongIdentifiers())
363 {
364 bindPassword.addLongIdentifier("bind-password");
365 }
366 parser.addArgument(bindPassword);
367
368 bindPasswordFile = new FileArgument('j', "bindPasswordFile", false, 1,
369 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
370 INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
371 false);
372 bindPasswordFile.setArgumentGroupName(argumentGroup);
373 if (includeAlternateLongIdentifiers())
374 {
375 bindPasswordFile.addLongIdentifier("bind-password-file");
376 }
377 parser.addArgument(bindPasswordFile);
378
379 promptForBindPassword = new BooleanArgument(null, "promptForBindPassword",
380 1, INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_PROMPT.get());
381 promptForBindPassword.setArgumentGroupName(argumentGroup);
382 if (includeAlternateLongIdentifiers())
383 {
384 promptForBindPassword.addLongIdentifier("prompt-for-bind-password");
385 }
386 parser.addArgument(promptForBindPassword);
387 }
388
389 useSSL = new BooleanArgument('Z', "useSSL", 1,
390 INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get());
391 useSSL.setArgumentGroupName(argumentGroup);
392 if (includeAlternateLongIdentifiers())
393 {
394 useSSL.addLongIdentifier("use-ssl");
395 }
396 parser.addArgument(useSSL);
397
398 useStartTLS = new BooleanArgument('q', "useStartTLS", 1,
399 INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get());
400 useStartTLS.setArgumentGroupName(argumentGroup);
401 if (includeAlternateLongIdentifiers())
402 {
403 useStartTLS.addLongIdentifier("use-starttls");
404 useStartTLS.addLongIdentifier("use-start-tls");
405 }
406 parser.addArgument(useStartTLS);
407
408 trustAll = new BooleanArgument('X', "trustAll", 1,
409 INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get());
410 trustAll.setArgumentGroupName(argumentGroup);
411 if (includeAlternateLongIdentifiers())
412 {
413 trustAll.addLongIdentifier("trustAllCertificates");
414 trustAll.addLongIdentifier("trust-all");
415 trustAll.addLongIdentifier("trust-all-certificates");
416 }
417 parser.addArgument(trustAll);
418
419 keyStorePath = new StringArgument('K', "keyStorePath", false, 1,
420 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
421 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get());
422 keyStorePath.setArgumentGroupName(argumentGroup);
423 if (includeAlternateLongIdentifiers())
424 {
425 keyStorePath.addLongIdentifier("key-store-path");
426 }
427 parser.addArgument(keyStorePath);
428
429 keyStorePassword = new StringArgument('W', "keyStorePassword", false, 1,
430 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
431 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get());
432 keyStorePassword.setSensitive(true);
433 keyStorePassword.setArgumentGroupName(argumentGroup);
434 if (includeAlternateLongIdentifiers())
435 {
436 keyStorePassword.addLongIdentifier("keyStorePIN");
437 keyStorePassword.addLongIdentifier("key-store-password");
438 keyStorePassword.addLongIdentifier("key-store-pin");
439 }
440 parser.addArgument(keyStorePassword);
441
442 keyStorePasswordFile = new FileArgument('u', "keyStorePasswordFile", false,
443 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
444 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get());
445 keyStorePasswordFile.setArgumentGroupName(argumentGroup);
446 if (includeAlternateLongIdentifiers())
447 {
448 keyStorePasswordFile.addLongIdentifier("keyStorePINFile");
449 keyStorePasswordFile.addLongIdentifier("key-store-password-file");
450 keyStorePasswordFile.addLongIdentifier("key-store-pin-file");
451 }
452 parser.addArgument(keyStorePasswordFile);
453
454 promptForKeyStorePassword = new BooleanArgument(null,
455 "promptForKeyStorePassword", 1,
456 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_PROMPT.get());
457 promptForKeyStorePassword.setArgumentGroupName(argumentGroup);
458 if (includeAlternateLongIdentifiers())
459 {
460 promptForKeyStorePassword.addLongIdentifier("promptForKeyStorePIN");
461 promptForKeyStorePassword.addLongIdentifier(
462 "prompt-for-key-store-password");
463 promptForKeyStorePassword.addLongIdentifier("prompt-for-key-store-pin");
464 }
465 parser.addArgument(promptForKeyStorePassword);
466
467 keyStoreFormat = new StringArgument(null, "keyStoreFormat", false, 1,
468 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
469 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
470 keyStoreFormat.setArgumentGroupName(argumentGroup);
471 if (includeAlternateLongIdentifiers())
472 {
473 keyStoreFormat.addLongIdentifier("keyStoreType");
474 keyStoreFormat.addLongIdentifier("key-store-format");
475 keyStoreFormat.addLongIdentifier("key-store-type");
476 }
477 parser.addArgument(keyStoreFormat);
478
479 trustStorePath = new StringArgument('P', "trustStorePath", false, 1,
480 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
481 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
482 trustStorePath.setArgumentGroupName(argumentGroup);
483 if (includeAlternateLongIdentifiers())
484 {
485 trustStorePath.addLongIdentifier("trust-store-path");
486 }
487 parser.addArgument(trustStorePath);
488
489 trustStorePassword = new StringArgument('T', "trustStorePassword", false, 1,
490 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
491 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
492 trustStorePassword.setSensitive(true);
493 trustStorePassword.setArgumentGroupName(argumentGroup);
494 if (includeAlternateLongIdentifiers())
495 {
496 trustStorePassword.addLongIdentifier("trustStorePIN");
497 trustStorePassword.addLongIdentifier("trust-store-password");
498 trustStorePassword.addLongIdentifier("trust-store-pin");
499 }
500 parser.addArgument(trustStorePassword);
501
502 trustStorePasswordFile = new FileArgument('U', "trustStorePasswordFile",
503 false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
504 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get());
505 trustStorePasswordFile.setArgumentGroupName(argumentGroup);
506 if (includeAlternateLongIdentifiers())
507 {
508 trustStorePasswordFile.addLongIdentifier("trustStorePINFile");
509 trustStorePasswordFile.addLongIdentifier("trust-store-password-file");
510 trustStorePasswordFile.addLongIdentifier("trust-store-pin-file");
511 }
512 parser.addArgument(trustStorePasswordFile);
513
514 promptForTrustStorePassword = new BooleanArgument(null,
515 "promptForTrustStorePassword", 1,
516 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_PROMPT.get());
517 promptForTrustStorePassword.setArgumentGroupName(argumentGroup);
518 if (includeAlternateLongIdentifiers())
519 {
520 promptForTrustStorePassword.addLongIdentifier("promptForTrustStorePIN");
521 promptForTrustStorePassword.addLongIdentifier(
522 "prompt-for-trust-store-password");
523 promptForTrustStorePassword.addLongIdentifier(
524 "prompt-for-trust-store-pin");
525 }
526 parser.addArgument(promptForTrustStorePassword);
527
528 trustStoreFormat = new StringArgument(null, "trustStoreFormat", false, 1,
529 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
530 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
531 trustStoreFormat.setArgumentGroupName(argumentGroup);
532 if (includeAlternateLongIdentifiers())
533 {
534 trustStoreFormat.addLongIdentifier("trustStoreType");
535 trustStoreFormat.addLongIdentifier("trust-store-format");
536 trustStoreFormat.addLongIdentifier("trust-store-type");
537 }
538 parser.addArgument(trustStoreFormat);
539
540 certificateNickname = new StringArgument('N', "certNickname", false, 1,
541 INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
542 INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
543 certificateNickname.setArgumentGroupName(argumentGroup);
544 if (includeAlternateLongIdentifiers())
545 {
546 certificateNickname.addLongIdentifier("certificateNickname");
547 certificateNickname.addLongIdentifier("cert-nickname");
548 certificateNickname.addLongIdentifier("certificate-nickname");
549 }
550 parser.addArgument(certificateNickname);
551
552 if (supportsAuthentication)
553 {
554 saslOption = new StringArgument('o', "saslOption", false, 0,
555 INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
556 INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
557 saslOption.setArgumentGroupName(argumentGroup);
558 if (includeAlternateLongIdentifiers())
559 {
560 saslOption.addLongIdentifier("sasl-option");
561 }
562 parser.addArgument(saslOption);
563
564 useSASLExternal = new BooleanArgument(null, "useSASLExternal", 1,
565 INFO_LDAP_TOOL_DESCRIPTION_USE_SASL_EXTERNAL.get());
566 useSASLExternal.setArgumentGroupName(argumentGroup);
567 if (includeAlternateLongIdentifiers())
568 {
569 useSASLExternal.addLongIdentifier("use-sasl-external");
570 }
571 parser.addArgument(useSASLExternal);
572
573 if (supportsSASLHelp())
574 {
575 helpSASL = new BooleanArgument(null, "helpSASL",
576 INFO_LDAP_TOOL_DESCRIPTION_HELP_SASL.get());
577 helpSASL.setArgumentGroupName(argumentGroup);
578 if (includeAlternateLongIdentifiers())
579 {
580 helpSASL.addLongIdentifier("help-sasl");
581 }
582 helpSASL.setUsageArgument(true);
583 parser.addArgument(helpSASL);
584 setHelpSASLArgument(helpSASL);
585 }
586 }
587
588
589 // Both useSSL and useStartTLS cannot be used together.
590 parser.addExclusiveArgumentSet(useSSL, useStartTLS);
591
592 // Only one option may be used for specifying the key store password.
593 parser.addExclusiveArgumentSet(keyStorePassword, keyStorePasswordFile,
594 promptForKeyStorePassword);
595
596 // Only one option may be used for specifying the trust store password.
597 parser.addExclusiveArgumentSet(trustStorePassword, trustStorePasswordFile,
598 promptForTrustStorePassword);
599
600 // It doesn't make sense to provide a trust store path if any server
601 // certificate should be trusted.
602 parser.addExclusiveArgumentSet(trustAll, trustStorePath);
603
604 // If a key store password is provided, then a key store path must have also
605 // been provided.
606 parser.addDependentArgumentSet(keyStorePassword, keyStorePath);
607 parser.addDependentArgumentSet(keyStorePasswordFile, keyStorePath);
608 parser.addDependentArgumentSet(promptForKeyStorePassword, keyStorePath);
609
610 // If a trust store password is provided, then a trust store path must have
611 // also been provided.
612 parser.addDependentArgumentSet(trustStorePassword, trustStorePath);
613 parser.addDependentArgumentSet(trustStorePasswordFile, trustStorePath);
614 parser.addDependentArgumentSet(promptForTrustStorePassword, trustStorePath);
615
616 // If a key or trust store path is provided, then the tool must either use
617 // SSL or StartTLS.
618 parser.addDependentArgumentSet(keyStorePath, useSSL, useStartTLS);
619 parser.addDependentArgumentSet(trustStorePath, useSSL, useStartTLS);
620
621 // If the tool should trust all server certificates, then the tool must
622 // either use SSL or StartTLS.
623 parser.addDependentArgumentSet(trustAll, useSSL, useStartTLS);
624
625 if (supportsAuthentication)
626 {
627 // If a bind DN was provided, then a bind password must have also been
628 // provided unless defaultToPromptForBindPassword returns true.
629 if (! defaultToPromptForBindPassword())
630 {
631 parser.addDependentArgumentSet(bindDN, bindPassword, bindPasswordFile,
632 promptForBindPassword);
633 }
634
635 // The bindDN, saslOption, and useSASLExternal arguments are all mutually
636 // exclusive.
637 parser.addExclusiveArgumentSet(bindDN, saslOption, useSASLExternal);
638
639 // Only one option may be used for specifying the bind password.
640 parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile,
641 promptForBindPassword);
642
643 // If a bind password was provided, then the a bind DN or SASL option
644 // must have also been provided.
645 parser.addDependentArgumentSet(bindPassword, bindDN, saslOption);
646 parser.addDependentArgumentSet(bindPasswordFile, bindDN, saslOption);
647 parser.addDependentArgumentSet(promptForBindPassword, bindDN, saslOption);
648 }
649
650 addNonLDAPArguments(parser);
651 }
652
653
654
655 /**
656 * Adds the arguments needed by this command-line tool to the provided
657 * argument parser which are not related to connecting or authenticating to
658 * the directory server.
659 *
660 * @param parser The argument parser to which the arguments should be added.
661 *
662 * @throws ArgumentException If a problem occurs while adding the arguments.
663 */
664 public abstract void addNonLDAPArguments(final ArgumentParser parser)
665 throws ArgumentException;
666
667
668
669 /**
670 * {@inheritDoc}
671 */
672 @Override()
673 public final void doExtendedArgumentValidation()
674 throws ArgumentException
675 {
676 // If more than one hostname or port number was provided, then make sure
677 // that the same number of values were provided for each.
678 if ((host.getValues().size() > 1) || (port.getValues().size() > 1))
679 {
680 if (host.getValues().size() != port.getValues().size())
681 {
682 throw new ArgumentException(
683 ERR_LDAP_TOOL_HOST_PORT_COUNT_MISMATCH.get(
684 host.getLongIdentifier(), port.getLongIdentifier()));
685 }
686 }
687
688
689 doExtendedNonLDAPArgumentValidation();
690 }
691
692
693
694 /**
695 * Indicates whether this tool should provide the arguments that allow it to
696 * bind via simple or SASL authentication.
697 *
698 * @return {@code true} if this tool should provide the arguments that allow
699 * it to bind via simple or SASL authentication, or {@code false} if
700 * not.
701 */
702 protected boolean supportsAuthentication()
703 {
704 return true;
705 }
706
707
708
709 /**
710 * Indicates whether this tool should default to interactively prompting for
711 * the bind password if a password is required but no argument was provided
712 * to indicate how to get the password.
713 *
714 * @return {@code true} if this tool should default to interactively
715 * prompting for the bind password, or {@code false} if not.
716 */
717 protected boolean defaultToPromptForBindPassword()
718 {
719 return false;
720 }
721
722
723
724 /**
725 * Indicates whether this tool should provide a "--help-sasl" argument that
726 * provides information about the supported SASL mechanisms and their
727 * associated properties.
728 *
729 * @return {@code true} if this tool should provide a "--help-sasl" argument,
730 * or {@code false} if not.
731 */
732 protected boolean supportsSASLHelp()
733 {
734 return true;
735 }
736
737
738
739 /**
740 * Indicates whether the LDAP-specific arguments should include alternate
741 * versions of all long identifiers that consist of multiple words so that
742 * they are available in both camelCase and dash-separated versions.
743 *
744 * @return {@code true} if this tool should provide multiple versions of
745 * long identifiers for LDAP-specific arguments, or {@code false} if
746 * not.
747 */
748 protected boolean includeAlternateLongIdentifiers()
749 {
750 return false;
751 }
752
753
754
755 /**
756 * Retrieves a set of controls that should be included in any bind request
757 * generated by this tool.
758 *
759 * @return A set of controls that should be included in any bind request
760 * generated by this tool. It may be {@code null} or empty if no
761 * controls should be included in the bind request.
762 */
763 protected List<Control> getBindControls()
764 {
765 return null;
766 }
767
768
769
770 /**
771 * Indicates whether this tool supports creating connections to multiple
772 * servers. If it is to support multiple servers, then the "--hostname" and
773 * "--port" arguments will be allowed to be provided multiple times, and
774 * will be required to be provided the same number of times. The same type of
775 * communication security and bind credentials will be used for all servers.
776 *
777 * @return {@code true} if this tool supports creating connections to
778 * multiple servers, or {@code false} if not.
779 */
780 protected boolean supportsMultipleServers()
781 {
782 return false;
783 }
784
785
786
787 /**
788 * Performs any necessary processing that should be done to ensure that the
789 * provided set of command-line arguments were valid. This method will be
790 * called after the basic argument parsing has been performed and after all
791 * LDAP-specific argument validation has been processed, and immediately
792 * before the {@link CommandLineTool#doToolProcessing} method is invoked.
793 *
794 * @throws ArgumentException If there was a problem with the command-line
795 * arguments provided to this program.
796 */
797 public void doExtendedNonLDAPArgumentValidation()
798 throws ArgumentException
799 {
800 // No processing will be performed by default.
801 }
802
803
804
805 /**
806 * Retrieves the connection options that should be used for connections that
807 * are created with this command line tool. Subclasses may override this
808 * method to use a custom set of connection options.
809 *
810 * @return The connection options that should be used for connections that
811 * are created with this command line tool.
812 */
813 public LDAPConnectionOptions getConnectionOptions()
814 {
815 return new LDAPConnectionOptions();
816 }
817
818
819
820 /**
821 * Retrieves a connection that may be used to communicate with the target
822 * directory server.
823 * <BR><BR>
824 * Note that this method is threadsafe and may be invoked by multiple threads
825 * accessing the same instance only while that instance is in the process of
826 * invoking the {@link #doToolProcessing} method.
827 *
828 * @return A connection that may be used to communicate with the target
829 * directory server.
830 *
831 * @throws LDAPException If a problem occurs while creating the connection.
832 */
833 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
834 public final LDAPConnection getConnection()
835 throws LDAPException
836 {
837 final LDAPConnection connection = getUnauthenticatedConnection();
838
839 try
840 {
841 if (bindRequest != null)
842 {
843 connection.bind(bindRequest);
844 }
845 }
846 catch (LDAPException le)
847 {
848 debugException(le);
849 connection.close();
850 throw le;
851 }
852
853 return connection;
854 }
855
856
857
858 /**
859 * Retrieves an unauthenticated connection that may be used to communicate
860 * with the target directory server.
861 * <BR><BR>
862 * Note that this method is threadsafe and may be invoked by multiple threads
863 * accessing the same instance only while that instance is in the process of
864 * invoking the {@link #doToolProcessing} method.
865 *
866 * @return An unauthenticated connection that may be used to communicate with
867 * the target directory server.
868 *
869 * @throws LDAPException If a problem occurs while creating the connection.
870 */
871 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
872 public final LDAPConnection getUnauthenticatedConnection()
873 throws LDAPException
874 {
875 if (serverSet == null)
876 {
877 serverSet = createServerSet();
878 bindRequest = createBindRequest();
879 }
880
881 final LDAPConnection connection = serverSet.getConnection();
882
883 if (useStartTLS.isPresent())
884 {
885 try
886 {
887 final ExtendedResult extendedResult =
888 connection.processExtendedOperation(
889 new StartTLSExtendedRequest(startTLSSocketFactory));
890 if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
891 {
892 throw new LDAPException(extendedResult.getResultCode(),
893 ERR_LDAP_TOOL_START_TLS_FAILED.get(
894 extendedResult.getDiagnosticMessage()));
895 }
896 }
897 catch (LDAPException le)
898 {
899 debugException(le);
900 connection.close();
901 throw le;
902 }
903 }
904
905 return connection;
906 }
907
908
909
910 /**
911 * Retrieves a connection pool that may be used to communicate with the target
912 * directory server.
913 * <BR><BR>
914 * Note that this method is threadsafe and may be invoked by multiple threads
915 * accessing the same instance only while that instance is in the process of
916 * invoking the {@link #doToolProcessing} method.
917 *
918 * @param initialConnections The number of connections that should be
919 * initially established in the pool.
920 * @param maxConnections The maximum number of connections to maintain
921 * in the pool.
922 *
923 * @return A connection that may be used to communicate with the target
924 * directory server.
925 *
926 * @throws LDAPException If a problem occurs while creating the connection
927 * pool.
928 */
929 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
930 public final LDAPConnectionPool getConnectionPool(
931 final int initialConnections,
932 final int maxConnections)
933 throws LDAPException
934 {
935 return getConnectionPool(initialConnections, maxConnections, 1, null, null,
936 true, null);
937 }
938
939
940
941 /**
942 * Retrieves a connection pool that may be used to communicate with the target
943 * directory server.
944 * <BR><BR>
945 * Note that this method is threadsafe and may be invoked by multiple threads
946 * accessing the same instance only while that instance is in the process of
947 * invoking the {@link #doToolProcessing} method.
948 *
949 * @param initialConnections The number of connections that should be
950 * initially established in the pool.
951 * @param maxConnections The maximum number of connections to
952 * maintain in the pool.
953 * @param initialConnectThreads The number of concurrent threads to use to
954 * establish the initial set of connections.
955 * A value greater than one indicates that
956 * the attempt to establish connections
957 * should be parallelized.
958 * @param beforeStartTLSProcessor An optional post-connect processor that
959 * should be used for the connection pool and
960 * should be invoked before any StartTLS
961 * post-connect processor that may be needed
962 * based on the selected arguments. It may
963 * be {@code null} if no such post-connect
964 * processor is needed.
965 * @param afterStartTLSProcessor An optional post-connect processor that
966 * should be used for the connection pool and
967 * should be invoked after any StartTLS
968 * post-connect processor that may be needed
969 * based on the selected arguments. It may
970 * be {@code null} if no such post-connect
971 * processor is needed.
972 * @param throwOnConnectFailure If an exception should be thrown if a
973 * problem is encountered while attempting to
974 * create the specified initial number of
975 * connections. If {@code true}, then the
976 * attempt to create the pool will fail if
977 * any connection cannot be established. If
978 * {@code false}, then the pool will be
979 * created but may have fewer than the
980 * initial number of connections (or possibly
981 * no connections).
982 * @param healthCheck An optional health check that should be
983 * configured for the connection pool. It
984 * may be {@code null} if the default health
985 * checking should be performed.
986 *
987 * @return A connection that may be used to communicate with the target
988 * directory server.
989 *
990 * @throws LDAPException If a problem occurs while creating the connection
991 * pool.
992 */
993 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
994 public final LDAPConnectionPool getConnectionPool(
995 final int initialConnections, final int maxConnections,
996 final int initialConnectThreads,
997 final PostConnectProcessor beforeStartTLSProcessor,
998 final PostConnectProcessor afterStartTLSProcessor,
999 final boolean throwOnConnectFailure,
1000 final LDAPConnectionPoolHealthCheck healthCheck)
1001 throws LDAPException
1002 {
1003 // Create the server set and bind request, if necessary.
1004 if (serverSet == null)
1005 {
1006 serverSet = createServerSet();
1007 bindRequest = createBindRequest();
1008 }
1009
1010
1011 // Prepare the post-connect processor for the pool.
1012 final ArrayList<PostConnectProcessor> pcpList =
1013 new ArrayList<PostConnectProcessor>(3);
1014 if (beforeStartTLSProcessor != null)
1015 {
1016 pcpList.add(beforeStartTLSProcessor);
1017 }
1018
1019 if (useStartTLS.isPresent())
1020 {
1021 pcpList.add(new StartTLSPostConnectProcessor(startTLSSocketFactory));
1022 }
1023
1024 if (afterStartTLSProcessor != null)
1025 {
1026 pcpList.add(afterStartTLSProcessor);
1027 }
1028
1029 final PostConnectProcessor postConnectProcessor;
1030 switch (pcpList.size())
1031 {
1032 case 0:
1033 postConnectProcessor = null;
1034 break;
1035 case 1:
1036 postConnectProcessor = pcpList.get(0);
1037 break;
1038 default:
1039 postConnectProcessor = new AggregatePostConnectProcessor(pcpList);
1040 break;
1041 }
1042
1043 return new LDAPConnectionPool(serverSet, bindRequest, initialConnections,
1044 maxConnections, initialConnectThreads, postConnectProcessor,
1045 throwOnConnectFailure, healthCheck);
1046 }
1047
1048
1049
1050 /**
1051 * Creates the server set to use when creating connections or connection
1052 * pools.
1053 *
1054 * @return The server set to use when creating connections or connection
1055 * pools.
1056 *
1057 * @throws LDAPException If a problem occurs while creating the server set.
1058 */
1059 public ServerSet createServerSet()
1060 throws LDAPException
1061 {
1062 final SSLUtil sslUtil = createSSLUtil();
1063
1064 SocketFactory socketFactory = null;
1065 if (useSSL.isPresent())
1066 {
1067 try
1068 {
1069 socketFactory = sslUtil.createSSLSocketFactory();
1070 }
1071 catch (Exception e)
1072 {
1073 debugException(e);
1074 throw new LDAPException(ResultCode.LOCAL_ERROR,
1075 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1076 getExceptionMessage(e)), e);
1077 }
1078 }
1079 else if (useStartTLS.isPresent())
1080 {
1081 try
1082 {
1083 startTLSSocketFactory = sslUtil.createSSLSocketFactory();
1084 }
1085 catch (Exception e)
1086 {
1087 debugException(e);
1088 throw new LDAPException(ResultCode.LOCAL_ERROR,
1089 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1090 getExceptionMessage(e)), e);
1091 }
1092 }
1093
1094 if (host.getValues().size() == 1)
1095 {
1096 return new SingleServerSet(host.getValue(), port.getValue(),
1097 socketFactory, getConnectionOptions());
1098 }
1099 else
1100 {
1101 final List<String> hostList = host.getValues();
1102 final List<Integer> portList = port.getValues();
1103
1104 final String[] hosts = new String[hostList.size()];
1105 final int[] ports = new int[hosts.length];
1106
1107 for (int i=0; i < hosts.length; i++)
1108 {
1109 hosts[i] = hostList.get(i);
1110 ports[i] = portList.get(i);
1111 }
1112
1113 return new RoundRobinServerSet(hosts, ports, socketFactory,
1114 getConnectionOptions());
1115 }
1116 }
1117
1118
1119
1120 /**
1121 * Creates the SSLUtil instance to use for secure communication.
1122 *
1123 * @return The SSLUtil instance to use for secure communication, or
1124 * {@code null} if secure communication is not needed.
1125 *
1126 * @throws LDAPException If a problem occurs while creating the SSLUtil
1127 * instance.
1128 */
1129 public SSLUtil createSSLUtil()
1130 throws LDAPException
1131 {
1132 return createSSLUtil(false);
1133 }
1134
1135
1136
1137 /**
1138 * Creates the SSLUtil instance to use for secure communication.
1139 *
1140 * @param force Indicates whether to create the SSLUtil object even if
1141 * neither the "--useSSL" nor the "--useStartTLS" argument was
1142 * provided. The key store and/or trust store paths must still
1143 * have been provided. This may be useful for tools that
1144 * accept SSL-based communication but do not themselves intend
1145 * to perform SSL-based communication as an LDAP client.
1146 *
1147 * @return The SSLUtil instance to use for secure communication, or
1148 * {@code null} if secure communication is not needed.
1149 *
1150 * @throws LDAPException If a problem occurs while creating the SSLUtil
1151 * instance.
1152 */
1153 public SSLUtil createSSLUtil(final boolean force)
1154 throws LDAPException
1155 {
1156 if (force || useSSL.isPresent() || useStartTLS.isPresent())
1157 {
1158 KeyManager keyManager = null;
1159 if (keyStorePath.isPresent())
1160 {
1161 char[] pw = null;
1162 if (keyStorePassword.isPresent())
1163 {
1164 pw = keyStorePassword.getValue().toCharArray();
1165 }
1166 else if (keyStorePasswordFile.isPresent())
1167 {
1168 try
1169 {
1170 pw = keyStorePasswordFile.getNonBlankFileLines().get(0).
1171 toCharArray();
1172 }
1173 catch (Exception e)
1174 {
1175 debugException(e);
1176 throw new LDAPException(ResultCode.LOCAL_ERROR,
1177 ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
1178 getExceptionMessage(e)), e);
1179 }
1180 }
1181 else if (promptForKeyStorePassword.isPresent())
1182 {
1183 getOut().print(INFO_LDAP_TOOL_ENTER_KEY_STORE_PASSWORD.get());
1184 pw = StaticUtils.toUTF8String(
1185 PasswordReader.readPassword()).toCharArray();
1186 getOut().println();
1187 }
1188
1189 try
1190 {
1191 keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw,
1192 keyStoreFormat.getValue(), certificateNickname.getValue());
1193 }
1194 catch (Exception e)
1195 {
1196 debugException(e);
1197 throw new LDAPException(ResultCode.LOCAL_ERROR,
1198 ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
1199 getExceptionMessage(e)), e);
1200 }
1201 }
1202
1203 TrustManager trustManager;
1204 if (trustAll.isPresent())
1205 {
1206 trustManager = new TrustAllTrustManager(false);
1207 }
1208 else if (trustStorePath.isPresent())
1209 {
1210 char[] pw = null;
1211 if (trustStorePassword.isPresent())
1212 {
1213 pw = trustStorePassword.getValue().toCharArray();
1214 }
1215 else if (trustStorePasswordFile.isPresent())
1216 {
1217 try
1218 {
1219 pw = trustStorePasswordFile.getNonBlankFileLines().get(0).
1220 toCharArray();
1221 }
1222 catch (Exception e)
1223 {
1224 debugException(e);
1225 throw new LDAPException(ResultCode.LOCAL_ERROR,
1226 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
1227 getExceptionMessage(e)), e);
1228 }
1229 }
1230 else if (promptForTrustStorePassword.isPresent())
1231 {
1232 getOut().print(INFO_LDAP_TOOL_ENTER_TRUST_STORE_PASSWORD.get());
1233 pw = StaticUtils.toUTF8String(
1234 PasswordReader.readPassword()).toCharArray();
1235 getOut().println();
1236 }
1237
1238 trustManager = new TrustStoreTrustManager(trustStorePath.getValue(), pw,
1239 trustStoreFormat.getValue(), true);
1240 }
1241 else
1242 {
1243 trustManager = promptTrustManager.get();
1244 if (trustManager == null)
1245 {
1246 final PromptTrustManager m = new PromptTrustManager();
1247 promptTrustManager.compareAndSet(null, m);
1248 trustManager = promptTrustManager.get();
1249 }
1250 }
1251
1252 return new SSLUtil(keyManager, trustManager);
1253 }
1254 else
1255 {
1256 return null;
1257 }
1258 }
1259
1260
1261
1262 /**
1263 * Creates the bind request to use to authenticate to the server.
1264 *
1265 * @return The bind request to use to authenticate to the server, or
1266 * {@code null} if no bind should be performed.
1267 *
1268 * @throws LDAPException If a problem occurs while creating the bind
1269 * request.
1270 */
1271 public BindRequest createBindRequest()
1272 throws LDAPException
1273 {
1274 if (! supportsAuthentication())
1275 {
1276 return null;
1277 }
1278
1279 final Control[] bindControls;
1280 final List<Control> bindControlList = getBindControls();
1281 if ((bindControlList == null) || bindControlList.isEmpty())
1282 {
1283 bindControls = NO_CONTROLS;
1284 }
1285 else
1286 {
1287 bindControls = new Control[bindControlList.size()];
1288 bindControlList.toArray(bindControls);
1289 }
1290
1291 byte[] pw;
1292 if (bindPassword.isPresent())
1293 {
1294 pw = StaticUtils.getBytes(bindPassword.getValue());
1295 }
1296 else if (bindPasswordFile.isPresent())
1297 {
1298 try
1299 {
1300 pw = StaticUtils.getBytes(
1301 bindPasswordFile.getNonBlankFileLines().get(0));
1302 }
1303 catch (Exception e)
1304 {
1305 debugException(e);
1306 throw new LDAPException(ResultCode.LOCAL_ERROR,
1307 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
1308 getExceptionMessage(e)), e);
1309 }
1310 }
1311 else if (promptForBindPassword.isPresent())
1312 {
1313 getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1314 pw = PasswordReader.readPassword();
1315 getOriginalOut().println();
1316 }
1317 else
1318 {
1319 pw = null;
1320 }
1321
1322 if (saslOption.isPresent())
1323 {
1324 final String dnStr;
1325 if (bindDN.isPresent())
1326 {
1327 dnStr = bindDN.getValue().toString();
1328 }
1329 else
1330 {
1331 dnStr = null;
1332 }
1333
1334 return SASLUtils.createBindRequest(dnStr, pw,
1335 defaultToPromptForBindPassword(), this, null,
1336 saslOption.getValues(), bindControls);
1337 }
1338 else if (useSASLExternal.isPresent())
1339 {
1340 return new EXTERNALBindRequest(bindControls);
1341 }
1342 else if (bindDN.isPresent())
1343 {
1344 if ((pw == null) && (! bindDN.getValue().isNullDN()) &&
1345 defaultToPromptForBindPassword())
1346 {
1347 getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1348 pw = PasswordReader.readPassword();
1349 getOriginalOut().println();
1350 }
1351
1352 return new SimpleBindRequest(bindDN.getValue(), pw, bindControls);
1353 }
1354 else
1355 {
1356 return null;
1357 }
1358 }
1359 }