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