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