001/* 002 * Copyright 2012-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2012-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2012-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.util; 037 038 039 040import java.io.OutputStream; 041import java.util.concurrent.atomic.AtomicReference; 042import javax.net.SocketFactory; 043import javax.net.ssl.KeyManager; 044import javax.net.ssl.SSLSocketFactory; 045import javax.net.ssl.TrustManager; 046 047import com.unboundid.ldap.sdk.BindRequest; 048import com.unboundid.ldap.sdk.ExtendedResult; 049import com.unboundid.ldap.sdk.InternalSDKHelper; 050import com.unboundid.ldap.sdk.LDAPConnection; 051import com.unboundid.ldap.sdk.LDAPConnectionOptions; 052import com.unboundid.ldap.sdk.LDAPConnectionPool; 053import com.unboundid.ldap.sdk.LDAPException; 054import com.unboundid.ldap.sdk.PostConnectProcessor; 055import com.unboundid.ldap.sdk.ResultCode; 056import com.unboundid.ldap.sdk.ServerSet; 057import com.unboundid.ldap.sdk.SimpleBindRequest; 058import com.unboundid.ldap.sdk.SingleServerSet; 059import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor; 060import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; 061import com.unboundid.util.args.ArgumentException; 062import com.unboundid.util.args.ArgumentParser; 063import com.unboundid.util.args.BooleanArgument; 064import com.unboundid.util.args.DNArgument; 065import com.unboundid.util.args.FileArgument; 066import com.unboundid.util.args.IntegerArgument; 067import com.unboundid.util.args.StringArgument; 068import com.unboundid.util.ssl.AggregateTrustManager; 069import com.unboundid.util.ssl.KeyStoreKeyManager; 070import com.unboundid.util.ssl.SSLUtil; 071import com.unboundid.util.ssl.TrustAllTrustManager; 072import com.unboundid.util.ssl.TrustStoreTrustManager; 073 074import static com.unboundid.util.UtilityMessages.*; 075 076 077 078/** 079 * This class provides a basis for developing command-line tools that have the 080 * ability to communicate with multiple directory servers, potentially with 081 * very different settings for each. For example, it may be used to help create 082 * tools that move or compare data from one server to another. 083 * <BR><BR> 084 * Each server will be identified by a prefix and/or suffix that will be added 085 * to the argument name (e.g., if the first server has a prefix of "source", 086 * then the "hostname" argument will actually be "sourceHostname"). The 087 * base names for the arguments this class supports include: 088 * <UL> 089 * <LI>hostname -- Specifies the address of the directory server. If this 090 * isn't specified, then a default of "localhost" will be used.</LI> 091 * <LI>port -- specifies the port number of the directory server. If this 092 * isn't specified, then a default port of 389 will be used.</LI> 093 * <LI>bindDN -- Specifies the DN to use to bind to the directory server using 094 * simple authentication. If this isn't specified, then simple 095 * authentication will not be performed.</LI> 096 * <LI>bindPassword -- Specifies the password to use when binding with simple 097 * authentication or a password-based SASL mechanism.</LI> 098 * <LI>bindPasswordFile -- Specifies the path to a file containing the 099 * password to use when binding with simple authentication or a 100 * password-based SASL mechanism.</LI> 101 * <LI>useSSL -- Indicates that communication with the server should be 102 * secured using SSL.</LI> 103 * <LI>useStartTLS -- Indicates that communication with the server should be 104 * secured using StartTLS.</LI> 105 * <LI>"--defaultTrust" -- Indicates that the client should use a default, 106 * non-interactive mechanism for determining whether to trust any 107 * presented server certificate.</LI> 108 * <LI>trustAll -- Indicates that the client should trust any certificate 109 * that the server presents to it.</LI> 110 * <LI>keyStorePath -- Specifies the path to the key store to use to obtain 111 * client certificates.</LI> 112 * <LI>keyStorePassword -- Specifies the password to use to access the 113 * contents of the key store.</LI> 114 * <LI>keyStorePasswordFile -- Specifies the path ot a file containing the 115 * password to use to access the contents of the key store.</LI> 116 * <LI>keyStoreFormat -- Specifies the format to use for the key store 117 * file.</LI> 118 * <LI>trustStorePath -- Specifies the path to the trust store to use to 119 * obtain client certificates.</LI> 120 * <LI>trustStorePassword -- Specifies the password to use to access the 121 * contents of the trust store.</LI> 122 * <LI>trustStorePasswordFile -- Specifies the path ot a file containing the 123 * password to use to access the contents of the trust store.</LI> 124 * <LI>trustStoreFormat -- Specifies the format to use for the trust store 125 * file.</LI> 126 * <LI>certNickname -- Specifies the nickname of the client certificate to 127 * use when performing SSL client authentication.</LI> 128 * <LI>saslOption -- Specifies a SASL option to use when performing SASL 129 * authentication.</LI> 130 * </UL> 131 * If SASL authentication is to be used, then a "mech" SASL option must be 132 * provided to specify the name of the SASL mechanism to use. Depending on the 133 * SASL mechanism, additional SASL options may be required or optional. 134 */ 135@Extensible() 136@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE) 137public abstract class MultiServerLDAPCommandLineTool 138 extends CommandLineTool 139{ 140 // The set of prefixes and suffixes that will be used for server names. 141 private final int numServers; 142 @Nullable private final String[] serverNamePrefixes; 143 @Nullable private final String[] serverNameSuffixes; 144 145 // The set of arguments used to hold information about connection properties. 146 @NotNull private final BooleanArgument[] defaultTrust; 147 @NotNull private final BooleanArgument[] trustAll; 148 @NotNull private final BooleanArgument[] useSSL; 149 @NotNull private final BooleanArgument[] useStartTLS; 150 @NotNull private final DNArgument[] bindDN; 151 @NotNull private final FileArgument[] bindPasswordFile; 152 @NotNull private final FileArgument[] keyStorePasswordFile; 153 @NotNull private final FileArgument[] trustStorePasswordFile; 154 @NotNull private final IntegerArgument[] port; 155 @NotNull private final StringArgument[] bindPassword; 156 @NotNull private final StringArgument[] certificateNickname; 157 @NotNull private final StringArgument[] host; 158 @NotNull private final StringArgument[] keyStoreFormat; 159 @NotNull private final StringArgument[] keyStorePath; 160 @NotNull private final StringArgument[] keyStorePassword; 161 @NotNull private final StringArgument[] saslOption; 162 @NotNull private final StringArgument[] trustStoreFormat; 163 @NotNull private final StringArgument[] trustStorePath; 164 @NotNull private final StringArgument[] trustStorePassword; 165 166 // Variables used when creating and authenticating connections. 167 @NotNull private final BindRequest[] bindRequest; 168 @NotNull private final ServerSet[] serverSet; 169 @NotNull private final SSLSocketFactory[] startTLSSocketFactory; 170 171 // An atomic reference to an aggregate trust manager that will check a 172 // JVM-default set of trusted issuers, and then its own cache, before 173 // prompting the user about whether to trust the presented certificate chain. 174 // Re-using this trust manager will allow the tool to benefit from a common 175 // cache if multiple connections are needed. 176 @NotNull private final AtomicReference<AggregateTrustManager> 177 promptTrustManager; 178 179 180 181 /** 182 * Creates a new instance of this multi-server LDAP command-line tool. At 183 * least one of the set of server name prefixes and suffixes must be 184 * non-{@code null}. If both are non-{@code null}, then they must have the 185 * same number of elements. 186 * 187 * @param outStream The output stream to use for standard output. 188 * It may be {@code System.out} for the JVM's 189 * default standard output stream, {@code null} if 190 * no output should be generated, or a custom 191 * output stream if the output should be sent to 192 * an alternate location. 193 * @param errStream The output stream to use for standard error. 194 * It may be {@code System.err} for the JVM's 195 * default standard error stream, {@code null} if 196 * no output should be generated, or a custom 197 * output stream if the output should be sent to 198 * an alternate location. 199 * @param serverNamePrefixes The prefixes to include before the names of 200 * each of the parameters to identify each server. 201 * It may be {@code null} if only suffixes should 202 * be used. 203 * @param serverNameSuffixes The suffixes to include after the names of each 204 * of the parameters to identify each server. It 205 * may be {@code null} if only prefixes should be 206 * used. 207 * 208 * @throws LDAPSDKUsageException If both the sets of server name prefixes 209 * and suffixes are {@code null} or empty, or 210 * if both sets are non-{@code null} but have 211 * different numbers of elements. 212 */ 213 public MultiServerLDAPCommandLineTool(@Nullable final OutputStream outStream, 214 @Nullable final OutputStream errStream, 215 @Nullable final String[] serverNamePrefixes, 216 @Nullable final String[] serverNameSuffixes) 217 throws LDAPSDKUsageException 218 { 219 super(outStream, errStream); 220 221 promptTrustManager = new AtomicReference<>(); 222 223 this.serverNamePrefixes = serverNamePrefixes; 224 this.serverNameSuffixes = serverNameSuffixes; 225 226 if (serverNamePrefixes == null) 227 { 228 if (serverNameSuffixes == null) 229 { 230 throw new LDAPSDKUsageException( 231 ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_NULL.get()); 232 } 233 else 234 { 235 numServers = serverNameSuffixes.length; 236 } 237 } 238 else 239 { 240 numServers = serverNamePrefixes.length; 241 242 if ((serverNameSuffixes != null) && 243 (serverNamePrefixes.length != serverNameSuffixes.length)) 244 { 245 throw new LDAPSDKUsageException( 246 ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_MISMATCH.get()); 247 } 248 } 249 250 if (numServers == 0) 251 { 252 throw new LDAPSDKUsageException( 253 ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_EMPTY.get()); 254 } 255 256 defaultTrust = new BooleanArgument[numServers]; 257 trustAll = new BooleanArgument[numServers]; 258 useSSL = new BooleanArgument[numServers]; 259 useStartTLS = new BooleanArgument[numServers]; 260 bindDN = new DNArgument[numServers]; 261 bindPasswordFile = new FileArgument[numServers]; 262 keyStorePasswordFile = new FileArgument[numServers]; 263 trustStorePasswordFile = new FileArgument[numServers]; 264 port = new IntegerArgument[numServers]; 265 bindPassword = new StringArgument[numServers]; 266 certificateNickname = new StringArgument[numServers]; 267 host = new StringArgument[numServers]; 268 keyStoreFormat = new StringArgument[numServers]; 269 keyStorePath = new StringArgument[numServers]; 270 keyStorePassword = new StringArgument[numServers]; 271 saslOption = new StringArgument[numServers]; 272 trustStoreFormat = new StringArgument[numServers]; 273 trustStorePath = new StringArgument[numServers]; 274 trustStorePassword = new StringArgument[numServers]; 275 276 bindRequest = new BindRequest[numServers]; 277 serverSet = new ServerSet[numServers]; 278 startTLSSocketFactory = new SSLSocketFactory[numServers]; 279 } 280 281 282 283 /** 284 * {@inheritDoc} 285 */ 286 @Override() 287 public final void addToolArguments(@NotNull final ArgumentParser parser) 288 throws ArgumentException 289 { 290 for (int i=0; i < numServers; i++) 291 { 292 final StringBuilder groupNameBuffer = new StringBuilder(); 293 if (serverNamePrefixes != null) 294 { 295 final String prefix = serverNamePrefixes[i].replace('-', ' ').trim(); 296 groupNameBuffer.append(StaticUtils.capitalize(prefix, true)); 297 } 298 299 if (serverNameSuffixes != null) 300 { 301 if (groupNameBuffer.length() > 0) 302 { 303 groupNameBuffer.append(' '); 304 } 305 306 final String suffix = serverNameSuffixes[i].replace('-', ' ').trim(); 307 groupNameBuffer.append(StaticUtils.capitalize(suffix, true)); 308 } 309 310 groupNameBuffer.append(' '); 311 groupNameBuffer.append(INFO_MULTI_LDAP_TOOL_GROUP_CONN_AND_AUTH.get()); 312 final String groupName = groupNameBuffer.toString(); 313 314 315 host[i] = new StringArgument(null, genArgName(i, "hostname"), true, 1, 316 INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(), 317 INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost"); 318 if (includeAlternateLongIdentifiers()) 319 { 320 host[i].addLongIdentifier(genDashedArgName(i, "hostname"), true); 321 host[i].addLongIdentifier(genArgName(i, "host"), true); 322 host[i].addLongIdentifier(genDashedArgName(i, "host"), true); 323 host[i].addLongIdentifier(genArgName(i, "address"), true); 324 host[i].addLongIdentifier(genDashedArgName(i, "address"), true); 325 } 326 host[i].setArgumentGroupName(groupName); 327 parser.addArgument(host[i]); 328 329 port[i] = new IntegerArgument(null, genArgName(i, "port"), true, 1, 330 INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(), 331 INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65_535, 389); 332 port[i].setArgumentGroupName(groupName); 333 if (includeAlternateLongIdentifiers()) 334 { 335 port[i].addLongIdentifier(genDashedArgName(i, "port"), true); 336 } 337 parser.addArgument(port[i]); 338 339 bindDN[i] = new DNArgument(null, genArgName(i, "bindDN"), false, 1, 340 INFO_LDAP_TOOL_PLACEHOLDER_DN.get(), 341 INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get()); 342 if (includeAlternateLongIdentifiers()) 343 { 344 bindDN[i].addLongIdentifier(genDashedArgName(i, "bind-dn"), true); 345 } 346 bindDN[i].setArgumentGroupName(groupName); 347 parser.addArgument(bindDN[i]); 348 349 bindPassword[i] = new StringArgument(null, genArgName(i, "bindPassword"), 350 false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(), 351 INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get()); 352 if (includeAlternateLongIdentifiers()) 353 { 354 bindPassword[i].addLongIdentifier(genDashedArgName(i, "bind-password"), 355 true); 356 } 357 bindPassword[i].setSensitive(true); 358 bindPassword[i].setArgumentGroupName(groupName); 359 parser.addArgument(bindPassword[i]); 360 361 bindPasswordFile[i] = new FileArgument(null, 362 genArgName(i, "bindPasswordFile"), false, 1, 363 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 364 INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true, 365 false); 366 if (includeAlternateLongIdentifiers()) 367 { 368 bindPasswordFile[i].addLongIdentifier(genDashedArgName(i, 369 "bind-password-file"), true); 370 } 371 bindPasswordFile[i].setArgumentGroupName(groupName); 372 parser.addArgument(bindPasswordFile[i]); 373 374 useSSL[i] = new BooleanArgument(null, genArgName(i, "useSSL"), 1, 375 INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get()); 376 if (includeAlternateLongIdentifiers()) 377 { 378 useSSL[i].addLongIdentifier(genDashedArgName(i, "use-ssl"), true); 379 } 380 useSSL[i].setArgumentGroupName(groupName); 381 parser.addArgument(useSSL[i]); 382 383 useStartTLS[i] = new BooleanArgument(null, genArgName(i, "useStartTLS"), 384 1, INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get()); 385 if (includeAlternateLongIdentifiers()) 386 { 387 useStartTLS[i].addLongIdentifier(genDashedArgName(i, "use-start-tls"), 388 true); 389 } 390 useStartTLS[i].setArgumentGroupName(groupName); 391 parser.addArgument(useStartTLS[i]); 392 393 final String defaultTrustArgDesc; 394 if (InternalSDKHelper.getPingIdentityServerRoot() != null) 395 { 396 defaultTrustArgDesc = 397 INFO_LDAP_TOOL_DESCRIPTION_DEFAULT_TRUST_WITH_PING_DS.get(); 398 } 399 else 400 { 401 defaultTrustArgDesc = 402 INFO_LDAP_TOOL_DESCRIPTION_DEFAULT_TRUST_WITHOUT_PING_DS.get(); 403 } 404 defaultTrust[i] = new BooleanArgument(null, 405 genArgName(i, "defaultTrust"), 1, defaultTrustArgDesc); 406 defaultTrust[i].setArgumentGroupName(groupName); 407 if (includeAlternateLongIdentifiers()) 408 { 409 defaultTrust[i].addLongIdentifier( 410 genDashedArgName(i, "default-trust"), true); 411 defaultTrust[i].addLongIdentifier( 412 genArgName(i, "useDefaultTrust"), true); 413 defaultTrust[i].addLongIdentifier( 414 genDashedArgName(i, "use-default-trust"), true); 415 } 416 parser.addArgument(defaultTrust[i]); 417 418 trustAll[i] = new BooleanArgument(null, genArgName(i, "trustAll"), 1, 419 INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get()); 420 if (includeAlternateLongIdentifiers()) 421 { 422 trustAll[i].addLongIdentifier(genDashedArgName(i, "trust-all"), true); 423 } 424 trustAll[i].setArgumentGroupName(groupName); 425 parser.addArgument(trustAll[i]); 426 427 keyStorePath[i] = new StringArgument(null, genArgName(i, "keyStorePath"), 428 false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 429 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get()); 430 if (includeAlternateLongIdentifiers()) 431 { 432 keyStorePath[i].addLongIdentifier(genDashedArgName(i, "key-store-path"), 433 true); 434 } 435 keyStorePath[i].setArgumentGroupName(groupName); 436 parser.addArgument(keyStorePath[i]); 437 438 keyStorePassword[i] = new StringArgument(null, 439 genArgName(i, "keyStorePassword"), false, 1, 440 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(), 441 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get()); 442 if (includeAlternateLongIdentifiers()) 443 { 444 keyStorePassword[i].addLongIdentifier(genDashedArgName(i, 445 "key-store-password"), true); 446 } 447 keyStorePassword[i].setSensitive(true); 448 keyStorePassword[i].setArgumentGroupName(groupName); 449 parser.addArgument(keyStorePassword[i]); 450 451 keyStorePasswordFile[i] = new FileArgument(null, 452 genArgName(i, "keyStorePasswordFile"), false, 1, 453 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 454 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get(), true, 455 true, true, false); 456 if (includeAlternateLongIdentifiers()) 457 { 458 keyStorePasswordFile[i].addLongIdentifier(genDashedArgName(i, 459 "key-store-password-file"), true); 460 } 461 keyStorePasswordFile[i].setArgumentGroupName(groupName); 462 parser.addArgument(keyStorePasswordFile[i]); 463 464 keyStoreFormat[i] = new StringArgument(null, 465 genArgName(i, "keyStoreFormat"), false, 1, 466 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(), 467 INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get()); 468 if (includeAlternateLongIdentifiers()) 469 { 470 keyStoreFormat[i].addLongIdentifier(genDashedArgName(i, 471 "key-store-format"), true); 472 keyStoreFormat[i].addLongIdentifier(genArgName(i, "keyStoreType"), 473 true); 474 keyStoreFormat[i].addLongIdentifier(genDashedArgName(i, 475 "key-store-type"), true); 476 } 477 keyStoreFormat[i].setArgumentGroupName(groupName); 478 parser.addArgument(keyStoreFormat[i]); 479 480 trustStorePath[i] = new StringArgument(null, 481 genArgName(i, "trustStorePath"), false, 1, 482 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 483 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get()); 484 if (includeAlternateLongIdentifiers()) 485 { 486 trustStorePath[i].addLongIdentifier(genDashedArgName(i, 487 "trust-store-path"), true); 488 } 489 trustStorePath[i].setArgumentGroupName(groupName); 490 parser.addArgument(trustStorePath[i]); 491 492 trustStorePassword[i] = new StringArgument(null, 493 genArgName(i, "trustStorePassword"), false, 1, 494 INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(), 495 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get()); 496 if (includeAlternateLongIdentifiers()) 497 { 498 trustStorePassword[i].addLongIdentifier(genDashedArgName(i, 499 "trust-store-password"), true); 500 } 501 trustStorePassword[i].setSensitive(true); 502 trustStorePassword[i].setArgumentGroupName(groupName); 503 parser.addArgument(trustStorePassword[i]); 504 505 trustStorePasswordFile[i] = new FileArgument(null, 506 genArgName(i, "trustStorePasswordFile"), false, 1, 507 INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(), 508 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get(), true, 509 true, true, false); 510 if (includeAlternateLongIdentifiers()) 511 { 512 trustStorePasswordFile[i].addLongIdentifier(genDashedArgName(i, 513 "trust-store-password-file"), true); 514 } 515 trustStorePasswordFile[i].setArgumentGroupName(groupName); 516 parser.addArgument(trustStorePasswordFile[i]); 517 518 trustStoreFormat[i] = new StringArgument(null, 519 genArgName(i, "trustStoreFormat"), false, 1, 520 INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(), 521 INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get()); 522 if (includeAlternateLongIdentifiers()) 523 { 524 trustStoreFormat[i].addLongIdentifier(genDashedArgName(i, 525 "trust-store-format"), true); 526 trustStoreFormat[i].addLongIdentifier(genArgName(i, "trustStoreType"), 527 true); 528 trustStoreFormat[i].addLongIdentifier(genDashedArgName(i, 529 "trust-store-type"), true); 530 } 531 trustStoreFormat[i].setArgumentGroupName(groupName); 532 parser.addArgument(trustStoreFormat[i]); 533 534 certificateNickname[i] = new StringArgument(null, 535 genArgName(i, "certNickname"), false, 1, 536 INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(), 537 INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get()); 538 if (includeAlternateLongIdentifiers()) 539 { 540 certificateNickname[i].addLongIdentifier(genDashedArgName(i, 541 "cert-nickname"), true); 542 certificateNickname[i].addLongIdentifier(genArgName(i, 543 "certificateNickname"), true); 544 certificateNickname[i].addLongIdentifier(genDashedArgName(i, 545 "certificate-nickname"), true); 546 } 547 certificateNickname[i].setArgumentGroupName(groupName); 548 parser.addArgument(certificateNickname[i]); 549 550 saslOption[i] = new StringArgument(null, genArgName(i, "saslOption"), 551 false, 0, INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(), 552 INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get()); 553 if (includeAlternateLongIdentifiers()) 554 { 555 saslOption[i].addLongIdentifier(genDashedArgName(i, "sasl-option"), 556 true); 557 } 558 saslOption[i].setArgumentGroupName(groupName); 559 parser.addArgument(saslOption[i]); 560 561 parser.addDependentArgumentSet(bindDN[i], bindPassword[i], 562 bindPasswordFile[i]); 563 564 parser.addExclusiveArgumentSet(useSSL[i], useStartTLS[i]); 565 parser.addExclusiveArgumentSet(bindPassword[i], bindPasswordFile[i]); 566 parser.addExclusiveArgumentSet(keyStorePassword[i], 567 keyStorePasswordFile[i]); 568 parser.addExclusiveArgumentSet(trustStorePassword[i], 569 trustStorePasswordFile[i]); 570 parser.addExclusiveArgumentSet(trustAll[i], trustStorePath[i]); 571 parser.addExclusiveArgumentSet(trustAll[i], defaultTrust[i]); 572 } 573 574 addNonLDAPArguments(parser); 575 } 576 577 578 579 /** 580 * Constructs the name to use for an argument from the given base and the 581 * appropriate prefix and suffix. 582 * 583 * @param index The index into the set of prefixes and suffixes. 584 * @param base The base name for the argument. 585 * 586 * @return The constructed argument name. 587 */ 588 @NotNull() 589 private String genArgName(final int index, @NotNull final String base) 590 { 591 final StringBuilder buffer = new StringBuilder(); 592 593 if (serverNamePrefixes != null) 594 { 595 buffer.append(serverNamePrefixes[index]); 596 597 if (base.equals("saslOption")) 598 { 599 buffer.append("SASLOption"); 600 } 601 else 602 { 603 buffer.append(StaticUtils.capitalize(base)); 604 } 605 } 606 else 607 { 608 buffer.append(base); 609 } 610 611 if (serverNameSuffixes != null) 612 { 613 buffer.append(serverNameSuffixes[index]); 614 } 615 616 return buffer.toString(); 617 } 618 619 620 621 /** 622 * Constructs the name to use for an argument from the given base and the 623 * appropriate prefix and suffix. In this case, dashes will be used to 624 * separate words rather than capitalization. 625 * 626 * @param index The index into the set of prefixes and suffixes. 627 * @param base The base name for the argument. 628 * 629 * @return The constructed argument name. 630 */ 631 @NotNull() 632 private String genDashedArgName(final int index, @NotNull final String base) 633 { 634 final StringBuilder buffer = new StringBuilder(); 635 636 if (serverNamePrefixes != null) 637 { 638 buffer.append(serverNamePrefixes[index]); 639 buffer.append('-'); 640 } 641 642 buffer.append(base); 643 644 if (serverNameSuffixes != null) 645 { 646 buffer.append('-'); 647 buffer.append(serverNameSuffixes[index]); 648 } 649 650 return buffer.toString(); 651 } 652 653 654 655 /** 656 * Indicates whether the LDAP-specific arguments should include alternate 657 * versions of all long identifiers that consist of multiple words so that 658 * they are available in both camelCase and dash-separated versions. 659 * 660 * @return {@code true} if this tool should provide multiple versions of 661 * long identifiers for LDAP-specific arguments, or {@code false} if 662 * not. 663 */ 664 protected boolean includeAlternateLongIdentifiers() 665 { 666 return false; 667 } 668 669 670 671 /** 672 * Adds the arguments needed by this command-line tool to the provided 673 * argument parser which are not related to connecting or authenticating to 674 * the directory server. 675 * 676 * @param parser The argument parser to which the arguments should be added. 677 * 678 * @throws ArgumentException If a problem occurs while adding the arguments. 679 */ 680 public abstract void addNonLDAPArguments(@NotNull ArgumentParser parser) 681 throws ArgumentException; 682 683 684 685 /** 686 * {@inheritDoc} 687 */ 688 @Override() 689 public final void doExtendedArgumentValidation() 690 throws ArgumentException 691 { 692 doExtendedNonLDAPArgumentValidation(); 693 } 694 695 696 697 /** 698 * Performs any necessary processing that should be done to ensure that the 699 * provided set of command-line arguments were valid. This method will be 700 * called after the basic argument parsing has been performed and after all 701 * LDAP-specific argument validation has been processed, and immediately 702 * before the {@link CommandLineTool#doToolProcessing} method is invoked. 703 * 704 * @throws ArgumentException If there was a problem with the command-line 705 * arguments provided to this program. 706 */ 707 public void doExtendedNonLDAPArgumentValidation() 708 throws ArgumentException 709 { 710 // No processing will be performed by default. 711 } 712 713 714 715 /** 716 * Retrieves the connection options that should be used for connections that 717 * are created with this command line tool. Subclasses may override this 718 * method to use a custom set of connection options. 719 * 720 * @return The connection options that should be used for connections that 721 * are created with this command line tool. 722 */ 723 @NotNull() 724 public LDAPConnectionOptions getConnectionOptions() 725 { 726 return new LDAPConnectionOptions(); 727 } 728 729 730 731 /** 732 * Retrieves a connection that may be used to communicate with the indicated 733 * directory server. 734 * <BR><BR> 735 * Note that this method is threadsafe and may be invoked by multiple threads 736 * accessing the same instance only while that instance is in the process of 737 * invoking the {@link #doToolProcessing} method. 738 * 739 * @param serverIndex The zero-based index of the server to which the 740 * connection should be established. 741 * 742 * @return A connection that may be used to communicate with the indicated 743 * directory server. 744 * 745 * @throws LDAPException If a problem occurs while creating the connection. 746 */ 747 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) 748 @NotNull() 749 public final LDAPConnection getConnection(final int serverIndex) 750 throws LDAPException 751 { 752 final LDAPConnection connection = getUnauthenticatedConnection(serverIndex); 753 754 try 755 { 756 if (bindRequest[serverIndex] != null) 757 { 758 connection.bind(bindRequest[serverIndex]); 759 } 760 } 761 catch (final LDAPException le) 762 { 763 Debug.debugException(le); 764 connection.close(); 765 throw le; 766 } 767 768 return connection; 769 } 770 771 772 773 /** 774 * Retrieves an unauthenticated connection that may be used to communicate 775 * with the indicated directory server. 776 * <BR><BR> 777 * Note that this method is threadsafe and may be invoked by multiple threads 778 * accessing the same instance only while that instance is in the process of 779 * invoking the {@link #doToolProcessing} method. 780 * 781 * @param serverIndex The zero-based index of the server to which the 782 * connection should be established. 783 * 784 * @return An unauthenticated connection that may be used to communicate with 785 * the indicated directory server. 786 * 787 * @throws LDAPException If a problem occurs while creating the connection. 788 */ 789 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) 790 @NotNull() 791 public final LDAPConnection getUnauthenticatedConnection( 792 final int serverIndex) 793 throws LDAPException 794 { 795 if (serverSet[serverIndex] == null) 796 { 797 serverSet[serverIndex] = createServerSet(serverIndex); 798 bindRequest[serverIndex] = createBindRequest(serverIndex); 799 } 800 801 final LDAPConnection connection = serverSet[serverIndex].getConnection(); 802 803 if (useStartTLS[serverIndex].isPresent()) 804 { 805 try 806 { 807 final ExtendedResult extendedResult = 808 connection.processExtendedOperation(new StartTLSExtendedRequest( 809 startTLSSocketFactory[serverIndex])); 810 if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS)) 811 { 812 throw new LDAPException(extendedResult.getResultCode(), 813 ERR_LDAP_TOOL_START_TLS_FAILED.get( 814 extendedResult.getDiagnosticMessage())); 815 } 816 } 817 catch (final LDAPException le) 818 { 819 Debug.debugException(le); 820 connection.close(); 821 throw le; 822 } 823 } 824 825 return connection; 826 } 827 828 829 830 /** 831 * Retrieves a connection pool that may be used to communicate with the 832 * indicated directory server. 833 * <BR><BR> 834 * Note that this method is threadsafe and may be invoked by multiple threads 835 * accessing the same instance only while that instance is in the process of 836 * invoking the {@link #doToolProcessing} method. 837 * 838 * @param serverIndex The zero-based index of the server to which the 839 * connection should be established. 840 * @param initialConnections The number of connections that should be 841 * initially established in the pool. 842 * @param maxConnections The maximum number of connections to maintain 843 * in the pool. 844 * 845 * @return A connection that may be used to communicate with the indicated 846 * directory server. 847 * 848 * @throws LDAPException If a problem occurs while creating the connection 849 * pool. 850 */ 851 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) 852 @NotNull() 853 public final LDAPConnectionPool getConnectionPool( 854 final int serverIndex, 855 final int initialConnections, 856 final int maxConnections) 857 throws LDAPException 858 { 859 if (serverSet[serverIndex] == null) 860 { 861 serverSet[serverIndex] = createServerSet(serverIndex); 862 bindRequest[serverIndex] = createBindRequest(serverIndex); 863 } 864 865 PostConnectProcessor postConnectProcessor = null; 866 if (useStartTLS[serverIndex].isPresent()) 867 { 868 postConnectProcessor = new StartTLSPostConnectProcessor( 869 startTLSSocketFactory[serverIndex]); 870 } 871 872 return new LDAPConnectionPool(serverSet[serverIndex], 873 bindRequest[serverIndex], initialConnections, maxConnections, 874 postConnectProcessor); 875 } 876 877 878 879 /** 880 * Creates the server set to use when creating connections or connection 881 * pools. 882 * 883 * @param serverIndex The zero-based index of the server to which the 884 * connection should be established. 885 * 886 * @return The server set to use when creating connections or connection 887 * pools. 888 * 889 * @throws LDAPException If a problem occurs while creating the server set. 890 */ 891 @NotNull() 892 public final ServerSet createServerSet(final int serverIndex) 893 throws LDAPException 894 { 895 final SSLUtil sslUtil = createSSLUtil(serverIndex); 896 897 SocketFactory socketFactory = null; 898 if (useSSL[serverIndex].isPresent()) 899 { 900 try 901 { 902 socketFactory = sslUtil.createSSLSocketFactory(); 903 } 904 catch (final Exception e) 905 { 906 Debug.debugException(e); 907 throw new LDAPException(ResultCode.LOCAL_ERROR, 908 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get( 909 StaticUtils.getExceptionMessage(e)), e); 910 } 911 } 912 else if (useStartTLS[serverIndex].isPresent()) 913 { 914 try 915 { 916 startTLSSocketFactory[serverIndex] = sslUtil.createSSLSocketFactory(); 917 } 918 catch (final Exception e) 919 { 920 Debug.debugException(e); 921 throw new LDAPException(ResultCode.LOCAL_ERROR, 922 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get( 923 StaticUtils.getExceptionMessage(e)), e); 924 } 925 } 926 927 return new SingleServerSet(host[serverIndex].getValue(), 928 port[serverIndex].getValue(), socketFactory, getConnectionOptions()); 929 } 930 931 932 933 /** 934 * Creates the SSLUtil instance to use for secure communication. 935 * 936 * @param serverIndex The zero-based index of the server to which the 937 * connection should be established. 938 * 939 * @return The SSLUtil instance to use for secure communication, or 940 * {@code null} if secure communication is not needed. 941 * 942 * @throws LDAPException If a problem occurs while creating the SSLUtil 943 * instance. 944 */ 945 @Nullable() 946 public final SSLUtil createSSLUtil(final int serverIndex) 947 throws LDAPException 948 { 949 if (useSSL[serverIndex].isPresent() || useStartTLS[serverIndex].isPresent()) 950 { 951 KeyManager keyManager = null; 952 if (keyStorePath[serverIndex].isPresent()) 953 { 954 char[] pw = null; 955 if (keyStorePassword[serverIndex].isPresent()) 956 { 957 pw = keyStorePassword[serverIndex].getValue().toCharArray(); 958 } 959 else if (keyStorePasswordFile[serverIndex].isPresent()) 960 { 961 try 962 { 963 pw = getPasswordFileReader().readPassword( 964 keyStorePasswordFile[serverIndex].getValue()); 965 } 966 catch (final Exception e) 967 { 968 Debug.debugException(e); 969 throw new LDAPException(ResultCode.LOCAL_ERROR, 970 ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get( 971 StaticUtils.getExceptionMessage(e)), e); 972 } 973 } 974 975 try 976 { 977 keyManager = new KeyStoreKeyManager( 978 keyStorePath[serverIndex].getValue(), pw, 979 keyStoreFormat[serverIndex].getValue(), 980 certificateNickname[serverIndex].getValue(), true); 981 } 982 catch (final Exception e) 983 { 984 Debug.debugException(e); 985 throw new LDAPException(ResultCode.LOCAL_ERROR, 986 ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get( 987 StaticUtils.getExceptionMessage(e)), e); 988 } 989 } 990 991 TrustManager tm; 992 if (trustAll[serverIndex].isPresent()) 993 { 994 tm = new TrustAllTrustManager(false); 995 } 996 else if (trustStorePath[serverIndex].isPresent()) 997 { 998 char[] pw = null; 999 if (trustStorePassword[serverIndex].isPresent()) 1000 { 1001 pw = trustStorePassword[serverIndex].getValue().toCharArray(); 1002 } 1003 else if (trustStorePasswordFile[serverIndex].isPresent()) 1004 { 1005 try 1006 { 1007 pw = getPasswordFileReader().readPassword( 1008 trustStorePasswordFile[serverIndex].getValue()); 1009 } 1010 catch (final Exception e) 1011 { 1012 Debug.debugException(e); 1013 throw new LDAPException(ResultCode.LOCAL_ERROR, 1014 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get( 1015 StaticUtils.getExceptionMessage(e)), e); 1016 } 1017 } 1018 1019 final TrustStoreTrustManager trustStoreTrustManager = 1020 new TrustStoreTrustManager(trustStorePath[serverIndex].getValue(), 1021 pw, trustStoreFormat[serverIndex].getValue(), true); 1022 if (defaultTrust[serverIndex].isPresent()) 1023 { 1024 tm = InternalSDKHelper.getPreferredNonInteractiveTrustManagerChain( 1025 trustStoreTrustManager); 1026 } 1027 else 1028 { 1029 tm = trustStoreTrustManager; 1030 } 1031 } 1032 else if (defaultTrust[serverIndex].isPresent()) 1033 { 1034 tm = InternalSDKHelper.getPreferredNonInteractiveTrustManagerChain(); 1035 } 1036 else 1037 { 1038 tm = promptTrustManager.get(); 1039 if (tm == null) 1040 { 1041 final AggregateTrustManager atm = 1042 InternalSDKHelper.getPreferredPromptTrustManagerChain(null); 1043 if (promptTrustManager.compareAndSet(null, atm)) 1044 { 1045 tm = atm; 1046 } 1047 else 1048 { 1049 tm = promptTrustManager.get(); 1050 } 1051 } 1052 } 1053 1054 return new SSLUtil(keyManager, tm); 1055 } 1056 else 1057 { 1058 return null; 1059 } 1060 } 1061 1062 1063 1064 /** 1065 * Creates the bind request to use to authenticate to the indicated server. 1066 * 1067 * @param serverIndex The zero-based index of the server to which the 1068 * connection should be established. 1069 * 1070 * @return The bind request to use to authenticate to the indicated server, 1071 * or {@code null} if no bind should be performed. 1072 * 1073 * @throws LDAPException If a problem occurs while creating the bind 1074 * request. 1075 */ 1076 @Nullable() 1077 public final BindRequest createBindRequest(final int serverIndex) 1078 throws LDAPException 1079 { 1080 final String pw; 1081 if (bindPassword[serverIndex].isPresent()) 1082 { 1083 pw = bindPassword[serverIndex].getValue(); 1084 } 1085 else if (bindPasswordFile[serverIndex].isPresent()) 1086 { 1087 try 1088 { 1089 pw = new String(getPasswordFileReader().readPassword( 1090 bindPasswordFile[serverIndex].getValue())); 1091 } 1092 catch (final Exception e) 1093 { 1094 Debug.debugException(e); 1095 throw new LDAPException(ResultCode.LOCAL_ERROR, 1096 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get( 1097 StaticUtils.getExceptionMessage(e)), e); 1098 } 1099 } 1100 else 1101 { 1102 pw = null; 1103 } 1104 1105 if (saslOption[serverIndex].isPresent()) 1106 { 1107 final String dnStr; 1108 if (bindDN[serverIndex].isPresent()) 1109 { 1110 dnStr = bindDN[serverIndex].getValue().toString(); 1111 } 1112 else 1113 { 1114 dnStr = null; 1115 } 1116 1117 return SASLUtils.createBindRequest(dnStr, pw, null, 1118 saslOption[serverIndex].getValues()); 1119 } 1120 else if (bindDN[serverIndex].isPresent()) 1121 { 1122 return new SimpleBindRequest(bindDN[serverIndex].getValue(), pw); 1123 } 1124 else 1125 { 1126 return null; 1127 } 1128 } 1129}