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