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