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