001/* 002 * Copyright 2012-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2012-2017 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.concurrent.atomic.AtomicReference; 027import javax.net.SocketFactory; 028import javax.net.ssl.KeyManager; 029import javax.net.ssl.SSLSocketFactory; 030import javax.net.ssl.TrustManager; 031 032import com.unboundid.ldap.sdk.BindRequest; 033import com.unboundid.ldap.sdk.ExtendedResult; 034import com.unboundid.ldap.sdk.LDAPConnection; 035import com.unboundid.ldap.sdk.LDAPConnectionOptions; 036import com.unboundid.ldap.sdk.LDAPConnectionPool; 037import com.unboundid.ldap.sdk.LDAPException; 038import com.unboundid.ldap.sdk.PostConnectProcessor; 039import com.unboundid.ldap.sdk.ResultCode; 040import com.unboundid.ldap.sdk.ServerSet; 041import com.unboundid.ldap.sdk.SimpleBindRequest; 042import com.unboundid.ldap.sdk.SingleServerSet; 043import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor; 044import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; 045import com.unboundid.util.args.ArgumentException; 046import com.unboundid.util.args.ArgumentParser; 047import com.unboundid.util.args.BooleanArgument; 048import com.unboundid.util.args.DNArgument; 049import com.unboundid.util.args.FileArgument; 050import com.unboundid.util.args.IntegerArgument; 051import com.unboundid.util.args.StringArgument; 052import com.unboundid.util.ssl.KeyStoreKeyManager; 053import com.unboundid.util.ssl.PromptTrustManager; 054import com.unboundid.util.ssl.SSLUtil; 055import com.unboundid.util.ssl.TrustAllTrustManager; 056import com.unboundid.util.ssl.TrustStoreTrustManager; 057 058import static com.unboundid.util.UtilityMessages.*; 059 060 061 062/** 063 * This class provides a basis for developing command-line tools that have the 064 * ability to communicate with multiple directory servers, potentially with 065 * very different settings for each. For example, it may be used to help create 066 * tools that move or compare data from one server to another. 067 * <BR><BR> 068 * Each server will be identified by a prefix and/or suffix that will be added 069 * to the argument name (e.g., if the first server has a prefix of "source", 070 * then the "hostname" argument will actually be "sourceHostname"). The 071 * base names for the arguments this class supports include: 072 * <UL> 073 * <LI>hostname -- Specifies the address of the directory server. If this 074 * isn't specified, then a default of "localhost" will be used.</LI> 075 * <LI>port -- specifies the port number of the directory server. If this 076 * isn't specified, then a default port of 389 will be used.</LI> 077 * <LI>bindDN -- Specifies the DN to use to bind to the directory server using 078 * simple authentication. If this isn't specified, then simple 079 * authentication will not be performed.</LI> 080 * <LI>bindPassword -- Specifies the password to use when binding with simple 081 * authentication or a password-based SASL mechanism.</LI> 082 * <LI>bindPasswordFile -- Specifies the path to a file containing the 083 * password to use when binding with simple authentication or a 084 * password-based SASL mechanism.</LI> 085 * <LI>useSSL -- Indicates that communication with the server should be 086 * secured using SSL.</LI> 087 * <LI>useStartTLS -- Indicates that communication with the server should be 088 * secured using StartTLS.</LI> 089 * <LI>trustAll -- Indicates that the client should trust any certificate 090 * that the server presents to it.</LI> 091 * <LI>keyStorePath -- Specifies the path to the key store to use to obtain 092 * client certificates.</LI> 093 * <LI>keyStorePassword -- Specifies the password to use to access the 094 * contents of the key store.</LI> 095 * <LI>keyStorePasswordFile -- Specifies the path ot a file containing the 096 * password to use to access the contents of the key store.</LI> 097 * <LI>keyStoreFormat -- Specifies the format to use for the key store 098 * file.</LI> 099 * <LI>trustStorePath -- Specifies the path to the trust store to use to 100 * obtain client certificates.</LI> 101 * <LI>trustStorePassword -- Specifies the password to use to access the 102 * contents of the trust store.</LI> 103 * <LI>trustStorePasswordFile -- Specifies the path ot a file containing the 104 * password to use to access the contents of the trust store.</LI> 105 * <LI>trustStoreFormat -- Specifies the format to use for the trust store 106 * file.</LI> 107 * <LI>certNickname -- Specifies the nickname of the client certificate to 108 * use when performing SSL client authentication.</LI> 109 * <LI>saslOption -- Specifies a SASL option to use when performing SASL 110 * authentication.</LI> 111 * </UL> 112 * If SASL authentication is to be used, then a "mech" SASL option must be 113 * provided to specify the name of the SASL mechanism to use. Depending on the 114 * SASL mechanism, additional SASL options may be required or optional. 115 */ 116@Extensible() 117@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE) 118public abstract class MultiServerLDAPCommandLineTool 119 extends CommandLineTool 120{ 121 // The set of prefixes and suffixes that will be used for server names. 122 private final int numServers; 123 private final String[] serverNamePrefixes; 124 private final String[] serverNameSuffixes; 125 126 // The set of arguments used to hold information about connection properties. 127 private final BooleanArgument[] trustAll; 128 private final BooleanArgument[] useSSL; 129 private final BooleanArgument[] useStartTLS; 130 private final DNArgument[] bindDN; 131 private final FileArgument[] bindPasswordFile; 132 private final FileArgument[] keyStorePasswordFile; 133 private final FileArgument[] trustStorePasswordFile; 134 private final IntegerArgument[] port; 135 private final StringArgument[] bindPassword; 136 private final StringArgument[] certificateNickname; 137 private final StringArgument[] host; 138 private final StringArgument[] keyStoreFormat; 139 private final StringArgument[] keyStorePath; 140 private final StringArgument[] keyStorePassword; 141 private final StringArgument[] saslOption; 142 private final StringArgument[] trustStoreFormat; 143 private final StringArgument[] trustStorePath; 144 private final StringArgument[] trustStorePassword; 145 146 // Variables used when creating and authenticating connections. 147 private final BindRequest[] bindRequest; 148 private final ServerSet[] serverSet; 149 private final SSLSocketFactory[] startTLSSocketFactory; 150 151 // The prompt trust manager that will be shared by all connections created for 152 // which it is appropriate. This will allow them to benefit from the common 153 // cache. 154 private final AtomicReference<PromptTrustManager> promptTrustManager; 155 156 157 158 /** 159 * Creates a new instance of this multi-server LDAP command-line tool. At 160 * least one of the set of server name prefixes and suffixes must be 161 * non-{@code null}. If both are non-{@code null}, then they must have the 162 * same number of elements. 163 * 164 * @param outStream The output stream to use for standard output. 165 * It may be {@code System.out} for the JVM's 166 * default standard output stream, {@code null} if 167 * no output should be generated, or a custom 168 * output stream if the output should be sent to 169 * an alternate location. 170 * @param errStream The output stream to use for standard error. 171 * It may be {@code System.err} for the JVM's 172 * default standard error stream, {@code null} if 173 * no output should be generated, or a custom 174 * output stream if the output should be sent to 175 * an alternate location. 176 * @param serverNamePrefixes The prefixes to include before the names of 177 * each of the parameters to identify each server. 178 * It may be {@code null} if only suffixes should 179 * be used. 180 * @param serverNameSuffixes The suffixes to include after the names of each 181 * of the parameters to identify each server. It 182 * may be {@code null} if only prefixes should be 183 * used. 184 * 185 * @throws LDAPSDKUsageException If both the sets of server name prefixes 186 * and suffixes are {@code null} or empty, or 187 * if both sets are non-{@code null} but have 188 * different numbers of elements. 189 */ 190 public MultiServerLDAPCommandLineTool(final OutputStream outStream, 191 final OutputStream errStream, 192 final String[] serverNamePrefixes, 193 final String[] serverNameSuffixes) 194 throws LDAPSDKUsageException 195 { 196 super(outStream, errStream); 197 198 promptTrustManager = new AtomicReference<PromptTrustManager>(); 199 200 this.serverNamePrefixes = serverNamePrefixes; 201 this.serverNameSuffixes = serverNameSuffixes; 202 203 if (serverNamePrefixes == null) 204 { 205 if (serverNameSuffixes == null) 206 { 207 throw new LDAPSDKUsageException( 208 ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_NULL.get()); 209 } 210 else 211 { 212 numServers = serverNameSuffixes.length; 213 } 214 } 215 else 216 { 217 numServers = serverNamePrefixes.length; 218 219 if ((serverNameSuffixes != null) && 220 (serverNamePrefixes.length != serverNameSuffixes.length)) 221 { 222 throw new LDAPSDKUsageException( 223 ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_MISMATCH.get()); 224 } 225 } 226 227 if (numServers == 0) 228 { 229 throw new LDAPSDKUsageException( 230 ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_EMPTY.get()); 231 } 232 233 trustAll = new BooleanArgument[numServers]; 234 useSSL = new BooleanArgument[numServers]; 235 useStartTLS = new BooleanArgument[numServers]; 236 bindDN = new DNArgument[numServers]; 237 bindPasswordFile = new FileArgument[numServers]; 238 keyStorePasswordFile = new FileArgument[numServers]; 239 trustStorePasswordFile = new FileArgument[numServers]; 240 port = new IntegerArgument[numServers]; 241 bindPassword = new StringArgument[numServers]; 242 certificateNickname = new StringArgument[numServers]; 243 host = new StringArgument[numServers]; 244 keyStoreFormat = new StringArgument[numServers]; 245 keyStorePath = new StringArgument[numServers]; 246 keyStorePassword = new StringArgument[numServers]; 247 saslOption = new StringArgument[numServers]; 248 trustStoreFormat = new StringArgument[numServers]; 249 trustStorePath = new StringArgument[numServers]; 250 trustStorePassword = new StringArgument[numServers]; 251 252 bindRequest = new BindRequest[numServers]; 253 serverSet = new ServerSet[numServers]; 254 startTLSSocketFactory = new SSLSocketFactory[numServers]; 255 } 256 257 258 259 /** 260 * {@inheritDoc} 261 */ 262 @Override() 263 public final void addToolArguments(final ArgumentParser parser) 264 throws ArgumentException 265 { 266 for (int i=0; i < numServers; i++) 267 { 268 final StringBuilder groupNameBuffer = new StringBuilder(); 269 if (serverNamePrefixes != null) 270 { 271 final String prefix = serverNamePrefixes[i].replace('-', ' ').trim(); 272 groupNameBuffer.append(StaticUtils.capitalize(prefix, true)); 273 } 274 275 if (serverNameSuffixes != null) 276 { 277 if (groupNameBuffer.length() > 0) 278 { 279 groupNameBuffer.append(' '); 280 } 281 282 final String suffix = serverNameSuffixes[i].replace('-', ' ').trim(); 283 groupNameBuffer.append(StaticUtils.capitalize(suffix, true)); 284 } 285 286 groupNameBuffer.append(' '); 287 groupNameBuffer.append(INFO_MULTI_LDAP_TOOL_GROUP_CONN_AND_AUTH.get()); 288 final String groupName = groupNameBuffer.toString(); 289 290 291 host[i] = new StringArgument(null, genArgName(i, "hostname"), true, 1, 292 INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(), 293 INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost"); 294 host[i].setArgumentGroupName(groupName); 295 parser.addArgument(host[i]); 296 297 port[i] = new IntegerArgument(null, genArgName(i, "port"), true, 1, 298 INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(), 299 INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389); 300 port[i].setArgumentGroupName(groupName); 301 parser.addArgument(port[i]); 302 303 bindDN[i] = new DNArgument(null, genArgName(i, "bindDN"), false, 1, 304 INFO_LDAP_TOOL_PLACEHOLDER_DN.get(), 305 INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get()); 306 bindDN[i].setArgumentGroupName(groupName); 307 parser.addArgument(bindDN[i]); 308 309 bindPassword[i] = new StringArgument(null, genArgName(i, "bindPassword"), 310 false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(), 311 INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get()); 312 bindPassword[i].setSensitive(true); 313 bindPassword[i].setArgumentGroupName(groupName); 314 parser.addArgument(bindPassword[i]); 315 316 bindPasswordFile[i] = new FileArgument(null, 317 genArgName(i, "bindPasswordFile"), false, 1, 318 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 319 INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true, 320 false); 321 bindPasswordFile[i].setArgumentGroupName(groupName); 322 parser.addArgument(bindPasswordFile[i]); 323 324 useSSL[i] = new BooleanArgument(null, genArgName(i, "useSSL"), 1, 325 INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get()); 326 useSSL[i].setArgumentGroupName(groupName); 327 parser.addArgument(useSSL[i]); 328 329 useStartTLS[i] = new BooleanArgument(null, genArgName(i, "useStartTLS"), 330 1, INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get()); 331 useStartTLS[i].setArgumentGroupName(groupName); 332 parser.addArgument(useStartTLS[i]); 333 334 trustAll[i] = new BooleanArgument(null, genArgName(i, "trustAll"), 1, 335 INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get()); 336 trustAll[i].setArgumentGroupName(groupName); 337 parser.addArgument(trustAll[i]); 338 339 keyStorePath[i] = new StringArgument(null, genArgName(i, "keyStorePath"), 340 false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 341 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get()); 342 keyStorePath[i].setArgumentGroupName(groupName); 343 parser.addArgument(keyStorePath[i]); 344 345 keyStorePassword[i] = new StringArgument(null, 346 genArgName(i, "keyStorePassword"), false, 1, 347 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(), 348 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get()); 349 keyStorePassword[i].setSensitive(true); 350 keyStorePassword[i].setArgumentGroupName(groupName); 351 parser.addArgument(keyStorePassword[i]); 352 353 keyStorePasswordFile[i] = new FileArgument(null, 354 genArgName(i, "keyStorePasswordFile"), false, 1, 355 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 356 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get(), true, 357 true, true, false); 358 keyStorePasswordFile[i].setArgumentGroupName(groupName); 359 parser.addArgument(keyStorePasswordFile[i]); 360 361 keyStoreFormat[i] = new StringArgument(null, 362 genArgName(i, "keyStoreFormat"), false, 1, 363 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(), 364 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get()); 365 keyStoreFormat[i].setArgumentGroupName(groupName); 366 parser.addArgument(keyStoreFormat[i]); 367 368 trustStorePath[i] = new StringArgument(null, 369 genArgName(i, "trustStorePath"), false, 1, 370 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 371 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get()); 372 trustStorePath[i].setArgumentGroupName(groupName); 373 parser.addArgument(trustStorePath[i]); 374 375 trustStorePassword[i] = new StringArgument(null, 376 genArgName(i, "trustStorePassword"), false, 1, 377 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(), 378 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get()); 379 trustStorePassword[i].setSensitive(true); 380 trustStorePassword[i].setArgumentGroupName(groupName); 381 parser.addArgument(trustStorePassword[i]); 382 383 trustStorePasswordFile[i] = new FileArgument(null, 384 genArgName(i, "trustStorePasswordFile"), false, 1, 385 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 386 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get(), true, 387 true, true, false); 388 trustStorePasswordFile[i].setArgumentGroupName(groupName); 389 parser.addArgument(trustStorePasswordFile[i]); 390 391 trustStoreFormat[i] = new StringArgument(null, 392 genArgName(i, "trustStoreFormat"), false, 1, 393 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(), 394 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get()); 395 trustStoreFormat[i].setArgumentGroupName(groupName); 396 parser.addArgument(trustStoreFormat[i]); 397 398 certificateNickname[i] = new StringArgument(null, 399 genArgName(i, "certNickname"), false, 1, 400 INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(), 401 INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get()); 402 certificateNickname[i].setArgumentGroupName(groupName); 403 parser.addArgument(certificateNickname[i]); 404 405 saslOption[i] = new StringArgument(null, genArgName(i, "saslOption"), 406 false, 0, INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(), 407 INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get()); 408 saslOption[i].setArgumentGroupName(groupName); 409 parser.addArgument(saslOption[i]); 410 411 parser.addDependentArgumentSet(bindDN[i], bindPassword[i], 412 bindPasswordFile[i]); 413 414 parser.addExclusiveArgumentSet(useSSL[i], useStartTLS[i]); 415 parser.addExclusiveArgumentSet(bindPassword[i], bindPasswordFile[i]); 416 parser.addExclusiveArgumentSet(keyStorePassword[i], 417 keyStorePasswordFile[i]); 418 parser.addExclusiveArgumentSet(trustStorePassword[i], 419 trustStorePasswordFile[i]); 420 parser.addExclusiveArgumentSet(trustAll[i], trustStorePath[i]); 421 } 422 423 addNonLDAPArguments(parser); 424 } 425 426 427 428 /** 429 * Constructs the name to use for an argument from the given base and the 430 * appropriate prefix and suffix. 431 * 432 * @param index The index into the set of prefixes and suffixes. 433 * @param base The base name for the argument. 434 * 435 * @return The constructed argument name. 436 */ 437 private String genArgName(final int index, final String base) 438 { 439 final StringBuilder buffer = new StringBuilder(); 440 441 if (serverNamePrefixes != null) 442 { 443 buffer.append(serverNamePrefixes[index]); 444 445 if (base.equals("saslOption")) 446 { 447 buffer.append("SASLOption"); 448 } 449 else 450 { 451 buffer.append(StaticUtils.capitalize(base)); 452 } 453 } 454 else 455 { 456 buffer.append(base); 457 } 458 459 if (serverNameSuffixes != null) 460 { 461 buffer.append(serverNameSuffixes[index]); 462 } 463 464 return buffer.toString(); 465 } 466 467 468 469 /** 470 * Adds the arguments needed by this command-line tool to the provided 471 * argument parser which are not related to connecting or authenticating to 472 * the directory server. 473 * 474 * @param parser The argument parser to which the arguments should be added. 475 * 476 * @throws ArgumentException If a problem occurs while adding the arguments. 477 */ 478 public abstract void addNonLDAPArguments(final ArgumentParser parser) 479 throws ArgumentException; 480 481 482 483 /** 484 * {@inheritDoc} 485 */ 486 @Override() 487 public final void doExtendedArgumentValidation() 488 throws ArgumentException 489 { 490 doExtendedNonLDAPArgumentValidation(); 491 } 492 493 494 495 /** 496 * Performs any necessary processing that should be done to ensure that the 497 * provided set of command-line arguments were valid. This method will be 498 * called after the basic argument parsing has been performed and after all 499 * LDAP-specific argument validation has been processed, and immediately 500 * before the {@link CommandLineTool#doToolProcessing} method is invoked. 501 * 502 * @throws ArgumentException If there was a problem with the command-line 503 * arguments provided to this program. 504 */ 505 public void doExtendedNonLDAPArgumentValidation() 506 throws ArgumentException 507 { 508 // No processing will be performed by default. 509 } 510 511 512 513 /** 514 * Retrieves the connection options that should be used for connections that 515 * are created with this command line tool. Subclasses may override this 516 * method to use a custom set of connection options. 517 * 518 * @return The connection options that should be used for connections that 519 * are created with this command line tool. 520 */ 521 public LDAPConnectionOptions getConnectionOptions() 522 { 523 return new LDAPConnectionOptions(); 524 } 525 526 527 528 /** 529 * Retrieves a connection that may be used to communicate with the indicated 530 * directory server. 531 * <BR><BR> 532 * Note that this method is threadsafe and may be invoked by multiple threads 533 * accessing the same instance only while that instance is in the process of 534 * invoking the {@link #doToolProcessing} method. 535 * 536 * @param serverIndex The zero-based index of the server to which the 537 * connection should be established. 538 * 539 * @return A connection that may be used to communicate with the indicated 540 * directory server. 541 * 542 * @throws LDAPException If a problem occurs while creating the connection. 543 */ 544 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) 545 public final LDAPConnection getConnection(final int serverIndex) 546 throws LDAPException 547 { 548 final LDAPConnection connection = getUnauthenticatedConnection(serverIndex); 549 550 try 551 { 552 if (bindRequest[serverIndex] != null) 553 { 554 connection.bind(bindRequest[serverIndex]); 555 } 556 } 557 catch (LDAPException le) 558 { 559 Debug.debugException(le); 560 connection.close(); 561 throw le; 562 } 563 564 return connection; 565 } 566 567 568 569 /** 570 * Retrieves an unauthenticated connection that may be used to communicate 571 * with the indicated directory server. 572 * <BR><BR> 573 * Note that this method is threadsafe and may be invoked by multiple threads 574 * accessing the same instance only while that instance is in the process of 575 * invoking the {@link #doToolProcessing} method. 576 * 577 * @param serverIndex The zero-based index of the server to which the 578 * connection should be established. 579 * 580 * @return An unauthenticated connection that may be used to communicate with 581 * the indicated directory server. 582 * 583 * @throws LDAPException If a problem occurs while creating the connection. 584 */ 585 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) 586 public final LDAPConnection getUnauthenticatedConnection( 587 final int serverIndex) 588 throws LDAPException 589 { 590 if (serverSet[serverIndex] == null) 591 { 592 serverSet[serverIndex] = createServerSet(serverIndex); 593 bindRequest[serverIndex] = createBindRequest(serverIndex); 594 } 595 596 final LDAPConnection connection = serverSet[serverIndex].getConnection(); 597 598 if (useStartTLS[serverIndex].isPresent()) 599 { 600 try 601 { 602 final ExtendedResult extendedResult = 603 connection.processExtendedOperation(new StartTLSExtendedRequest( 604 startTLSSocketFactory[serverIndex])); 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 Debug.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 627 * indicated 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 serverIndex The zero-based index of the server to which the 634 * connection should be established. 635 * @param initialConnections The number of connections that should be 636 * initially established in the pool. 637 * @param maxConnections The maximum number of connections to maintain 638 * in the pool. 639 * 640 * @return A connection that may be used to communicate with the indicated 641 * directory server. 642 * 643 * @throws LDAPException If a problem occurs while creating the connection 644 * pool. 645 */ 646 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) 647 public final LDAPConnectionPool getConnectionPool( 648 final int serverIndex, 649 final int initialConnections, 650 final int maxConnections) 651 throws LDAPException 652 { 653 if (serverSet[serverIndex] == null) 654 { 655 serverSet[serverIndex] = createServerSet(serverIndex); 656 bindRequest[serverIndex] = createBindRequest(serverIndex); 657 } 658 659 PostConnectProcessor postConnectProcessor = null; 660 if (useStartTLS[serverIndex].isPresent()) 661 { 662 postConnectProcessor = new StartTLSPostConnectProcessor( 663 startTLSSocketFactory[serverIndex]); 664 } 665 666 return new LDAPConnectionPool(serverSet[serverIndex], 667 bindRequest[serverIndex], initialConnections, maxConnections, 668 postConnectProcessor); 669 } 670 671 672 673 /** 674 * Creates the server set to use when creating connections or connection 675 * pools. 676 * 677 * @param serverIndex The zero-based index of the server to which the 678 * connection should be established. 679 * 680 * @return The server set to use when creating connections or connection 681 * pools. 682 * 683 * @throws LDAPException If a problem occurs while creating the server set. 684 */ 685 public final ServerSet createServerSet(final int serverIndex) 686 throws LDAPException 687 { 688 final SSLUtil sslUtil = createSSLUtil(serverIndex); 689 690 SocketFactory socketFactory = null; 691 if (useSSL[serverIndex].isPresent()) 692 { 693 try 694 { 695 socketFactory = sslUtil.createSSLSocketFactory(); 696 } 697 catch (Exception e) 698 { 699 Debug.debugException(e); 700 throw new LDAPException(ResultCode.LOCAL_ERROR, 701 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get( 702 StaticUtils.getExceptionMessage(e)), e); 703 } 704 } 705 else if (useStartTLS[serverIndex].isPresent()) 706 { 707 try 708 { 709 startTLSSocketFactory[serverIndex] = sslUtil.createSSLSocketFactory(); 710 } 711 catch (Exception e) 712 { 713 Debug.debugException(e); 714 throw new LDAPException(ResultCode.LOCAL_ERROR, 715 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get( 716 StaticUtils.getExceptionMessage(e)), e); 717 } 718 } 719 720 return new SingleServerSet(host[serverIndex].getValue(), 721 port[serverIndex].getValue(), socketFactory, getConnectionOptions()); 722 } 723 724 725 726 /** 727 * Creates the SSLUtil instance to use for secure communication. 728 * 729 * @param serverIndex The zero-based index of the server to which the 730 * connection should be established. 731 * 732 * @return The SSLUtil instance to use for secure communication, or 733 * {@code null} if secure communication is not needed. 734 * 735 * @throws LDAPException If a problem occurs while creating the SSLUtil 736 * instance. 737 */ 738 public final SSLUtil createSSLUtil(final int serverIndex) 739 throws LDAPException 740 { 741 if (useSSL[serverIndex].isPresent() || useStartTLS[serverIndex].isPresent()) 742 { 743 KeyManager keyManager = null; 744 if (keyStorePath[serverIndex].isPresent()) 745 { 746 char[] pw = null; 747 if (keyStorePassword[serverIndex].isPresent()) 748 { 749 pw = keyStorePassword[serverIndex].getValue().toCharArray(); 750 } 751 else if (keyStorePasswordFile[serverIndex].isPresent()) 752 { 753 try 754 { 755 pw = keyStorePasswordFile[serverIndex].getNonBlankFileLines(). 756 get(0).toCharArray(); 757 } 758 catch (Exception e) 759 { 760 Debug.debugException(e); 761 throw new LDAPException(ResultCode.LOCAL_ERROR, 762 ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get( 763 StaticUtils.getExceptionMessage(e)), e); 764 } 765 } 766 767 try 768 { 769 keyManager = new KeyStoreKeyManager( 770 keyStorePath[serverIndex].getValue(), pw, 771 keyStoreFormat[serverIndex].getValue(), 772 certificateNickname[serverIndex].getValue()); 773 } 774 catch (Exception e) 775 { 776 Debug.debugException(e); 777 throw new LDAPException(ResultCode.LOCAL_ERROR, 778 ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get( 779 StaticUtils.getExceptionMessage(e)), e); 780 } 781 } 782 783 TrustManager trustManager; 784 if (trustAll[serverIndex].isPresent()) 785 { 786 trustManager = new TrustAllTrustManager(false); 787 } 788 else if (trustStorePath[serverIndex].isPresent()) 789 { 790 char[] pw = null; 791 if (trustStorePassword[serverIndex].isPresent()) 792 { 793 pw = trustStorePassword[serverIndex].getValue().toCharArray(); 794 } 795 else if (trustStorePasswordFile[serverIndex].isPresent()) 796 { 797 try 798 { 799 pw = trustStorePasswordFile[serverIndex].getNonBlankFileLines(). 800 get(0).toCharArray(); 801 } 802 catch (Exception e) 803 { 804 Debug.debugException(e); 805 throw new LDAPException(ResultCode.LOCAL_ERROR, 806 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get( 807 StaticUtils.getExceptionMessage(e)), e); 808 } 809 } 810 811 trustManager = new TrustStoreTrustManager( 812 trustStorePath[serverIndex].getValue(), pw, 813 trustStoreFormat[serverIndex].getValue(), true); 814 } 815 else 816 { 817 trustManager = promptTrustManager.get(); 818 if (trustManager == null) 819 { 820 final PromptTrustManager m = new PromptTrustManager(); 821 promptTrustManager.compareAndSet(null, m); 822 trustManager = promptTrustManager.get(); 823 } 824 } 825 826 return new SSLUtil(keyManager, trustManager); 827 } 828 else 829 { 830 return null; 831 } 832 } 833 834 835 836 /** 837 * Creates the bind request to use to authenticate to the indicated server. 838 * 839 * @param serverIndex The zero-based index of the server to which the 840 * connection should be established. 841 * 842 * @return The bind request to use to authenticate to the indicated server, 843 * or {@code null} if no bind should be performed. 844 * 845 * @throws LDAPException If a problem occurs while creating the bind 846 * request. 847 */ 848 public final BindRequest createBindRequest(final int serverIndex) 849 throws LDAPException 850 { 851 final String pw; 852 if (bindPassword[serverIndex].isPresent()) 853 { 854 pw = bindPassword[serverIndex].getValue(); 855 } 856 else if (bindPasswordFile[serverIndex].isPresent()) 857 { 858 try 859 { 860 pw = bindPasswordFile[serverIndex].getNonBlankFileLines().get(0); 861 } 862 catch (Exception e) 863 { 864 Debug.debugException(e); 865 throw new LDAPException(ResultCode.LOCAL_ERROR, 866 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get( 867 StaticUtils.getExceptionMessage(e)), e); 868 } 869 } 870 else 871 { 872 pw = null; 873 } 874 875 if (saslOption[serverIndex].isPresent()) 876 { 877 final String dnStr; 878 if (bindDN[serverIndex].isPresent()) 879 { 880 dnStr = bindDN[serverIndex].getValue().toString(); 881 } 882 else 883 { 884 dnStr = null; 885 } 886 887 return SASLUtils.createBindRequest(dnStr, pw, null, 888 saslOption[serverIndex].getValues()); 889 } 890 else if (bindDN[serverIndex].isPresent()) 891 { 892 return new SimpleBindRequest(bindDN[serverIndex].getValue(), pw); 893 } 894 else 895 { 896 return null; 897 } 898 } 899}