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