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 includeAuthenticationArgs Indicates whether to include
263 * authentication-related arguments.
264 *
265 * @return A set containing the long identifiers used for LDAP-related
266 * arguments injected by this class.
267 */
268 static Set<String> getLongLDAPArgumentIdentifiers(
269 final boolean includeAuthenticationArgs)
270 {
271 final LinkedHashSet<String> ids = new LinkedHashSet<String>(21);
272
273 ids.add("hostname");
274 ids.add("port");
275
276 if (includeAuthenticationArgs)
277 {
278 ids.add("bindDN");
279 ids.add("bindPassword");
280 ids.add("bindPasswordFile");
281 ids.add("promptForBindPassword");
282 }
283
284 ids.add("useSSL");
285 ids.add("useStartTLS");
286 ids.add("trustAll");
287 ids.add("keyStorePath");
288 ids.add("keyStorePassword");
289 ids.add("keyStorePasswordFile");
290 ids.add("promptForKeyStorePassword");
291 ids.add("keyStoreFormat");
292 ids.add("trustStorePath");
293 ids.add("trustStorePassword");
294 ids.add("trustStorePasswordFile");
295 ids.add("promptForTrustStorePassword");
296 ids.add("trustStoreFormat");
297 ids.add("certNickname");
298
299 if (includeAuthenticationArgs)
300 {
301 ids.add("saslOption");
302 ids.add("helpSASL");
303 }
304
305 return Collections.unmodifiableSet(ids);
306 }
307
308
309
310 /**
311 * {@inheritDoc}
312 */
313 @Override()
314 public final void addToolArguments(final ArgumentParser parser)
315 throws ArgumentException
316 {
317 final String argumentGroup;
318 final boolean supportsAuthentication = supportsAuthentication();
319 if (supportsAuthentication)
320 {
321 argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT_AND_AUTH.get();
322 }
323 else
324 {
325 argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT.get();
326 }
327
328
329 host = new StringArgument('h', "hostname", true,
330 (supportsMultipleServers() ? 0 : 1),
331 INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
332 INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
333 host.setArgumentGroupName(argumentGroup);
334 parser.addArgument(host);
335
336 port = new IntegerArgument('p', "port", true,
337 (supportsMultipleServers() ? 0 : 1),
338 INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
339 INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389);
340 port.setArgumentGroupName(argumentGroup);
341 parser.addArgument(port);
342
343 if (supportsAuthentication)
344 {
345 bindDN = new DNArgument('D', "bindDN", false, 1,
346 INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
347 INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
348 bindDN.setArgumentGroupName(argumentGroup);
349 if (includeAlternateLongIdentifiers())
350 {
351 bindDN.addLongIdentifier("bind-dn");
352 }
353 parser.addArgument(bindDN);
354
355 bindPassword = new StringArgument('w', "bindPassword", false, 1,
356 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
357 INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
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.setArgumentGroupName(argumentGroup);
430 if (includeAlternateLongIdentifiers())
431 {
432 keyStorePassword.addLongIdentifier("keyStorePIN");
433 keyStorePassword.addLongIdentifier("key-store-password");
434 keyStorePassword.addLongIdentifier("key-store-pin");
435 }
436 parser.addArgument(keyStorePassword);
437
438 keyStorePasswordFile = new FileArgument('u', "keyStorePasswordFile", false,
439 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
440 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get());
441 keyStorePasswordFile.setArgumentGroupName(argumentGroup);
442 if (includeAlternateLongIdentifiers())
443 {
444 keyStorePasswordFile.addLongIdentifier("keyStorePINFile");
445 keyStorePasswordFile.addLongIdentifier("key-store-password-file");
446 keyStorePasswordFile.addLongIdentifier("key-store-pin-file");
447 }
448 parser.addArgument(keyStorePasswordFile);
449
450 promptForKeyStorePassword = new BooleanArgument(null,
451 "promptForKeyStorePassword", 1,
452 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_PROMPT.get());
453 promptForKeyStorePassword.setArgumentGroupName(argumentGroup);
454 if (includeAlternateLongIdentifiers())
455 {
456 promptForKeyStorePassword.addLongIdentifier("promptForKeyStorePIN");
457 promptForKeyStorePassword.addLongIdentifier(
458 "prompt-for-key-store-password");
459 promptForKeyStorePassword.addLongIdentifier("prompt-for-key-store-pin");
460 }
461 parser.addArgument(promptForKeyStorePassword);
462
463 keyStoreFormat = new StringArgument(null, "keyStoreFormat", false, 1,
464 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
465 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
466 keyStoreFormat.setArgumentGroupName(argumentGroup);
467 if (includeAlternateLongIdentifiers())
468 {
469 keyStoreFormat.addLongIdentifier("keyStoreType");
470 keyStoreFormat.addLongIdentifier("key-store-format");
471 keyStoreFormat.addLongIdentifier("key-store-type");
472 }
473 parser.addArgument(keyStoreFormat);
474
475 trustStorePath = new StringArgument('P', "trustStorePath", false, 1,
476 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
477 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
478 trustStorePath.setArgumentGroupName(argumentGroup);
479 if (includeAlternateLongIdentifiers())
480 {
481 trustStorePath.addLongIdentifier("trust-store-path");
482 }
483 parser.addArgument(trustStorePath);
484
485 trustStorePassword = new StringArgument('T', "trustStorePassword", false, 1,
486 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
487 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
488 trustStorePassword.setArgumentGroupName(argumentGroup);
489 if (includeAlternateLongIdentifiers())
490 {
491 trustStorePassword.addLongIdentifier("trustStorePIN");
492 trustStorePassword.addLongIdentifier("trust-store-password");
493 trustStorePassword.addLongIdentifier("trust-store-pin");
494 }
495 parser.addArgument(trustStorePassword);
496
497 trustStorePasswordFile = new FileArgument('U', "trustStorePasswordFile",
498 false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
499 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get());
500 trustStorePasswordFile.setArgumentGroupName(argumentGroup);
501 if (includeAlternateLongIdentifiers())
502 {
503 trustStorePasswordFile.addLongIdentifier("trustStorePINFile");
504 trustStorePasswordFile.addLongIdentifier("trust-store-password-file");
505 trustStorePasswordFile.addLongIdentifier("trust-store-pin-file");
506 }
507 parser.addArgument(trustStorePasswordFile);
508
509 promptForTrustStorePassword = new BooleanArgument(null,
510 "promptForTrustStorePassword", 1,
511 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_PROMPT.get());
512 promptForTrustStorePassword.setArgumentGroupName(argumentGroup);
513 if (includeAlternateLongIdentifiers())
514 {
515 promptForTrustStorePassword.addLongIdentifier("promptForTrustStorePIN");
516 promptForTrustStorePassword.addLongIdentifier(
517 "prompt-for-trust-store-password");
518 promptForTrustStorePassword.addLongIdentifier(
519 "prompt-for-trust-store-pin");
520 }
521 parser.addArgument(promptForTrustStorePassword);
522
523 trustStoreFormat = new StringArgument(null, "trustStoreFormat", false, 1,
524 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
525 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
526 trustStoreFormat.setArgumentGroupName(argumentGroup);
527 if (includeAlternateLongIdentifiers())
528 {
529 trustStoreFormat.addLongIdentifier("trustStoreType");
530 trustStoreFormat.addLongIdentifier("trust-store-format");
531 trustStoreFormat.addLongIdentifier("trust-store-type");
532 }
533 parser.addArgument(trustStoreFormat);
534
535 certificateNickname = new StringArgument('N', "certNickname", false, 1,
536 INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
537 INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
538 certificateNickname.setArgumentGroupName(argumentGroup);
539 if (includeAlternateLongIdentifiers())
540 {
541 certificateNickname.addLongIdentifier("certificate-nickname");
542 }
543 parser.addArgument(certificateNickname);
544
545 if (supportsAuthentication)
546 {
547 saslOption = new StringArgument('o', "saslOption", false, 0,
548 INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
549 INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
550 saslOption.setArgumentGroupName(argumentGroup);
551 if (includeAlternateLongIdentifiers())
552 {
553 saslOption.addLongIdentifier("sasl-option");
554 }
555 parser.addArgument(saslOption);
556
557 if (supportsSASLHelp())
558 {
559 helpSASL = new BooleanArgument(null, "helpSASL",
560 INFO_LDAP_TOOL_DESCRIPTION_HELP_SASL.get());
561 helpSASL.setArgumentGroupName(argumentGroup);
562 if (includeAlternateLongIdentifiers())
563 {
564 helpSASL.addLongIdentifier("help-sasl");
565 }
566 helpSASL.setUsageArgument(true);
567 parser.addArgument(helpSASL);
568 setHelpSASLArgument(helpSASL);
569 }
570 }
571
572
573 // Both useSSL and useStartTLS cannot be used together.
574 parser.addExclusiveArgumentSet(useSSL, useStartTLS);
575
576 // Only one option may be used for specifying the key store password.
577 parser.addExclusiveArgumentSet(keyStorePassword, keyStorePasswordFile,
578 promptForKeyStorePassword);
579
580 // Only one option may be used for specifying the trust store password.
581 parser.addExclusiveArgumentSet(trustStorePassword, trustStorePasswordFile,
582 promptForTrustStorePassword);
583
584 // It doesn't make sense to provide a trust store path if any server
585 // certificate should be trusted.
586 parser.addExclusiveArgumentSet(trustAll, trustStorePath);
587
588 // If a key store password is provided, then a key store path must have also
589 // been provided.
590 parser.addDependentArgumentSet(keyStorePassword, keyStorePath);
591 parser.addDependentArgumentSet(keyStorePasswordFile, keyStorePath);
592 parser.addDependentArgumentSet(promptForKeyStorePassword, keyStorePath);
593
594 // If a trust store password is provided, then a trust store path must have
595 // also been provided.
596 parser.addDependentArgumentSet(trustStorePassword, trustStorePath);
597 parser.addDependentArgumentSet(trustStorePasswordFile, trustStorePath);
598 parser.addDependentArgumentSet(promptForTrustStorePassword, trustStorePath);
599
600 // If a key or trust store path is provided, then the tool must either use
601 // SSL or StartTLS.
602 parser.addDependentArgumentSet(keyStorePath, useSSL, useStartTLS);
603 parser.addDependentArgumentSet(trustStorePath, useSSL, useStartTLS);
604
605 // If the tool should trust all server certificates, then the tool must
606 // either use SSL or StartTLS.
607 parser.addDependentArgumentSet(trustAll, useSSL, useStartTLS);
608
609 if (supportsAuthentication)
610 {
611 // If a bind DN was provided, then a bind password must have also been
612 // provided.
613 parser.addDependentArgumentSet(bindDN, bindPassword, bindPasswordFile,
614 promptForBindPassword);
615
616 // Only one option may be used for specifying the bind password.
617 parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile,
618 promptForBindPassword);
619
620 // If a bind password was provided, then the a bind DN or SASL option
621 // must have also been provided.
622 parser.addDependentArgumentSet(bindPassword, bindDN, saslOption);
623 parser.addDependentArgumentSet(bindPasswordFile, bindDN, saslOption);
624 parser.addDependentArgumentSet(promptForBindPassword, bindDN, saslOption);
625 }
626
627 addNonLDAPArguments(parser);
628 }
629
630
631
632 /**
633 * Adds the arguments needed by this command-line tool to the provided
634 * argument parser which are not related to connecting or authenticating to
635 * the directory server.
636 *
637 * @param parser The argument parser to which the arguments should be added.
638 *
639 * @throws ArgumentException If a problem occurs while adding the arguments.
640 */
641 public abstract void addNonLDAPArguments(final ArgumentParser parser)
642 throws ArgumentException;
643
644
645
646 /**
647 * {@inheritDoc}
648 */
649 @Override()
650 public final void doExtendedArgumentValidation()
651 throws ArgumentException
652 {
653 // If more than one hostname or port number was provided, then make sure
654 // that the same number of values were provided for each.
655 if ((host.getValues().size() > 1) || (port.getValues().size() > 1))
656 {
657 if (host.getValues().size() != port.getValues().size())
658 {
659 throw new ArgumentException(
660 ERR_LDAP_TOOL_HOST_PORT_COUNT_MISMATCH.get(
661 host.getLongIdentifier(), port.getLongIdentifier()));
662 }
663 }
664
665
666 doExtendedNonLDAPArgumentValidation();
667 }
668
669
670
671 /**
672 * Indicates whether this tool should provide the arguments that allow it to
673 * bind via simple or SASL authentication.
674 *
675 * @return {@code true} if this tool should provide the arguments that allow
676 * it to bind via simple or SASL authentication, or {@code false} if
677 * not.
678 */
679 protected boolean supportsAuthentication()
680 {
681 return true;
682 }
683
684
685
686 /**
687 * Indicates whether this tool should provide a "--help-sasl" argument that
688 * provides information about the supported SASL mechanisms and their
689 * associated properties.
690 *
691 * @return {@code true} if this tool should provide a "--help-sasl" argument,
692 * or {@code false} if not.
693 */
694 protected boolean supportsSASLHelp()
695 {
696 return true;
697 }
698
699
700
701 /**
702 * Indicates whether the LDAP-specific arguments should include alternate
703 * versions of all long identifiers that consist of multiple words so that
704 * they are available in both camelCase and dash-separated versions.
705 *
706 * @return {@code true} if this tool should provide multiple versions of
707 * long identifiers for LDAP-specific arguments, or {@code false} if
708 * not.
709 */
710 protected boolean includeAlternateLongIdentifiers()
711 {
712 return false;
713 }
714
715
716
717 /**
718 * Retrieves a set of controls that should be included in any bind request
719 * generated by this tool.
720 *
721 * @return A set of controls that should be included in any bind request
722 * generated by this tool. It may be {@code null} or empty if no
723 * controls should be included in the bind request.
724 */
725 protected List<Control> getBindControls()
726 {
727 return null;
728 }
729
730
731
732 /**
733 * Indicates whether this tool supports creating connections to multiple
734 * servers. If it is to support multiple servers, then the "--hostname" and
735 * "--port" arguments will be allowed to be provided multiple times, and
736 * will be required to be provided the same number of times. The same type of
737 * communication security and bind credentials will be used for all servers.
738 *
739 * @return {@code true} if this tool supports creating connections to
740 * multiple servers, or {@code false} if not.
741 */
742 protected boolean supportsMultipleServers()
743 {
744 return false;
745 }
746
747
748
749 /**
750 * Performs any necessary processing that should be done to ensure that the
751 * provided set of command-line arguments were valid. This method will be
752 * called after the basic argument parsing has been performed and after all
753 * LDAP-specific argument validation has been processed, and immediately
754 * before the {@link CommandLineTool#doToolProcessing} method is invoked.
755 *
756 * @throws ArgumentException If there was a problem with the command-line
757 * arguments provided to this program.
758 */
759 public void doExtendedNonLDAPArgumentValidation()
760 throws ArgumentException
761 {
762 // No processing will be performed by default.
763 }
764
765
766
767 /**
768 * Retrieves the connection options that should be used for connections that
769 * are created with this command line tool. Subclasses may override this
770 * method to use a custom set of connection options.
771 *
772 * @return The connection options that should be used for connections that
773 * are created with this command line tool.
774 */
775 public LDAPConnectionOptions getConnectionOptions()
776 {
777 return new LDAPConnectionOptions();
778 }
779
780
781
782 /**
783 * Retrieves a connection that may be used to communicate with the target
784 * directory server.
785 * <BR><BR>
786 * Note that this method is threadsafe and may be invoked by multiple threads
787 * accessing the same instance only while that instance is in the process of
788 * invoking the {@link #doToolProcessing} method.
789 *
790 * @return A connection that may be used to communicate with the target
791 * directory server.
792 *
793 * @throws LDAPException If a problem occurs while creating the connection.
794 */
795 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
796 public final LDAPConnection getConnection()
797 throws LDAPException
798 {
799 final LDAPConnection connection = getUnauthenticatedConnection();
800
801 try
802 {
803 if (bindRequest != null)
804 {
805 connection.bind(bindRequest);
806 }
807 }
808 catch (LDAPException le)
809 {
810 debugException(le);
811 connection.close();
812 throw le;
813 }
814
815 return connection;
816 }
817
818
819
820 /**
821 * Retrieves an unauthenticated connection that may be used to communicate
822 * with the target 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 An unauthenticated connection that may be used to communicate with
829 * the target 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 getUnauthenticatedConnection()
835 throws LDAPException
836 {
837 if (serverSet == null)
838 {
839 serverSet = createServerSet();
840 bindRequest = createBindRequest();
841 }
842
843 final LDAPConnection connection = serverSet.getConnection();
844
845 if (useStartTLS.isPresent())
846 {
847 try
848 {
849 final ExtendedResult extendedResult =
850 connection.processExtendedOperation(
851 new StartTLSExtendedRequest(startTLSSocketFactory));
852 if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
853 {
854 throw new LDAPException(extendedResult.getResultCode(),
855 ERR_LDAP_TOOL_START_TLS_FAILED.get(
856 extendedResult.getDiagnosticMessage()));
857 }
858 }
859 catch (LDAPException le)
860 {
861 debugException(le);
862 connection.close();
863 throw le;
864 }
865 }
866
867 return connection;
868 }
869
870
871
872 /**
873 * Retrieves a connection pool that may be used to communicate with the target
874 * directory server.
875 * <BR><BR>
876 * Note that this method is threadsafe and may be invoked by multiple threads
877 * accessing the same instance only while that instance is in the process of
878 * invoking the {@link #doToolProcessing} method.
879 *
880 * @param initialConnections The number of connections that should be
881 * initially established in the pool.
882 * @param maxConnections The maximum number of connections to maintain
883 * in the pool.
884 *
885 * @return A connection that may be used to communicate with the target
886 * directory server.
887 *
888 * @throws LDAPException If a problem occurs while creating the connection
889 * pool.
890 */
891 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
892 public final LDAPConnectionPool getConnectionPool(
893 final int initialConnections,
894 final int maxConnections)
895 throws LDAPException
896 {
897 return getConnectionPool(initialConnections, maxConnections, 1, null, null,
898 true, null);
899 }
900
901
902
903 /**
904 * Retrieves a connection pool that may be used to communicate with the target
905 * directory server.
906 * <BR><BR>
907 * Note that this method is threadsafe and may be invoked by multiple threads
908 * accessing the same instance only while that instance is in the process of
909 * invoking the {@link #doToolProcessing} method.
910 *
911 * @param initialConnections The number of connections that should be
912 * initially established in the pool.
913 * @param maxConnections The maximum number of connections to
914 * maintain in the pool.
915 * @param initialConnectThreads The number of concurrent threads to use to
916 * establish the initial set of connections.
917 * A value greater than one indicates that
918 * the attempt to establish connections
919 * should be parallelized.
920 * @param beforeStartTLSProcessor An optional post-connect processor that
921 * should be used for the connection pool and
922 * should be invoked before any StartTLS
923 * post-connect processor that may be needed
924 * based on the selected arguments. It may
925 * be {@code null} if no such post-connect
926 * processor is needed.
927 * @param afterStartTLSProcessor An optional post-connect processor that
928 * should be used for the connection pool and
929 * should be invoked after any StartTLS
930 * post-connect processor that may be needed
931 * based on the selected arguments. It may
932 * be {@code null} if no such post-connect
933 * processor is needed.
934 * @param throwOnConnectFailure If an exception should be thrown if a
935 * problem is encountered while attempting to
936 * create the specified initial number of
937 * connections. If {@code true}, then the
938 * attempt to create the pool will fail if
939 * any connection cannot be established. If
940 * {@code false}, then the pool will be
941 * created but may have fewer than the
942 * initial number of connections (or possibly
943 * no connections).
944 * @param healthCheck An optional health check that should be
945 * configured for the connection pool. It
946 * may be {@code null} if the default health
947 * checking should be performed.
948 *
949 * @return A connection that may be used to communicate with the target
950 * directory server.
951 *
952 * @throws LDAPException If a problem occurs while creating the connection
953 * pool.
954 */
955 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
956 public final LDAPConnectionPool getConnectionPool(
957 final int initialConnections, final int maxConnections,
958 final int initialConnectThreads,
959 final PostConnectProcessor beforeStartTLSProcessor,
960 final PostConnectProcessor afterStartTLSProcessor,
961 final boolean throwOnConnectFailure,
962 final LDAPConnectionPoolHealthCheck healthCheck)
963 throws LDAPException
964 {
965 // Create the server set and bind request, if necessary.
966 if (serverSet == null)
967 {
968 serverSet = createServerSet();
969 bindRequest = createBindRequest();
970 }
971
972
973 // Prepare the post-connect processor for the pool.
974 final ArrayList<PostConnectProcessor> pcpList =
975 new ArrayList<PostConnectProcessor>(3);
976 if (beforeStartTLSProcessor != null)
977 {
978 pcpList.add(beforeStartTLSProcessor);
979 }
980
981 if (useStartTLS.isPresent())
982 {
983 pcpList.add(new StartTLSPostConnectProcessor(startTLSSocketFactory));
984 }
985
986 if (afterStartTLSProcessor != null)
987 {
988 pcpList.add(afterStartTLSProcessor);
989 }
990
991 final PostConnectProcessor postConnectProcessor;
992 switch (pcpList.size())
993 {
994 case 0:
995 postConnectProcessor = null;
996 break;
997 case 1:
998 postConnectProcessor = pcpList.get(0);
999 break;
1000 default:
1001 postConnectProcessor = new AggregatePostConnectProcessor(pcpList);
1002 break;
1003 }
1004
1005 return new LDAPConnectionPool(serverSet, bindRequest, initialConnections,
1006 maxConnections, initialConnectThreads, postConnectProcessor,
1007 throwOnConnectFailure, healthCheck);
1008 }
1009
1010
1011
1012 /**
1013 * Creates the server set to use when creating connections or connection
1014 * pools.
1015 *
1016 * @return The server set to use when creating connections or connection
1017 * pools.
1018 *
1019 * @throws LDAPException If a problem occurs while creating the server set.
1020 */
1021 public ServerSet createServerSet()
1022 throws LDAPException
1023 {
1024 final SSLUtil sslUtil = createSSLUtil();
1025
1026 SocketFactory socketFactory = null;
1027 if (useSSL.isPresent())
1028 {
1029 try
1030 {
1031 socketFactory = sslUtil.createSSLSocketFactory();
1032 }
1033 catch (Exception e)
1034 {
1035 debugException(e);
1036 throw new LDAPException(ResultCode.LOCAL_ERROR,
1037 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1038 getExceptionMessage(e)), e);
1039 }
1040 }
1041 else if (useStartTLS.isPresent())
1042 {
1043 try
1044 {
1045 startTLSSocketFactory = sslUtil.createSSLSocketFactory();
1046 }
1047 catch (Exception e)
1048 {
1049 debugException(e);
1050 throw new LDAPException(ResultCode.LOCAL_ERROR,
1051 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1052 getExceptionMessage(e)), e);
1053 }
1054 }
1055
1056 if (host.getValues().size() == 1)
1057 {
1058 return new SingleServerSet(host.getValue(), port.getValue(),
1059 socketFactory, getConnectionOptions());
1060 }
1061 else
1062 {
1063 final List<String> hostList = host.getValues();
1064 final List<Integer> portList = port.getValues();
1065
1066 final String[] hosts = new String[hostList.size()];
1067 final int[] ports = new int[hosts.length];
1068
1069 for (int i=0; i < hosts.length; i++)
1070 {
1071 hosts[i] = hostList.get(i);
1072 ports[i] = portList.get(i);
1073 }
1074
1075 return new RoundRobinServerSet(hosts, ports, socketFactory,
1076 getConnectionOptions());
1077 }
1078 }
1079
1080
1081
1082 /**
1083 * Creates the SSLUtil instance to use for secure communication.
1084 *
1085 * @return The SSLUtil instance to use for secure communication, or
1086 * {@code null} if secure communication is not needed.
1087 *
1088 * @throws LDAPException If a problem occurs while creating the SSLUtil
1089 * instance.
1090 */
1091 public SSLUtil createSSLUtil()
1092 throws LDAPException
1093 {
1094 return createSSLUtil(false);
1095 }
1096
1097
1098
1099 /**
1100 * Creates the SSLUtil instance to use for secure communication.
1101 *
1102 * @param force Indicates whether to create the SSLUtil object even if
1103 * neither the "--useSSL" nor the "--useStartTLS" argument was
1104 * provided. The key store and/or trust store paths must still
1105 * have been provided. This may be useful for tools that
1106 * accept SSL-based communication but do not themselves intend
1107 * to perform SSL-based communication as an LDAP client.
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(final boolean force)
1116 throws LDAPException
1117 {
1118 if (force || useSSL.isPresent() || useStartTLS.isPresent())
1119 {
1120 KeyManager keyManager = null;
1121 if (keyStorePath.isPresent())
1122 {
1123 char[] pw = null;
1124 if (keyStorePassword.isPresent())
1125 {
1126 pw = keyStorePassword.getValue().toCharArray();
1127 }
1128 else if (keyStorePasswordFile.isPresent())
1129 {
1130 try
1131 {
1132 pw = keyStorePasswordFile.getNonBlankFileLines().get(0).
1133 toCharArray();
1134 }
1135 catch (Exception e)
1136 {
1137 debugException(e);
1138 throw new LDAPException(ResultCode.LOCAL_ERROR,
1139 ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
1140 getExceptionMessage(e)), e);
1141 }
1142 }
1143 else if (promptForKeyStorePassword.isPresent())
1144 {
1145 getOut().print(INFO_LDAP_TOOL_ENTER_KEY_STORE_PASSWORD.get());
1146 pw = StaticUtils.toUTF8String(
1147 PasswordReader.readPassword()).toCharArray();
1148 getOut().println();
1149 }
1150
1151 try
1152 {
1153 keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw,
1154 keyStoreFormat.getValue(), certificateNickname.getValue());
1155 }
1156 catch (Exception e)
1157 {
1158 debugException(e);
1159 throw new LDAPException(ResultCode.LOCAL_ERROR,
1160 ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
1161 getExceptionMessage(e)), e);
1162 }
1163 }
1164
1165 TrustManager trustManager;
1166 if (trustAll.isPresent())
1167 {
1168 trustManager = new TrustAllTrustManager(false);
1169 }
1170 else if (trustStorePath.isPresent())
1171 {
1172 char[] pw = null;
1173 if (trustStorePassword.isPresent())
1174 {
1175 pw = trustStorePassword.getValue().toCharArray();
1176 }
1177 else if (trustStorePasswordFile.isPresent())
1178 {
1179 try
1180 {
1181 pw = trustStorePasswordFile.getNonBlankFileLines().get(0).
1182 toCharArray();
1183 }
1184 catch (Exception e)
1185 {
1186 debugException(e);
1187 throw new LDAPException(ResultCode.LOCAL_ERROR,
1188 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
1189 getExceptionMessage(e)), e);
1190 }
1191 }
1192 else if (promptForTrustStorePassword.isPresent())
1193 {
1194 getOut().print(INFO_LDAP_TOOL_ENTER_TRUST_STORE_PASSWORD.get());
1195 pw = StaticUtils.toUTF8String(
1196 PasswordReader.readPassword()).toCharArray();
1197 getOut().println();
1198 }
1199
1200 trustManager = new TrustStoreTrustManager(trustStorePath.getValue(), pw,
1201 trustStoreFormat.getValue(), true);
1202 }
1203 else
1204 {
1205 trustManager = promptTrustManager.get();
1206 if (trustManager == null)
1207 {
1208 final PromptTrustManager m = new PromptTrustManager();
1209 promptTrustManager.compareAndSet(null, m);
1210 trustManager = promptTrustManager.get();
1211 }
1212 }
1213
1214 return new SSLUtil(keyManager, trustManager);
1215 }
1216 else
1217 {
1218 return null;
1219 }
1220 }
1221
1222
1223
1224 /**
1225 * Creates the bind request to use to authenticate to the server.
1226 *
1227 * @return The bind request to use to authenticate to the server, or
1228 * {@code null} if no bind should be performed.
1229 *
1230 * @throws LDAPException If a problem occurs while creating the bind
1231 * request.
1232 */
1233 public BindRequest createBindRequest()
1234 throws LDAPException
1235 {
1236 if (! supportsAuthentication())
1237 {
1238 return null;
1239 }
1240
1241 final Control[] bindControls;
1242 final List<Control> bindControlList = getBindControls();
1243 if ((bindControlList == null) || bindControlList.isEmpty())
1244 {
1245 bindControls = NO_CONTROLS;
1246 }
1247 else
1248 {
1249 bindControls = new Control[bindControlList.size()];
1250 bindControlList.toArray(bindControls);
1251 }
1252
1253 final String pw;
1254 if (bindPassword.isPresent())
1255 {
1256 pw = bindPassword.getValue();
1257 }
1258 else if (bindPasswordFile.isPresent())
1259 {
1260 try
1261 {
1262 pw = bindPasswordFile.getNonBlankFileLines().get(0);
1263 }
1264 catch (Exception e)
1265 {
1266 debugException(e);
1267 throw new LDAPException(ResultCode.LOCAL_ERROR,
1268 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
1269 getExceptionMessage(e)), e);
1270 }
1271 }
1272 else if (promptForBindPassword.isPresent())
1273 {
1274 getOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1275 pw = StaticUtils.toUTF8String(PasswordReader.readPassword());
1276 getOut().println();
1277 }
1278 else
1279 {
1280 pw = null;
1281 }
1282
1283 if (saslOption.isPresent())
1284 {
1285 final String dnStr;
1286 if (bindDN.isPresent())
1287 {
1288 dnStr = bindDN.getValue().toString();
1289 }
1290 else
1291 {
1292 dnStr = null;
1293 }
1294
1295 return SASLUtils.createBindRequest(dnStr, pw, null,
1296 saslOption.getValues(), bindControls);
1297 }
1298 else if (bindDN.isPresent())
1299 {
1300 return new SimpleBindRequest(bindDN.getValue(), pw, bindControls);
1301 }
1302 else
1303 {
1304 return null;
1305 }
1306 }
1307 }