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