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