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