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