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