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