001/* 002 * Copyright 2008-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util; 022 023 024 025import java.io.OutputStream; 026import java.util.List; 027import java.util.concurrent.atomic.AtomicReference; 028import javax.net.SocketFactory; 029import javax.net.ssl.KeyManager; 030import javax.net.ssl.SSLContext; 031import javax.net.ssl.TrustManager; 032 033import com.unboundid.ldap.sdk.BindRequest; 034import com.unboundid.ldap.sdk.ExtendedResult; 035import com.unboundid.ldap.sdk.LDAPConnection; 036import com.unboundid.ldap.sdk.LDAPConnectionOptions; 037import com.unboundid.ldap.sdk.LDAPConnectionPool; 038import com.unboundid.ldap.sdk.LDAPException; 039import com.unboundid.ldap.sdk.PostConnectProcessor; 040import com.unboundid.ldap.sdk.ResultCode; 041import com.unboundid.ldap.sdk.RoundRobinServerSet; 042import com.unboundid.ldap.sdk.ServerSet; 043import com.unboundid.ldap.sdk.SimpleBindRequest; 044import com.unboundid.ldap.sdk.SingleServerSet; 045import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor; 046import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; 047import com.unboundid.util.args.ArgumentException; 048import com.unboundid.util.args.ArgumentParser; 049import com.unboundid.util.args.BooleanArgument; 050import com.unboundid.util.args.DNArgument; 051import com.unboundid.util.args.FileArgument; 052import com.unboundid.util.args.IntegerArgument; 053import com.unboundid.util.args.StringArgument; 054import com.unboundid.util.ssl.KeyStoreKeyManager; 055import com.unboundid.util.ssl.PromptTrustManager; 056import com.unboundid.util.ssl.SSLUtil; 057import com.unboundid.util.ssl.TrustAllTrustManager; 058import com.unboundid.util.ssl.TrustStoreTrustManager; 059 060import static com.unboundid.util.Debug.*; 061import static com.unboundid.util.StaticUtils.*; 062import static com.unboundid.util.UtilityMessages.*; 063 064 065 066/** 067 * This class provides a basis for developing command-line tools that 068 * communicate with an LDAP directory server. It provides a common set of 069 * options for connecting and authenticating to a directory server, and then 070 * provides a mechanism for obtaining connections and connection pools to use 071 * when communicating with that server. 072 * <BR><BR> 073 * The arguments that this class supports include: 074 * <UL> 075 * <LI>"-h {address}" or "--hostname {address}" -- Specifies the address of 076 * the directory server. If this isn't specified, then a default of 077 * "localhost" will be used.</LI> 078 * <LI>"-p {port}" or "--port {port}" -- Specifies the port number of the 079 * directory server. If this isn't specified, then a default port of 389 080 * will be used.</LI> 081 * <LI>"-D {bindDN}" or "--bindDN {bindDN}" -- Specifies the DN to use to bind 082 * to the directory server using simple authentication. If this isn't 083 * specified, then simple authentication will not be performed.</LI> 084 * <LI>"-w {password}" or "--bindPassword {password}" -- Specifies the 085 * password to use when binding with simple authentication or a 086 * password-based SASL mechanism.</LI> 087 * <LI>"-j {path}" or "--bindPasswordFile {path}" -- Specifies the path to the 088 * file containing the password to use when binding with simple 089 * authentication or a password-based SASL mechanism.</LI> 090 * <LI>"--promptForBindPassword" -- Indicates that the tool should 091 * interactively prompt the user for the bind password.</LI> 092 * <LI>"-Z" or "--useSSL" -- Indicates that the communication with the server 093 * should be secured using SSL.</LI> 094 * <LI>"-q" or "--useStartTLS" -- Indicates that the communication with the 095 * server should be secured using StartTLS.</LI> 096 * <LI>"-X" or "--trustAll" -- Indicates that the client should trust any 097 * certificate that the server presents to it.</LI> 098 * <LI>"-K {path}" or "--keyStorePath {path}" -- Specifies the path to the 099 * key store to use to obtain client certificates.</LI> 100 * <LI>"-W {password}" or "--keyStorePassword {password}" -- Specifies the 101 * password to use to access the contents of the key store.</LI> 102 * <LI>"-u {path}" or "--keyStorePasswordFile {path}" -- Specifies the path to 103 * the file containing the password to use to access the contents of the 104 * key store.</LI> 105 * <LI>"--promptForKeyStorePassword" -- Indicates that the tool should 106 * interactively prompt the user for the key store password.</LI> 107 * <LI>"--keyStoreFormat {format}" -- Specifies the format to use for the key 108 * store file.</LI> 109 * <LI>"-P {path}" or "--trustStorePath {path}" -- Specifies the path to the 110 * trust store to use when determining whether to trust server 111 * certificates.</LI> 112 * <LI>"-T {password}" or "--trustStorePassword {password}" -- Specifies the 113 * password to use to access the contents of the trust store.</LI> 114 * <LI>"-U {path}" or "--trustStorePasswordFile {path}" -- Specifies the path 115 * to the file containing the password to use to access the contents of 116 * the trust store.</LI> 117 * <LI>"--promptForTrustStorePassword" -- Indicates that the tool should 118 * interactively prompt the user for the trust store password.</LI> 119 * <LI>"--trustStoreFormat {format}" -- Specifies the format to use for the 120 * trust store file.</LI> 121 * <LI>"-N {nickname}" or "--certNickname {nickname}" -- Specifies the 122 * nickname of the client certificate to use when performing SSL client 123 * authentication.</LI> 124 * <LI>"-o {name=value}" or "--saslOption {name=value}" -- Specifies a SASL 125 * option to use when performing SASL authentication.</LI> 126 * </UL> 127 * If SASL authentication is to be used, then a "mech" SASL option must be 128 * provided to specify the name of the SASL mechanism to use (e.g., 129 * "--saslOption mech=EXTERNAL" indicates that the EXTERNAL mechanism should be 130 * used). Depending on the SASL mechanism, additional SASL options may be 131 * required or optional. They include: 132 * <UL> 133 * <LI> 134 * mech=ANONYMOUS 135 * <UL> 136 * <LI>Required SASL options: </LI> 137 * <LI>Optional SASL options: trace</LI> 138 * </UL> 139 * </LI> 140 * <LI> 141 * mech=CRAM-MD5 142 * <UL> 143 * <LI>Required SASL options: authID</LI> 144 * <LI>Optional SASL options: </LI> 145 * </UL> 146 * </LI> 147 * <LI> 148 * mech=DIGEST-MD5 149 * <UL> 150 * <LI>Required SASL options: authID</LI> 151 * <LI>Optional SASL options: authzID, realm</LI> 152 * </UL> 153 * </LI> 154 * <LI> 155 * mech=EXTERNAL 156 * <UL> 157 * <LI>Required SASL options: </LI> 158 * <LI>Optional SASL options: </LI> 159 * </UL> 160 * </LI> 161 * <LI> 162 * mech=GSSAPI 163 * <UL> 164 * <LI>Required SASL options: authID</LI> 165 * <LI>Optional SASL options: authzID, configFile, debug, protocol, 166 * realm, kdcAddress, useTicketCache, requireCache, 167 * renewTGT, ticketCachePath</LI> 168 * </UL> 169 * </LI> 170 * <LI> 171 * mech=PLAIN 172 * <UL> 173 * <LI>Required SASL options: authID</LI> 174 * <LI>Optional SASL options: authzID</LI> 175 * </UL> 176 * </LI> 177 * </UL> 178 * <BR><BR> 179 * Note that in general, methods in this class are not threadsafe. However, the 180 * {@link #getConnection()} and {@link #getConnectionPool(int,int)} methods may 181 * be invoked concurrently by multiple threads accessing the same instance only 182 * while that instance is in the process of invoking the 183 * {@link #doToolProcessing()} method. 184 */ 185@Extensible() 186@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE) 187public abstract class LDAPCommandLineTool 188 extends CommandLineTool 189{ 190 191 192 193 // Arguments used to communicate with an LDAP directory server. 194 private BooleanArgument promptForBindPassword = null; 195 private BooleanArgument promptForKeyStorePassword = null; 196 private BooleanArgument promptForTrustStorePassword = null; 197 private BooleanArgument trustAll = null; 198 private BooleanArgument useSSL = null; 199 private BooleanArgument useStartTLS = null; 200 private DNArgument bindDN = null; 201 private FileArgument bindPasswordFile = null; 202 private FileArgument keyStorePasswordFile = null; 203 private FileArgument trustStorePasswordFile = null; 204 private IntegerArgument port = null; 205 private StringArgument bindPassword = null; 206 private StringArgument certificateNickname = null; 207 private StringArgument host = null; 208 private StringArgument keyStoreFormat = null; 209 private StringArgument keyStorePath = null; 210 private StringArgument keyStorePassword = null; 211 private StringArgument saslOption = null; 212 private StringArgument trustStoreFormat = null; 213 private StringArgument trustStorePath = null; 214 private StringArgument trustStorePassword = null; 215 216 // Variables used when creating and authenticating connections. 217 private BindRequest bindRequest = null; 218 private ServerSet serverSet = null; 219 private SSLContext startTLSContext = null; 220 221 // The prompt trust manager that will be shared by all connections created 222 // for which it is appropriate. This will allow them to benefit from the 223 // common cache. 224 private final AtomicReference<PromptTrustManager> promptTrustManager; 225 226 227 228 /** 229 * Creates a new instance of this LDAP-enabled command-line tool with the 230 * provided information. 231 * 232 * @param outStream The output stream to use for standard output. It may be 233 * {@code System.out} for the JVM's default standard output 234 * stream, {@code null} if no output should be generated, 235 * or a custom output stream if the output should be sent 236 * to an alternate location. 237 * @param errStream The output stream to use for standard error. It may be 238 * {@code System.err} for the JVM's default standard error 239 * stream, {@code null} if no output should be generated, 240 * or a custom output stream if the output should be sent 241 * to an alternate location. 242 */ 243 public LDAPCommandLineTool(final OutputStream outStream, 244 final OutputStream errStream) 245 { 246 super(outStream, errStream); 247 248 promptTrustManager = new AtomicReference<PromptTrustManager>(); 249 } 250 251 252 253 /** 254 * {@inheritDoc} 255 */ 256 @Override() 257 public final void addToolArguments(final ArgumentParser parser) 258 throws ArgumentException 259 { 260 host = new StringArgument('h', "hostname", true, 261 (supportsMultipleServers() ? 0 : 1), 262 INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(), 263 INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost"); 264 parser.addArgument(host); 265 266 port = new IntegerArgument('p', "port", true, 267 (supportsMultipleServers() ? 0 : 1), 268 INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(), 269 INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389); 270 parser.addArgument(port); 271 272 final boolean supportsAuthentication = supportsAuthentication(); 273 if (supportsAuthentication) 274 { 275 bindDN = new DNArgument('D', "bindDN", false, 1, 276 INFO_LDAP_TOOL_PLACEHOLDER_DN.get(), 277 INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get()); 278 parser.addArgument(bindDN); 279 280 bindPassword = new StringArgument('w', "bindPassword", false, 1, 281 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(), 282 INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get()); 283 parser.addArgument(bindPassword); 284 285 bindPasswordFile = new FileArgument('j', "bindPasswordFile", false, 1, 286 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 287 INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true, 288 false); 289 parser.addArgument(bindPasswordFile); 290 291 promptForBindPassword = new BooleanArgument(null, "promptForBindPassword", 292 1, INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_PROMPT.get()); 293 parser.addArgument(promptForBindPassword); 294 } 295 296 useSSL = new BooleanArgument('Z', "useSSL", 1, 297 INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get()); 298 parser.addArgument(useSSL); 299 300 useStartTLS = new BooleanArgument('q', "useStartTLS", 1, 301 INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get()); 302 parser.addArgument(useStartTLS); 303 304 trustAll = new BooleanArgument('X', "trustAll", 1, 305 INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get()); 306 parser.addArgument(trustAll); 307 308 keyStorePath = new StringArgument('K', "keyStorePath", false, 1, 309 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 310 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get()); 311 parser.addArgument(keyStorePath); 312 313 keyStorePassword = new StringArgument('W', "keyStorePassword", false, 1, 314 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(), 315 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get()); 316 parser.addArgument(keyStorePassword); 317 318 keyStorePasswordFile = new FileArgument('u', "keyStorePasswordFile", false, 319 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 320 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get()); 321 parser.addArgument(keyStorePasswordFile); 322 323 promptForKeyStorePassword = new BooleanArgument(null, 324 "promptForKeyStorePassword", 1, 325 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_PROMPT.get()); 326 parser.addArgument(promptForKeyStorePassword); 327 328 keyStoreFormat = new StringArgument(null, "keyStoreFormat", false, 1, 329 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(), 330 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get()); 331 parser.addArgument(keyStoreFormat); 332 333 trustStorePath = new StringArgument('P', "trustStorePath", false, 1, 334 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 335 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get()); 336 parser.addArgument(trustStorePath); 337 338 trustStorePassword = new StringArgument('T', "trustStorePassword", false, 1, 339 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(), 340 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get()); 341 parser.addArgument(trustStorePassword); 342 343 trustStorePasswordFile = new FileArgument('U', "trustStorePasswordFile", 344 false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 345 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get()); 346 parser.addArgument(trustStorePasswordFile); 347 348 promptForTrustStorePassword = new BooleanArgument(null, 349 "promptForTrustStorePassword", 1, 350 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_PROMPT.get()); 351 parser.addArgument(promptForTrustStorePassword); 352 353 trustStoreFormat = new StringArgument(null, "trustStoreFormat", false, 1, 354 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(), 355 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get()); 356 parser.addArgument(trustStoreFormat); 357 358 certificateNickname = new StringArgument('N', "certNickname", false, 1, 359 INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(), 360 INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get()); 361 parser.addArgument(certificateNickname); 362 363 if (supportsAuthentication) 364 { 365 saslOption = new StringArgument('o', "saslOption", false, 0, 366 INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(), 367 INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get()); 368 parser.addArgument(saslOption); 369 } 370 371 372 // Both useSSL and useStartTLS cannot be used together. 373 parser.addExclusiveArgumentSet(useSSL, useStartTLS); 374 375 // Only one option may be used for specifying the key store password. 376 parser.addExclusiveArgumentSet(keyStorePassword, keyStorePasswordFile, 377 promptForKeyStorePassword); 378 379 // Only one option may be used for specifying the trust store password. 380 parser.addExclusiveArgumentSet(trustStorePassword, trustStorePasswordFile, 381 promptForTrustStorePassword); 382 383 // It doesn't make sense to provide a trust store path if any server 384 // certificate should be trusted. 385 parser.addExclusiveArgumentSet(trustAll, trustStorePath); 386 387 // If a key store password is provided, then a key store path must have also 388 // been provided. 389 parser.addDependentArgumentSet(keyStorePassword, keyStorePath); 390 parser.addDependentArgumentSet(keyStorePasswordFile, keyStorePath); 391 parser.addDependentArgumentSet(promptForKeyStorePassword, keyStorePath); 392 393 // If a trust store password is provided, then a trust store path must have 394 // also been provided. 395 parser.addDependentArgumentSet(trustStorePassword, trustStorePath); 396 parser.addDependentArgumentSet(trustStorePasswordFile, trustStorePath); 397 parser.addDependentArgumentSet(promptForTrustStorePassword, trustStorePath); 398 399 // If a key or trust store path is provided, then the tool must either use 400 // SSL or StartTLS. 401 parser.addDependentArgumentSet(keyStorePath, useSSL, useStartTLS); 402 parser.addDependentArgumentSet(trustStorePath, useSSL, useStartTLS); 403 404 // If the tool should trust all server certificates, then the tool must 405 // either use SSL or StartTLS. 406 parser.addDependentArgumentSet(trustAll, useSSL, useStartTLS); 407 408 if (supportsAuthentication) 409 { 410 // If a bind DN was provided, then a bind password must have also been 411 // provided. 412 parser.addDependentArgumentSet(bindDN, bindPassword, bindPasswordFile, 413 promptForBindPassword); 414 415 // Only one option may be used for specifying the bind password. 416 parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile, 417 promptForBindPassword); 418 419 // If a bind password was provided, then the a bind DN or SASL option 420 // must have also been provided. 421 parser.addDependentArgumentSet(bindPassword, bindDN, saslOption); 422 parser.addDependentArgumentSet(bindPasswordFile, bindDN, saslOption); 423 parser.addDependentArgumentSet(promptForBindPassword, bindDN, saslOption); 424 } 425 426 addNonLDAPArguments(parser); 427 } 428 429 430 431 /** 432 * Adds the arguments needed by this command-line tool to the provided 433 * argument parser which are not related to connecting or authenticating to 434 * the directory server. 435 * 436 * @param parser The argument parser to which the arguments should be added. 437 * 438 * @throws ArgumentException If a problem occurs while adding the arguments. 439 */ 440 public abstract void addNonLDAPArguments(final ArgumentParser parser) 441 throws ArgumentException; 442 443 444 445 /** 446 * {@inheritDoc} 447 */ 448 @Override() 449 public final void doExtendedArgumentValidation() 450 throws ArgumentException 451 { 452 // If more than one hostname or port number was provided, then make sure 453 // that the same number of values were provided for each. 454 if ((host.getValues().size() > 1) || (port.getValues().size() > 1)) 455 { 456 if (host.getValues().size() != port.getValues().size()) 457 { 458 throw new ArgumentException( 459 ERR_LDAP_TOOL_HOST_PORT_COUNT_MISMATCH.get( 460 host.getLongIdentifier(), port.getLongIdentifier())); 461 } 462 } 463 464 465 doExtendedNonLDAPArgumentValidation(); 466 } 467 468 469 470 /** 471 * Indicates whether this tool should provide the arguments that allow it to 472 * bind via simple or SASL authentication. 473 * 474 * @return {@code true} if this tool should provide the arguments that allow 475 * it to bind via simple or SASL authentication, or {@code false} if 476 * not. 477 */ 478 protected boolean supportsAuthentication() 479 { 480 return true; 481 } 482 483 484 485 /** 486 * Indicates whether this tool supports creating connections to multiple 487 * servers. If it is to support multiple servers, then the "--hostname" and 488 * "--port" arguments will be allowed to be provided multiple times, and 489 * will be required to be provided the same number of times. The same type of 490 * communication security and bind credentials will be used for all servers. 491 * 492 * @return {@code true} if this tool supports creating connections to 493 * multiple servers, or {@code false} if not. 494 */ 495 protected boolean supportsMultipleServers() 496 { 497 return false; 498 } 499 500 501 502 /** 503 * Performs any necessary processing that should be done to ensure that the 504 * provided set of command-line arguments were valid. This method will be 505 * called after the basic argument parsing has been performed and after all 506 * LDAP-specific argument validation has been processed, and immediately 507 * before the {@link CommandLineTool#doToolProcessing} method is invoked. 508 * 509 * @throws ArgumentException If there was a problem with the command-line 510 * arguments provided to this program. 511 */ 512 public void doExtendedNonLDAPArgumentValidation() 513 throws ArgumentException 514 { 515 // No processing will be performed by default. 516 } 517 518 519 520 /** 521 * Retrieves the connection options that should be used for connections that 522 * are created with this command line tool. Subclasses may override this 523 * method to use a custom set of connection options. 524 * 525 * @return The connection options that should be used for connections that 526 * are created with this command line tool. 527 */ 528 public LDAPConnectionOptions getConnectionOptions() 529 { 530 return new LDAPConnectionOptions(); 531 } 532 533 534 535 /** 536 * Retrieves a connection that may be used to communicate with the target 537 * directory server. 538 * <BR><BR> 539 * Note that this method is threadsafe and may be invoked by multiple threads 540 * accessing the same instance only while that instance is in the process of 541 * invoking the {@link #doToolProcessing} method. 542 * 543 * @return A connection that may be used to communicate with the target 544 * directory server. 545 * 546 * @throws LDAPException If a problem occurs while creating the connection. 547 */ 548 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) 549 public final LDAPConnection getConnection() 550 throws LDAPException 551 { 552 final LDAPConnection connection = getUnauthenticatedConnection(); 553 554 try 555 { 556 if (bindRequest != null) 557 { 558 connection.bind(bindRequest); 559 } 560 } 561 catch (LDAPException le) 562 { 563 debugException(le); 564 connection.close(); 565 throw le; 566 } 567 568 return connection; 569 } 570 571 572 573 /** 574 * Retrieves an unauthenticated connection that may be used to communicate 575 * with the target directory server. 576 * <BR><BR> 577 * Note that this method is threadsafe and may be invoked by multiple threads 578 * accessing the same instance only while that instance is in the process of 579 * invoking the {@link #doToolProcessing} method. 580 * 581 * @return An unauthenticated connection that may be used to communicate with 582 * the target directory server. 583 * 584 * @throws LDAPException If a problem occurs while creating the connection. 585 */ 586 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) 587 public final LDAPConnection getUnauthenticatedConnection() 588 throws LDAPException 589 { 590 if (serverSet == null) 591 { 592 serverSet = createServerSet(); 593 bindRequest = createBindRequest(); 594 } 595 596 final LDAPConnection connection = serverSet.getConnection(); 597 598 if (useStartTLS.isPresent()) 599 { 600 try 601 { 602 final ExtendedResult extendedResult = 603 connection.processExtendedOperation( 604 new StartTLSExtendedRequest(startTLSContext)); 605 if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS)) 606 { 607 throw new LDAPException(extendedResult.getResultCode(), 608 ERR_LDAP_TOOL_START_TLS_FAILED.get( 609 extendedResult.getDiagnosticMessage())); 610 } 611 } 612 catch (LDAPException le) 613 { 614 debugException(le); 615 connection.close(); 616 throw le; 617 } 618 } 619 620 return connection; 621 } 622 623 624 625 /** 626 * Retrieves a connection pool that may be used to communicate with the target 627 * directory server. 628 * <BR><BR> 629 * Note that this method is threadsafe and may be invoked by multiple threads 630 * accessing the same instance only while that instance is in the process of 631 * invoking the {@link #doToolProcessing} method. 632 * 633 * @param initialConnections The number of connections that should be 634 * initially established in the pool. 635 * @param maxConnections The maximum number of connections to maintain 636 * in the pool. 637 * 638 * @return A connection that may be used to communicate with the target 639 * directory server. 640 * 641 * @throws LDAPException If a problem occurs while creating the connection 642 * pool. 643 */ 644 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) 645 public final LDAPConnectionPool getConnectionPool( 646 final int initialConnections, 647 final int maxConnections) 648 throws LDAPException 649 { 650 if (serverSet == null) 651 { 652 serverSet = createServerSet(); 653 bindRequest = createBindRequest(); 654 } 655 656 PostConnectProcessor postConnectProcessor = null; 657 if (useStartTLS.isPresent()) 658 { 659 postConnectProcessor = new StartTLSPostConnectProcessor(startTLSContext); 660 } 661 662 return new LDAPConnectionPool(serverSet, bindRequest, initialConnections, 663 maxConnections, postConnectProcessor); 664 } 665 666 667 668 /** 669 * Creates the server set to use when creating connections or connection 670 * pools. 671 * 672 * @return The server set to use when creating connections or connection 673 * pools. 674 * 675 * @throws LDAPException If a problem occurs while creating the server set. 676 */ 677 public ServerSet createServerSet() 678 throws LDAPException 679 { 680 final SSLUtil sslUtil = createSSLUtil(); 681 682 SocketFactory socketFactory = null; 683 if (useSSL.isPresent()) 684 { 685 try 686 { 687 socketFactory = sslUtil.createSSLSocketFactory(); 688 } 689 catch (Exception e) 690 { 691 debugException(e); 692 throw new LDAPException(ResultCode.LOCAL_ERROR, 693 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get( 694 getExceptionMessage(e)), e); 695 } 696 } 697 else if (useStartTLS.isPresent()) 698 { 699 try 700 { 701 startTLSContext = sslUtil.createSSLContext(); 702 } 703 catch (Exception e) 704 { 705 debugException(e); 706 throw new LDAPException(ResultCode.LOCAL_ERROR, 707 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_CONTEXT.get( 708 getExceptionMessage(e)), e); 709 } 710 } 711 712 if (host.getValues().size() == 1) 713 { 714 return new SingleServerSet(host.getValue(), port.getValue(), 715 socketFactory, getConnectionOptions()); 716 } 717 else 718 { 719 final List<String> hostList = host.getValues(); 720 final List<Integer> portList = port.getValues(); 721 722 final String[] hosts = new String[hostList.size()]; 723 final int[] ports = new int[hosts.length]; 724 725 for (int i=0; i < hosts.length; i++) 726 { 727 hosts[i] = hostList.get(i); 728 ports[i] = portList.get(i); 729 } 730 731 return new RoundRobinServerSet(hosts, ports, socketFactory, 732 getConnectionOptions()); 733 } 734 } 735 736 737 738 /** 739 * Creates the SSLUtil instance to use for secure communication. 740 * 741 * @return The SSLUtil instance to use for secure communication, or 742 * {@code null} if secure communication is not needed. 743 * 744 * @throws LDAPException If a problem occurs while creating the SSLUtil 745 * instance. 746 */ 747 public SSLUtil createSSLUtil() 748 throws LDAPException 749 { 750 return createSSLUtil(false); 751 } 752 753 754 755 /** 756 * Creates the SSLUtil instance to use for secure communication. 757 * 758 * @param force Indicates whether to create the SSLUtil object even if 759 * neither the "--useSSL" nor the "--useStartTLS" argument was 760 * provided. The key store and/or trust store paths must still 761 * have been provided. This may be useful for tools that 762 * accept SSL-based communication but do not themselves intend 763 * to perform SSL-based communication as an LDAP client. 764 * 765 * @return The SSLUtil instance to use for secure communication, or 766 * {@code null} if secure communication is not needed. 767 * 768 * @throws LDAPException If a problem occurs while creating the SSLUtil 769 * instance. 770 */ 771 public SSLUtil createSSLUtil(final boolean force) 772 throws LDAPException 773 { 774 if (force || useSSL.isPresent() || useStartTLS.isPresent()) 775 { 776 KeyManager keyManager = null; 777 if (keyStorePath.isPresent()) 778 { 779 char[] pw = null; 780 if (keyStorePassword.isPresent()) 781 { 782 pw = keyStorePassword.getValue().toCharArray(); 783 } 784 else if (keyStorePasswordFile.isPresent()) 785 { 786 try 787 { 788 pw = keyStorePasswordFile.getNonBlankFileLines().get(0). 789 toCharArray(); 790 } 791 catch (Exception e) 792 { 793 debugException(e); 794 throw new LDAPException(ResultCode.LOCAL_ERROR, 795 ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get( 796 getExceptionMessage(e)), e); 797 } 798 } 799 else if (promptForKeyStorePassword.isPresent()) 800 { 801 getOut().print(INFO_LDAP_TOOL_ENTER_KEY_STORE_PASSWORD.get()); 802 pw = StaticUtils.toUTF8String( 803 PasswordReader.readPassword()).toCharArray(); 804 getOut().println(); 805 } 806 807 try 808 { 809 keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw, 810 keyStoreFormat.getValue(), certificateNickname.getValue()); 811 } 812 catch (Exception e) 813 { 814 debugException(e); 815 throw new LDAPException(ResultCode.LOCAL_ERROR, 816 ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get( 817 getExceptionMessage(e)), e); 818 } 819 } 820 821 TrustManager trustManager; 822 if (trustAll.isPresent()) 823 { 824 trustManager = new TrustAllTrustManager(false); 825 } 826 else if (trustStorePath.isPresent()) 827 { 828 char[] pw = null; 829 if (trustStorePassword.isPresent()) 830 { 831 pw = trustStorePassword.getValue().toCharArray(); 832 } 833 else if (trustStorePasswordFile.isPresent()) 834 { 835 try 836 { 837 pw = trustStorePasswordFile.getNonBlankFileLines().get(0). 838 toCharArray(); 839 } 840 catch (Exception e) 841 { 842 debugException(e); 843 throw new LDAPException(ResultCode.LOCAL_ERROR, 844 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get( 845 getExceptionMessage(e)), e); 846 } 847 } 848 else if (promptForTrustStorePassword.isPresent()) 849 { 850 getOut().print(INFO_LDAP_TOOL_ENTER_TRUST_STORE_PASSWORD.get()); 851 pw = StaticUtils.toUTF8String( 852 PasswordReader.readPassword()).toCharArray(); 853 getOut().println(); 854 } 855 856 trustManager = new TrustStoreTrustManager(trustStorePath.getValue(), pw, 857 trustStoreFormat.getValue(), true); 858 } 859 else 860 { 861 trustManager = promptTrustManager.get(); 862 if (trustManager == null) 863 { 864 final PromptTrustManager m = new PromptTrustManager(); 865 promptTrustManager.compareAndSet(null, m); 866 trustManager = promptTrustManager.get(); 867 } 868 } 869 870 return new SSLUtil(keyManager, trustManager); 871 } 872 else 873 { 874 return null; 875 } 876 } 877 878 879 880 /** 881 * Creates the bind request to use to authenticate to the server. 882 * 883 * @return The bind request to use to authenticate to the server, or 884 * {@code null} if no bind should be performed. 885 * 886 * @throws LDAPException If a problem occurs while creating the bind 887 * request. 888 */ 889 public BindRequest createBindRequest() 890 throws LDAPException 891 { 892 if (! supportsAuthentication()) 893 { 894 return null; 895 } 896 897 final String pw; 898 if (bindPassword.isPresent()) 899 { 900 pw = bindPassword.getValue(); 901 } 902 else if (bindPasswordFile.isPresent()) 903 { 904 try 905 { 906 pw = bindPasswordFile.getNonBlankFileLines().get(0); 907 } 908 catch (Exception e) 909 { 910 debugException(e); 911 throw new LDAPException(ResultCode.LOCAL_ERROR, 912 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get( 913 getExceptionMessage(e)), e); 914 } 915 } 916 else if (promptForBindPassword.isPresent()) 917 { 918 getOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get()); 919 pw = StaticUtils.toUTF8String(PasswordReader.readPassword()); 920 getOut().println(); 921 } 922 else 923 { 924 pw = null; 925 } 926 927 if (saslOption.isPresent()) 928 { 929 final String dnStr; 930 if (bindDN.isPresent()) 931 { 932 dnStr = bindDN.getValue().toString(); 933 } 934 else 935 { 936 dnStr = null; 937 } 938 939 return SASLUtils.createBindRequest(dnStr, pw, null, 940 saslOption.getValues()); 941 } 942 else if (bindDN.isPresent()) 943 { 944 return new SimpleBindRequest(bindDN.getValue(), pw); 945 } 946 else 947 { 948 return null; 949 } 950 } 951}