001/* 002 * Copyright 2020-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2020-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) 2020-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.ldap.sdk.unboundidds.tools; 037 038 039 040import java.io.File; 041import java.io.IOException; 042import java.io.OutputStream; 043import java.io.PrintWriter; 044import java.lang.reflect.Method; 045import java.text.SimpleDateFormat; 046import java.util.ArrayList; 047import java.util.Arrays; 048import java.util.Collections; 049import java.util.Date; 050import java.util.LinkedHashMap; 051import java.util.List; 052import java.util.concurrent.TimeUnit; 053import java.util.concurrent.atomic.AtomicReference; 054import java.util.logging.Level; 055 056import com.unboundid.asn1.ASN1OctetString; 057import com.unboundid.ldap.sdk.Control; 058import com.unboundid.ldap.sdk.LDAPConnectionPool; 059import com.unboundid.ldap.sdk.LDAPException; 060import com.unboundid.ldap.sdk.ResultCode; 061import com.unboundid.ldap.sdk.Version; 062import com.unboundid.ldap.sdk.unboundidds.controls.NoOpRequestControl; 063import com.unboundid.ldap.sdk.unboundidds.extensions. 064 CollectSupportDataExtendedRequest; 065import com.unboundid.ldap.sdk.unboundidds.extensions. 066 CollectSupportDataExtendedRequestProperties; 067import com.unboundid.ldap.sdk.unboundidds.extensions. 068 CollectSupportDataExtendedResult; 069import com.unboundid.ldap.sdk.unboundidds.extensions. 070 DurationCollectSupportDataLogCaptureWindow; 071import com.unboundid.ldap.sdk.unboundidds.extensions. 072 HeadAndTailSizeCollectSupportDataLogCaptureWindow; 073import com.unboundid.ldap.sdk.unboundidds.extensions. 074 StartAdministrativeSessionExtendedRequest; 075import com.unboundid.ldap.sdk.unboundidds.extensions. 076 StartAdministrativeSessionPostConnectProcessor; 077import com.unboundid.ldap.sdk.unboundidds.extensions. 078 TimeWindowCollectSupportDataLogCaptureWindow; 079import com.unboundid.ldap.sdk.unboundidds.tasks.CollectSupportDataSecurityLevel; 080import com.unboundid.util.Base64; 081import com.unboundid.util.Debug; 082import com.unboundid.util.LDAPCommandLineTool; 083import com.unboundid.util.NotNull; 084import com.unboundid.util.Nullable; 085import com.unboundid.util.ObjectPair; 086import com.unboundid.util.PasswordReader; 087import com.unboundid.util.StaticUtils; 088import com.unboundid.util.ThreadLocalSecureRandom; 089import com.unboundid.util.ThreadSafety; 090import com.unboundid.util.ThreadSafetyLevel; 091import com.unboundid.util.args.Argument; 092import com.unboundid.util.args.ArgumentException; 093import com.unboundid.util.args.ArgumentParser; 094import com.unboundid.util.args.BooleanArgument; 095import com.unboundid.util.args.DurationArgument; 096import com.unboundid.util.args.FileArgument; 097import com.unboundid.util.args.IntegerArgument; 098import com.unboundid.util.args.StringArgument; 099import com.unboundid.util.args.TimestampArgument; 100 101import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*; 102 103 104 105/** 106 * This class provides a command-line tool that may be used to invoke the 107 * collect-support-data utility in the Ping Identity Directory Server and 108 * related server products. 109 * <BR> 110 * <BLOCKQUOTE> 111 * <B>NOTE:</B> This class, and other classes within the 112 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 113 * supported for use against Ping Identity, UnboundID, and 114 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 115 * for proprietary functionality or for external specifications that are not 116 * considered stable or mature enough to be guaranteed to work in an 117 * interoperable way with other types of LDAP servers. 118 * </BLOCKQUOTE> 119 * <BR> 120 * Note that this is a client-side wrapper for the application. While it may 121 * be used to invoke the tool against a remote server using the 122 * {@link CollectSupportDataExtendedRequest}, it does not include direct support 123 * for invoking the tool against a local instance. That will be accomplished 124 * indirectly by invoking the server-side version of the tool via reflection. 125 */ 126@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 127public final class CollectSupportData 128 extends LDAPCommandLineTool 129{ 130 /** 131 * The column at which to wrap long lines. 132 */ 133 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 134 135 136 137 /** 138 * The Ping Identity Directory Server's default access log timestamp format 139 * when configured to use millisecond precision. 140 */ 141 @NotNull static final String SERVER_LOG_TIMESTAMP_FORMAT_WITH_MILLIS = 142 "'['dd/MMM/yyyy:HH:mm:ss.SSS Z']'"; 143 144 145 146 /** 147 * The Ping Identity Directory Server's default access log timestamp format 148 * when configured to use second precision. 149 */ 150 @NotNull static final String SERVER_LOG_TIMESTAMP_FORMAT_WITHOUT_MILLIS = 151 "'['dd/MMM/yyyy:HH:mm:ss Z']'"; 152 153 154 155 /** 156 * The fully-qualified name of the class in the server codebase that will be 157 * invoked to perform collect-support-data processing when the 158 * --useRemoteServer argument is not provided. 159 */ 160 @NotNull private static final String SERVER_CSD_TOOL_CLASS = 161 "com.unboundid.directory.server.tools.CollectSupportData"; 162 163 164 165 // The completion message for this tool, if available. 166 @NotNull private final AtomicReference<String> toolCompletionMessage; 167 168 // The command-line arguments supported by this tool. 169 @Nullable private ArgumentParser parser; 170 @Nullable private BooleanArgument archiveExtensionSourceArg; 171 @Nullable private BooleanArgument collectExpensiveDataArg; 172 @Nullable private BooleanArgument collectReplicationStateDumpArg; 173 @Nullable private BooleanArgument dryRunArg; 174 @Nullable private BooleanArgument encryptArg; 175 @Nullable private BooleanArgument generatePassphraseArg; 176 @Nullable private BooleanArgument includeBinaryFilesArg; 177 @Nullable private BooleanArgument noLDAPArg; 178 @Nullable private BooleanArgument noPromptArg; 179 @Nullable private BooleanArgument scriptFriendlyArg; 180 @Nullable private BooleanArgument sequentialArg; 181 @Nullable private BooleanArgument useAdministrativeSessionArg; 182 @Nullable private BooleanArgument useRemoteServerArg; 183 @Nullable private DurationArgument logDurationArg; 184 @Nullable private FileArgument decryptArg; 185 @Nullable private FileArgument outputPathArg; 186 @Nullable private FileArgument passphraseFileArg; 187 @Nullable private IntegerArgument jstackCountArg; 188 @Nullable private IntegerArgument logFileHeadCollectionSizeKBArg; 189 @Nullable private IntegerArgument logFileTailCollectionSizeKBArg; 190 @Nullable private IntegerArgument reportCountArg; 191 @Nullable private IntegerArgument reportIntervalSecondsArg; 192 @Nullable private IntegerArgument pidArg; 193 @Nullable private IntegerArgument proxyToServerPortArg; 194 @Nullable private StringArgument commentArg; 195 @Nullable private StringArgument proxyToServerAddressArg; 196 @Nullable private StringArgument securityLevelArg; 197 @Nullable private StringArgument logTimeRangeArg; 198 199 200 201 /** 202 * Invokes this tool with the provided set of command-line arguments. The 203 * JVM's default standard output and standard error streams will be used. 204 * 205 * @param args The set of command-line arguments provided to this program. 206 * It must not be {@code null} but may be empty. 207 */ 208 public static void main(@NotNull final String... args) 209 { 210 final ResultCode resultCode = main(System.out, System.err, args); 211 if ((resultCode != ResultCode.SUCCESS) && 212 (resultCode != ResultCode.NO_OPERATION)) 213 { 214 System.exit(resultCode.intValue()); 215 } 216 } 217 218 219 220 /** 221 * Invokes this tool with the provided set of command-line arguments, using 222 * the given output and error streams. 223 * 224 * @param out The output stream to use for standard output. It may be 225 * {@code null} if standard output should be suppressed. 226 * @param err The output stream to use for standard error. It may be 227 * {@code null} if standard error should be suppressed. 228 * @param args The set of command-line arguments provided to this program. 229 * It must not be {@code null} but may be empty. 230 * 231 * @return A result code indicating the final status of the processing that 232 * was performed. A result code of {@link ResultCode#SUCCESS} 233 * indicates that all processing was successful. A result code of 234 * {@link ResultCode#NO_OPERATION} indicates that it is likely that 235 * processing would have been successful if the --dryRun argument 236 * had not been provided. Any other result code indicates that the 237 * processing did not complete successfully. 238 */ 239 @NotNull() 240 public static ResultCode main(@Nullable final OutputStream out, 241 @Nullable final OutputStream err, 242 @NotNull final String... args) 243 { 244 final CollectSupportData tool = new CollectSupportData(out, err); 245 return tool.runTool(args); 246 } 247 248 249 250 /** 251 * Creates a new instance of this tool that will use the provided streams for 252 * standard output and standard error. 253 * 254 * @param out The output stream to use for standard output. It may be 255 * {@code null} if standard output should be suppressed. 256 * @param err The output stream to use for standard error. It may be 257 * {@code null} if standard error should be suppressed. 258 */ 259 public CollectSupportData(@Nullable final OutputStream out, 260 @Nullable final OutputStream err) 261 { 262 super(out, err); 263 264 toolCompletionMessage = new AtomicReference<>(); 265 266 parser = null; 267 archiveExtensionSourceArg = null; 268 collectExpensiveDataArg = null; 269 collectReplicationStateDumpArg = null; 270 dryRunArg = null; 271 encryptArg = null; 272 generatePassphraseArg = null; 273 includeBinaryFilesArg = null; 274 noLDAPArg = null; 275 noPromptArg = null; 276 scriptFriendlyArg = null; 277 sequentialArg = null; 278 useRemoteServerArg = null; 279 logDurationArg = null; 280 logFileHeadCollectionSizeKBArg = null; 281 logFileTailCollectionSizeKBArg = null; 282 jstackCountArg = null; 283 outputPathArg = null; 284 reportCountArg = null; 285 decryptArg = null; 286 passphraseFileArg = null; 287 reportIntervalSecondsArg = null; 288 pidArg = null; 289 proxyToServerPortArg = null; 290 commentArg = null; 291 logTimeRangeArg = null; 292 proxyToServerAddressArg = null; 293 securityLevelArg = null; 294 } 295 296 297 298 /** 299 * {@inheritDoc} 300 */ 301 @Override() 302 @NotNull() 303 public String getToolName() 304 { 305 return "collect-support-data"; 306 } 307 308 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override() 314 @NotNull() 315 public String getToolDescription() 316 { 317 return INFO_CSD_TOOL_DESCRIPTION_1.get(); 318 } 319 320 321 322 /** 323 * {@inheritDoc} 324 */ 325 @Override() 326 @NotNull() 327 public List<String> getAdditionalDescriptionParagraphs() 328 { 329 return Collections.singletonList(INFO_CSD_TOOL_DESCRIPTION_2.get()); 330 } 331 332 333 334 /** 335 * {@inheritDoc} 336 */ 337 @Override() 338 @NotNull() 339 public String getToolVersion() 340 { 341 return Version.NUMERIC_VERSION_STRING; 342 } 343 344 345 346 /** 347 * {@inheritDoc} 348 */ 349 @Override() 350 public boolean supportsInteractiveMode() 351 { 352 return true; 353 } 354 355 356 357 /** 358 * {@inheritDoc} 359 */ 360 @Override() 361 public boolean defaultsToInteractiveMode() 362 { 363 return false; 364 } 365 366 367 368 /** 369 * {@inheritDoc} 370 */ 371 @Override() 372 public boolean supportsPropertiesFile() 373 { 374 return true; 375 } 376 377 378 379 /** 380 * {@inheritDoc} 381 */ 382 @Override() 383 protected boolean supportsDebugLogging() 384 { 385 return true; 386 } 387 388 389 390 /** 391 * {@inheritDoc} 392 */ 393 @Override() 394 protected boolean defaultToPromptForBindPassword() 395 { 396 if ((noPromptArg != null) && noPromptArg.isPresent()) 397 { 398 return false; 399 } 400 401 return true; 402 } 403 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override() 410 protected boolean includeAlternateLongIdentifiers() 411 { 412 return true; 413 } 414 415 416 417 /** 418 * {@inheritDoc} 419 */ 420 @Override() 421 protected boolean supportsSSLDebugging() 422 { 423 return true; 424 } 425 426 427 428 /** 429 * {@inheritDoc} 430 */ 431 @Override() 432 protected boolean logToolInvocationByDefault() 433 { 434 return true; 435 } 436 437 438 439 /** 440 * {@inheritDoc} 441 */ 442 @Override() 443 @Nullable() 444 protected String getToolCompletionMessage() 445 { 446 return toolCompletionMessage.get(); 447 } 448 449 450 451 /** 452 * {@inheritDoc} 453 */ 454 @Override() 455 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 456 throws ArgumentException 457 { 458 this.parser = parser; 459 460 // Output-related arguments. 461 outputPathArg = new FileArgument(null, "outputPath", false, 1, null, 462 INFO_CSD_ARG_DESC_OUTPUT_PATH.get(), false, true, false, false); 463 outputPathArg.addLongIdentifier("output-path", true); 464 outputPathArg.addLongIdentifier("outputFile", true); 465 outputPathArg.addLongIdentifier("output-file", true); 466 outputPathArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_OUTPUT.get()); 467 parser.addArgument(outputPathArg); 468 469 encryptArg = new BooleanArgument(null, "encrypt", 1, 470 INFO_CSD_ARG_DESC_ENCRYPT.get()); 471 encryptArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_OUTPUT.get()); 472 parser.addArgument(encryptArg); 473 474 passphraseFileArg = new FileArgument(null, "passphraseFile", false, 1, 475 null, INFO_CSD_ARG_DESC_PASSPHRASE_FILE.get(), false, true, true, 476 false); 477 passphraseFileArg.addLongIdentifier("passphrase-file", true); 478 passphraseFileArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_OUTPUT.get()); 479 parser.addArgument(passphraseFileArg); 480 481 generatePassphraseArg = new BooleanArgument(null, "generatePassphrase", 1, 482 INFO_CSD_ARG_DESC_GENERATE_PASSPHRASE.get()); 483 generatePassphraseArg.addLongIdentifier("generate-passphrase", true); 484 generatePassphraseArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_OUTPUT.get()); 485 parser.addArgument(generatePassphraseArg); 486 487 decryptArg = new FileArgument(null, "decrypt", false, 1, null, 488 INFO_CSD_ARG_DESC_DECRYPT.get(), false, true, true, false); 489 decryptArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_OUTPUT.get()); 490 parser.addArgument(decryptArg); 491 492 493 // Collection-related arguments. 494 collectExpensiveDataArg = new BooleanArgument(null, "collectExpensiveData", 495 1, INFO_CSD_ARG_DESC_COLLECT_EXPENSIVE_DATA.get()); 496 collectExpensiveDataArg.addLongIdentifier("collect-expensive-data", true); 497 collectExpensiveDataArg.addLongIdentifier("includeExpensiveData", true); 498 collectExpensiveDataArg.addLongIdentifier("include-expensive-data", true); 499 collectExpensiveDataArg.setArgumentGroupName( 500 INFO_CSD_ARG_GROUP_COLLECTION.get()); 501 parser.addArgument(collectExpensiveDataArg); 502 503 collectReplicationStateDumpArg = new BooleanArgument(null, 504 "collectReplicationStateDump", 1, 505 INFO_CSD_ARG_DESC_COLLECT_REPL_STATE.get()); 506 collectReplicationStateDumpArg.addLongIdentifier( 507 "collect-replication-state-dump", true); 508 collectReplicationStateDumpArg.addLongIdentifier( 509 "collectReplicationState", true); 510 collectReplicationStateDumpArg.addLongIdentifier( 511 "collect-replication-state", true); 512 collectReplicationStateDumpArg.addLongIdentifier( 513 "includeReplicationStateDump", true); 514 collectReplicationStateDumpArg.addLongIdentifier( 515 "include-replication-state-dump", true); 516 collectReplicationStateDumpArg.addLongIdentifier( 517 "includeReplicationState", true); 518 collectReplicationStateDumpArg.addLongIdentifier( 519 "include-replication-state", true); 520 collectReplicationStateDumpArg.setArgumentGroupName( 521 INFO_CSD_ARG_GROUP_COLLECTION.get()); 522 parser.addArgument(collectReplicationStateDumpArg); 523 524 includeBinaryFilesArg = new BooleanArgument(null, "includeBinaryFiles", 1, 525 INFO_CSD_ARG_DESC_INCLUDE_BINARY_FILES.get()); 526 includeBinaryFilesArg.addLongIdentifier("include-binary-files", true); 527 includeBinaryFilesArg.setArgumentGroupName( 528 INFO_CSD_ARG_GROUP_COLLECTION.get()); 529 parser.addArgument(includeBinaryFilesArg); 530 531 archiveExtensionSourceArg = new BooleanArgument(null, 532 "archiveExtensionSource", 1, 533 INFO_CSD_ARG_DESC_ARCHIVE_EXTENSION_SOURCE.get()); 534 archiveExtensionSourceArg.addLongIdentifier("archive-extension-source", 535 true); 536 archiveExtensionSourceArg.addLongIdentifier("includeExtensionSource", true); 537 archiveExtensionSourceArg.addLongIdentifier("include-extension-source", 538 true); 539 archiveExtensionSourceArg.setArgumentGroupName( 540 INFO_CSD_ARG_GROUP_COLLECTION.get()); 541 parser.addArgument(archiveExtensionSourceArg); 542 543 sequentialArg = new BooleanArgument(null, "sequential", 1, 544 INFO_CSD_ARG_DESC_SEQUENTIAL.get()); 545 sequentialArg.addLongIdentifier("sequentialMode", true); 546 sequentialArg.addLongIdentifier("sequential-mode", true); 547 sequentialArg.addLongIdentifier("useSequentialMode", true); 548 sequentialArg.addLongIdentifier("use-sequential-mode", true); 549 sequentialArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_COLLECTION.get()); 550 parser.addArgument(sequentialArg); 551 552 securityLevelArg = new StringArgument(null, "securityLevel", false, 1, 553 INFO_CSD_ARG_PLACEHOLDER_SECURITY_LEVEL.get(), 554 INFO_CSD_ARG_DESC_SECURITY_LEVEL.get(), 555 StaticUtils.setOf( 556 CollectSupportDataSecurityLevel.NONE.getName(), 557 CollectSupportDataSecurityLevel.OBSCURE_SECRETS.getName(), 558 CollectSupportDataSecurityLevel.MAXIMUM.getName())); 559 securityLevelArg.addLongIdentifier("security-level", true); 560 securityLevelArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_COLLECTION.get()); 561 parser.addArgument(securityLevelArg); 562 563 jstackCountArg = new IntegerArgument(null, "jstackCount", false, 1, 564 INFO_CSD_ARG_PLACEHOLDER_COUNT.get(), 565 INFO_CSD_ARG_DESC_JSTACK_COUNT.get(), 0, Integer.MAX_VALUE); 566 jstackCountArg.addLongIdentifier("jstack-count", true); 567 jstackCountArg.addLongIdentifier("maxJstacks", true); 568 jstackCountArg.addLongIdentifier("max-jstacks", true); 569 jstackCountArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_COLLECTION.get()); 570 parser.addArgument(jstackCountArg); 571 572 reportCountArg = new IntegerArgument(null, "reportCount", false, 1, 573 INFO_CSD_ARG_PLACEHOLDER_COUNT.get(), 574 INFO_CSD_ARG_DESC_REPORT_COUNT.get(), 0, Integer.MAX_VALUE); 575 reportCountArg.addLongIdentifier("report-count", true); 576 reportCountArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_COLLECTION.get()); 577 parser.addArgument(reportCountArg); 578 579 reportIntervalSecondsArg = new IntegerArgument(null, 580 "reportIntervalSeconds", false, 1, 581 INFO_CSD_ARG_PLACEHOLDER_SECONDS.get(), 582 INFO_CSD_ARG_DESC_REPORT_INTERVAL_SECONDS.get(), 1, 583 Integer.MAX_VALUE); 584 reportIntervalSecondsArg.addLongIdentifier("report-interval-seconds", true); 585 reportIntervalSecondsArg.addLongIdentifier("reportInterval", true); 586 reportIntervalSecondsArg.addLongIdentifier("report-interval", true); 587 reportIntervalSecondsArg.setArgumentGroupName( 588 INFO_CSD_ARG_GROUP_COLLECTION.get()); 589 parser.addArgument(reportIntervalSecondsArg); 590 591 logTimeRangeArg = new StringArgument(null, "logTimeRange", false, 1, 592 INFO_CSD_ARG_PLACEHOLDER_TIME_RANGE.get(), 593 INFO_CSD_ARG_DESC_TIME_RANGE.get()); 594 logTimeRangeArg.addLongIdentifier("log-time-range", true); 595 logTimeRangeArg.addLongIdentifier("timeRange", true); 596 logTimeRangeArg.addLongIdentifier("time-range", true); 597 logTimeRangeArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_COLLECTION.get()); 598 parser.addArgument(logTimeRangeArg); 599 600 logDurationArg = new DurationArgument(null, "logDuration", false, null, 601 INFO_CSD_ARG_DESC_DURATION.get()); 602 logDurationArg.addLongIdentifier("log-duration", true); 603 logDurationArg.addLongIdentifier("duration", true); 604 logDurationArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_COLLECTION.get()); 605 parser.addArgument(logDurationArg); 606 607 logFileHeadCollectionSizeKBArg = new IntegerArgument(null, 608 "logFileHeadCollectionSizeKB", false, 1, 609 INFO_CSD_ARG_PLACEHOLDER_SIZE_KB.get(), 610 INFO_CSD_ARG_DESC_LOG_HEAD_SIZE_KB.get(), 0, Integer.MAX_VALUE); 611 logFileHeadCollectionSizeKBArg.addLongIdentifier( 612 "log-file-head-collection-size-kb", true); 613 logFileHeadCollectionSizeKBArg.addLongIdentifier("logFileHeadSizeKB", true); 614 logFileHeadCollectionSizeKBArg.addLongIdentifier("log-file-head-size-kb", 615 true); 616 logFileHeadCollectionSizeKBArg.addLongIdentifier("logHeadCollectionSizeKB", 617 true); 618 logFileHeadCollectionSizeKBArg.addLongIdentifier( 619 "log-head-collection-size-kb", true); 620 logFileHeadCollectionSizeKBArg.addLongIdentifier("logHeadSizeKB", true); 621 logFileHeadCollectionSizeKBArg.addLongIdentifier("log-head-size-kb", true); 622 logFileHeadCollectionSizeKBArg.setArgumentGroupName( 623 INFO_CSD_ARG_GROUP_COLLECTION.get()); 624 parser.addArgument(logFileHeadCollectionSizeKBArg); 625 626 logFileTailCollectionSizeKBArg = new IntegerArgument(null, 627 "logFileTailCollectionSizeKB", false, 1, 628 INFO_CSD_ARG_PLACEHOLDER_SIZE_KB.get(), 629 INFO_CSD_ARG_DESC_LOG_TAIL_SIZE_KB.get(), 0, Integer.MAX_VALUE); 630 logFileTailCollectionSizeKBArg.addLongIdentifier( 631 "log-file-tail-collection-size-kb", true); 632 logFileTailCollectionSizeKBArg.addLongIdentifier("logFileTailSizeKB", true); 633 logFileTailCollectionSizeKBArg.addLongIdentifier("log-file-tail-size-kb", 634 true); 635 logFileTailCollectionSizeKBArg.addLongIdentifier("logTailCollectionSizeKB", 636 true); 637 logFileTailCollectionSizeKBArg.addLongIdentifier( 638 "log-tail-collection-size-kb", true); 639 logFileTailCollectionSizeKBArg.addLongIdentifier("logTailSizeKB", true); 640 logFileTailCollectionSizeKBArg.addLongIdentifier("log-tail-size-kb", true); 641 logFileTailCollectionSizeKBArg.setArgumentGroupName( 642 INFO_CSD_ARG_GROUP_COLLECTION.get()); 643 parser.addArgument(logFileTailCollectionSizeKBArg); 644 645 pidArg = new IntegerArgument(null, "pid", false, 0, 646 INFO_CSD_ARG_PLACEHOLDER_PID.get(), INFO_CSD_ARG_DESC_PID.get()); 647 pidArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_COLLECTION.get()); 648 parser.addArgument(pidArg); 649 650 commentArg = new StringArgument(null, "comment", false, 1, null, 651 INFO_CSD_ARG_DESC_COMMENT.get()); 652 commentArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_COLLECTION.get()); 653 parser.addArgument(commentArg); 654 655 656 // Communication-related arguments. 657 useRemoteServerArg = new BooleanArgument(null, "useRemoteServer", 1, 658 INFO_CSD_ARG_DEC_USE_REMOTE_SERVER.get()); 659 useRemoteServerArg.addLongIdentifier("use-remote-server", true); 660 useRemoteServerArg.setArgumentGroupName( 661 INFO_CSD_ARG_GROUP_COMMUNICATION.get()); 662 parser.addArgument(useRemoteServerArg); 663 664 useAdministrativeSessionArg = new BooleanArgument(null, 665 "useAdministrativeSession", 1, 666 INFO_CSD_ARG_DESC_USE_ADMIN_SESSION.get()); 667 useAdministrativeSessionArg.addLongIdentifier("use-administrative-session", 668 true); 669 useAdministrativeSessionArg.addLongIdentifier("useAdminSession", true); 670 useAdministrativeSessionArg.addLongIdentifier("use-admin-session", 671 true); 672 useAdministrativeSessionArg.addLongIdentifier("administrativeSession", 673 true); 674 useAdministrativeSessionArg.addLongIdentifier("administrative-session", 675 true); 676 useAdministrativeSessionArg.addLongIdentifier("adminSession", true); 677 useAdministrativeSessionArg.addLongIdentifier("admin-session", true); 678 useAdministrativeSessionArg.setArgumentGroupName( 679 INFO_CSD_ARG_GROUP_COMMUNICATION.get()); 680 parser.addArgument(useAdministrativeSessionArg); 681 682 proxyToServerAddressArg = new StringArgument(null, "proxyToServerAddress", 683 false, 1, INFO_CSD_ARG_PLACEHOLDER_ADDRESS.get(), 684 INFO_CSD_ARG_DESC_PROXY_TO_ADDRESS.get()); 685 proxyToServerAddressArg.addLongIdentifier("proxy-to-server-address", true); 686 proxyToServerAddressArg.addLongIdentifier("proxyToAddress", true); 687 proxyToServerAddressArg.addLongIdentifier("proxy-to-address", true); 688 proxyToServerAddressArg.setArgumentGroupName( 689 INFO_CSD_ARG_GROUP_COMMUNICATION.get()); 690 parser.addArgument(proxyToServerAddressArg); 691 692 proxyToServerPortArg = new IntegerArgument(null, "proxyToServerPort", false, 693 1, INFO_CSD_ARG_PLACEHOLDER_PORT.get(), 694 INFO_CSD_ARG_DESC_PROXY_TO_PORT.get(), 1, 65535); 695 proxyToServerPortArg.addLongIdentifier("proxy-to-server-port", true); 696 proxyToServerPortArg.addLongIdentifier("proxyToPort", true); 697 proxyToServerPortArg.addLongIdentifier("proxy-to-port", true); 698 proxyToServerPortArg.setArgumentGroupName( 699 INFO_CSD_ARG_GROUP_COMMUNICATION.get()); 700 parser.addArgument(proxyToServerPortArg); 701 702 noLDAPArg = new BooleanArgument(null, "noLDAP", 1, 703 INFO_CSD_ARG_DESC_NO_LDAP.get()); 704 noLDAPArg.addLongIdentifier("no-ldap", true); 705 noLDAPArg.setArgumentGroupName(INFO_CSD_ARG_GROUP_COMMUNICATION.get()); 706 parser.addArgument(noLDAPArg); 707 708 709 // Other arguments. 710 noPromptArg = new BooleanArgument('n', "noPrompt", 1, 711 INFO_CSD_ARG_DESC_NO_PROMPT.get()); 712 noPromptArg.addLongIdentifier("no-prompt", true); 713 parser.addArgument(noPromptArg); 714 715 dryRunArg = new BooleanArgument(null, "dryRun", 1, 716 INFO_CSD_ARG_DESC_DRY_RUN.get()); 717 dryRunArg.addLongIdentifier("dry-run", true); 718 dryRunArg.addLongIdentifier("noOperation", true); 719 dryRunArg.addLongIdentifier("no-operation", true); 720 dryRunArg.addLongIdentifier("noOp", true); 721 dryRunArg.addLongIdentifier("no-op", true); 722 parser.addArgument(dryRunArg); 723 724 scriptFriendlyArg = new BooleanArgument(null, "scriptFriendly", 1, 725 INFO_CSD_ARG_DESC_SCRIPT_FRIENDLY.get()); 726 scriptFriendlyArg.addLongIdentifier("script-friendly", true); 727 scriptFriendlyArg.setHidden(true); 728 parser.addArgument(scriptFriendlyArg); 729 730 731 // If the --useRemoteServer argument is provided, then none of the --pid, 732 // --decrypt, --noLDAP, or --scriptFriendly arguments may be given. 733 parser.addExclusiveArgumentSet(useRemoteServerArg, pidArg); 734 parser.addExclusiveArgumentSet(useRemoteServerArg, decryptArg); 735 parser.addExclusiveArgumentSet(useRemoteServerArg, noLDAPArg); 736 parser.addExclusiveArgumentSet(useRemoteServerArg, scriptFriendlyArg); 737 738 // The --useAdministrativeSession argument can only be provided if the 739 // --useRemoteServer argument is also given. 740 parser.addDependentArgumentSet(useAdministrativeSessionArg, 741 useRemoteServerArg); 742 743 // If the --proxyToServerAddress or --proxyToServerPort argument is given, 744 // then the other must be provided as well. 745 parser.addMutuallyDependentArgumentSet(proxyToServerAddressArg, 746 proxyToServerPortArg); 747 748 // The --proxyToServerAddress and --proxyToServerPort arguments can only 749 // be used if the --useRemoteServer argument is given. 750 parser.addDependentArgumentSet(proxyToServerAddressArg, useRemoteServerArg); 751 parser.addDependentArgumentSet(proxyToServerPortArg, useRemoteServerArg); 752 753 // The --logTimeRange and --logDuration arguments cannot be used together. 754 parser.addExclusiveArgumentSet(logTimeRangeArg, logDurationArg); 755 756 // Neither the --logFileHeadCollectionSizeKB argument nor the 757 // --logFileTailCollectionSizeKB argument can be used in conjunction with 758 // either the --logTimeRange or --logDuration argument. 759 parser.addExclusiveArgumentSet(logFileHeadCollectionSizeKBArg, 760 logTimeRangeArg, logDurationArg); 761 parser.addExclusiveArgumentSet(logFileTailCollectionSizeKBArg, 762 logTimeRangeArg, logDurationArg); 763 764 // The --generatePassphrase argument can only be used if both the 765 // --encrypt and --passphraseFile arguments are provided. 766 parser.addDependentArgumentSet(generatePassphraseArg, encryptArg); 767 parser.addDependentArgumentSet(generatePassphraseArg, passphraseFileArg); 768 769 // The --encrypt and --decrypt arguments cannot be used together. 770 parser.addExclusiveArgumentSet(encryptArg, decryptArg); 771 772 773 // There are several arguments that the LDAP SDK's LDAP command-line tool 774 // framework offers that the server-side version of the framework does not 775 // provide. Those arguments can only be used in conjunction with the 776 // --useRemoteServer argument. 777 for (final String argumentIdentifier : 778 Arrays.asList("promptForBindPassword", "promptForKeyStorePassword", 779 "promptForTrustStorePassword", "enableSSLDebugging", 780 "useSASLExternal")) 781 { 782 final Argument arg = parser.getNamedArgument(argumentIdentifier); 783 parser.addDependentArgumentSet(arg, useRemoteServerArg); 784 } 785 } 786 787 788 789 /** 790 * {@inheritDoc} 791 */ 792 @Override() 793 public void doExtendedNonLDAPArgumentValidation() 794 throws ArgumentException 795 { 796 // If the --logTimeRange argument was provided, then make sure we can 797 // parse each of the values and that the end time is greater than or equal 798 // to the start time. 799 if (logTimeRangeArg.isPresent()) 800 { 801 try 802 { 803 parseTimeRange(logTimeRangeArg.getValue(), 804 useRemoteServerArg.isPresent()); 805 } 806 catch (final LDAPException e) 807 { 808 Debug.debugException(e); 809 toolCompletionMessage.set(e.getMessage()); 810 throw new ArgumentException(e.getMessage(), e); 811 } 812 } 813 814 815 // If the --passphraseFile argument was provided without the 816 // --generatePassphrase argument, then make sure the file exists. 817 if (passphraseFileArg.isPresent() && (! generatePassphraseArg.isPresent())) 818 { 819 final File passphraseFile = passphraseFileArg.getValue(); 820 if (! passphraseFile.exists()) 821 { 822 final String message =ERR_CSD_PASSPHRASE_FILE_MISSING.get( 823 passphraseFile.getAbsolutePath()); 824 toolCompletionMessage.set(message); 825 throw new ArgumentException(message); 826 } 827 } 828 829 830 // If either the --encrypt or --decrypt argument is provided in conjunction 831 // with the --noPrompt argument, then the --passphraseFile argument must 832 // also have been provided. 833 if (noPromptArg.isPresent() && 834 (encryptArg.isPresent() || decryptArg.isPresent()) && 835 (! passphraseFileArg.isPresent())) 836 { 837 final String message = ERR_CSD_NO_PASSPHRASE_WITH_NO_PROMPT.get(); 838 toolCompletionMessage.set(message); 839 throw new ArgumentException(message); 840 } 841 } 842 843 844 845 /** 846 * Parses the provided string as a time range. If both start and end time 847 * values are provided, then they must be separated by a comma; otherwise, 848 * there must only be a start time value. Each timestamp must be in either 849 * the generalized time format or the Ping Identity Directory Server's default 850 * access log format (with or without millisecond precision). 851 * 852 * @param timeRangeStr The string to be parsed as a time range. It must not 853 * be {@code null}. 854 * @param strict Indicates whether to require strict compliance with 855 * the timestamp format. This should be {@code true} 856 * when the useRemoteServer argument was provided, and 857 * {@code false} otherwise. 858 * 859 * @return An object pair in which the first value is the start time for 860 * the range and the second value is the end time for the range. The 861 * first element will always be non-{@code null}, but the second 862 * element may be {@code null} if the time range did not specify an 863 * end time. The entire return value may be {@code null} if the 864 * time range string could not be parsed and {@code strict} is 865 * {@code false}. 866 * 867 * @throws LDAPException If a problem is encountered while parsing the 868 * provided string as a time range, or if the start 869 * time is greater than the end time. 870 */ 871 @Nullable() 872 static ObjectPair<Date,Date> parseTimeRange( 873 @NotNull final String timeRangeStr, 874 final boolean strict) 875 throws LDAPException 876 { 877 final Date startTime; 878 final Date endTime; 879 880 try 881 { 882 // See if there is a comma to separate the before and after times. If so, 883 // then parse each value separately. Otherwise, the value will be just 884 // the start time and the current time will be used as the end time. 885 final int commaPos = timeRangeStr.indexOf(','); 886 if (commaPos > 0) 887 { 888 startTime = parseTimestamp(timeRangeStr.substring(0, commaPos).trim()); 889 endTime = parseTimestamp(timeRangeStr.substring(commaPos+1).trim()); 890 } 891 else 892 { 893 startTime = parseTimestamp(timeRangeStr); 894 endTime = null; 895 } 896 } 897 catch (final LDAPException e) 898 { 899 Debug.debugException(e); 900 901 // NOTE: The server-side version of the collect-support-data tool has a 902 // not-so-documented feature in which you can provide rotated file names 903 // as an alternative to an actual time range. We can't handle that 904 // when operating against a remote server, so we'll require strict 905 // timestamp compliance when --useRemoteServer is provided, but we'll just 906 // return null and let the argument value be passed through to the 907 // server-side code otherwise. 908 if (strict) 909 { 910 throw e; 911 } 912 else 913 { 914 return null; 915 } 916 } 917 918 if ((endTime != null) && (startTime.getTime() > endTime.getTime())) 919 { 920 throw new LDAPException(ResultCode.PARAM_ERROR, 921 ERR_CSD_TIME_RANGE_START_GREATER_THAN_END.get()); 922 } 923 924 return new ObjectPair<>(startTime, endTime); 925 } 926 927 928 929 /** 930 * Parses the provided string as a timestamp value in either the generalized 931 * time format or the Ping Identity Directory Server's default access log 932 * format (with or without millisecond precision). 933 * 934 * @param timestampStr The timestamp to be parsed. It must not be 935 * {@code null}. 936 * 937 * @return The {@code Date} created by parsing the timestamp. 938 * 939 * @throws LDAPException If the provided string cannot be parsed as a 940 * valid timestamp. 941 */ 942 @NotNull() 943 static Date parseTimestamp(@NotNull final String timestampStr) 944 throws LDAPException 945 { 946 // First, try using the timestamp argument to parse the timestamp. 947 try 948 { 949 return TimestampArgument.parseTimestamp(timestampStr); 950 } 951 catch (final Exception e) 952 { 953 Debug.debugException(Level.FINEST, e); 954 } 955 956 957 // Next, try the server's default access log format with millisecond 958 // precision. 959 try 960 { 961 final SimpleDateFormat timestampFormatter = 962 new SimpleDateFormat(SERVER_LOG_TIMESTAMP_FORMAT_WITH_MILLIS); 963 timestampFormatter.setLenient(false); 964 return timestampFormatter.parse(timestampStr); 965 } 966 catch (final Exception e) 967 { 968 Debug.debugException(Level.FINEST, e); 969 } 970 971 972 // Next, try the server's default access log format with second precision. 973 try 974 { 975 final SimpleDateFormat timestampFormatter = 976 new SimpleDateFormat(SERVER_LOG_TIMESTAMP_FORMAT_WITHOUT_MILLIS); 977 timestampFormatter.setLenient(false); 978 return timestampFormatter.parse(timestampStr); 979 } 980 catch (final Exception e) 981 { 982 Debug.debugException(Level.FINEST, e); 983 } 984 985 986 // If we've gotten here, then we could not parse the timestamp. 987 throw new LDAPException(ResultCode.PARAM_ERROR, 988 ERR_CSD_TIME_RANGE_CANNOT_PARSE_TIMESTAMP.get(timestampStr)); 989 } 990 991 992 993 /** 994 * {@inheritDoc} 995 */ 996 @Override() 997 @NotNull() 998 public ResultCode doToolProcessing() 999 { 1000 // If the --useRemoteServer argument was provided, then use the extended 1001 // operation to perform the processing. Otherwise, use reflection to invoke 1002 // the server's version of the collect-support-data tool. 1003 if (useRemoteServerArg.isPresent()) 1004 { 1005 return doExtendedOperationProcessing(); 1006 } 1007 else 1008 { 1009 return doLocalProcessing(); 1010 } 1011 } 1012 1013 1014 1015 /** 1016 * Performs the collect-support-data processing against a remote server using 1017 * the extended operation. 1018 * 1019 * @return A result code that indicates the result of the processing. 1020 */ 1021 @NotNull() 1022 private ResultCode doExtendedOperationProcessing() 1023 { 1024 // Create a connection pool that will be used to communicate with the 1025 // server. Use an administrative session if appropriate. 1026 final StartAdministrativeSessionPostConnectProcessor p; 1027 if (useAdministrativeSessionArg.isPresent()) 1028 { 1029 p = new StartAdministrativeSessionPostConnectProcessor( 1030 new StartAdministrativeSessionExtendedRequest(getToolName(), 1031 true)); 1032 } 1033 else 1034 { 1035 p = null; 1036 } 1037 1038 final LDAPConnectionPool pool; 1039 try 1040 { 1041 pool = getConnectionPool(1, 1, 0, p, null, true, 1042 new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, 1043 false)); 1044 } 1045 catch (final LDAPException e) 1046 { 1047 Debug.debugException(e); 1048 wrapErr(0, WRAP_COLUMN, e.getMessage()); 1049 toolCompletionMessage.set(e.getMessage()); 1050 return e.getResultCode(); 1051 } 1052 1053 1054 try 1055 { 1056 // Create the properties to use for the extended request. 1057 final CollectSupportDataExtendedRequestProperties properties = 1058 new CollectSupportDataExtendedRequestProperties(); 1059 1060 final File outputPath = outputPathArg.getValue(); 1061 if (outputPath != null) 1062 { 1063 if (! (outputPath.exists() && outputPath.isDirectory())) 1064 { 1065 properties.setArchiveFileName(outputPath.getName()); 1066 } 1067 } 1068 1069 try 1070 { 1071 properties.setEncryptionPassphrase( 1072 getEncryptionPassphraseForExtOpProcessing()); 1073 } 1074 catch (final LDAPException e) 1075 { 1076 Debug.debugException(e); 1077 wrapErr(0, WRAP_COLUMN, e.getMessage()); 1078 toolCompletionMessage.set(e.getMessage()); 1079 return e.getResultCode(); 1080 } 1081 1082 1083 properties.setIncludeExpensiveData(collectExpensiveDataArg.isPresent()); 1084 properties.setIncludeReplicationStateDump( 1085 collectReplicationStateDumpArg.isPresent()); 1086 properties.setIncludeBinaryFiles(includeBinaryFilesArg.isPresent()); 1087 properties.setIncludeExtensionSource( 1088 archiveExtensionSourceArg.isPresent()); 1089 properties.setUseSequentialMode(sequentialArg.isPresent()); 1090 1091 if (securityLevelArg.isPresent()) 1092 { 1093 properties.setSecurityLevel(CollectSupportDataSecurityLevel.forName( 1094 securityLevelArg.getValue())); 1095 } 1096 1097 if (jstackCountArg.isPresent()) 1098 { 1099 properties.setJStackCount(jstackCountArg.getValue()); 1100 } 1101 1102 if (reportCountArg.isPresent()) 1103 { 1104 properties.setReportCount(reportCountArg.getValue()); 1105 } 1106 1107 if (reportIntervalSecondsArg.isPresent()) 1108 { 1109 properties.setReportIntervalSeconds( 1110 reportIntervalSecondsArg.getValue()); 1111 } 1112 1113 if (logTimeRangeArg.isPresent()) 1114 { 1115 try 1116 { 1117 final ObjectPair<Date,Date> timeRange = 1118 parseTimeRange(logTimeRangeArg.getValue(), true); 1119 properties.setLogCaptureWindow( 1120 new TimeWindowCollectSupportDataLogCaptureWindow( 1121 timeRange.getFirst(), timeRange.getSecond())); 1122 } 1123 catch (final LDAPException e) 1124 { 1125 // This should never happen because we should have pre-validated the 1126 // value. But handle it just in case. 1127 Debug.debugException(e); 1128 wrapErr(0, WRAP_COLUMN, e.getMessage()); 1129 toolCompletionMessage.set(e.getMessage()); 1130 return e.getResultCode(); 1131 } 1132 } 1133 else if (logDurationArg.isPresent()) 1134 { 1135 properties.setLogCaptureWindow( 1136 new DurationCollectSupportDataLogCaptureWindow( 1137 logDurationArg.getValue(TimeUnit.MILLISECONDS))); 1138 } 1139 else if (logFileHeadCollectionSizeKBArg.isPresent() || 1140 logFileTailCollectionSizeKBArg.isPresent()) 1141 { 1142 properties.setLogCaptureWindow( 1143 new HeadAndTailSizeCollectSupportDataLogCaptureWindow( 1144 logFileHeadCollectionSizeKBArg.getValue(), 1145 logFileTailCollectionSizeKBArg.getValue())); 1146 } 1147 1148 if (commentArg.isPresent()) 1149 { 1150 properties.setComment(commentArg.getValue()); 1151 } 1152 1153 if (proxyToServerAddressArg.isPresent()) 1154 { 1155 properties.setProxyToServer(proxyToServerAddressArg.getValue(), 1156 proxyToServerPortArg.getValue()); 1157 } 1158 1159 1160 // Create the intermediate response listener that will be used to handle 1161 // output and archive fragment messages. 1162 ResultCode resultCode = null; 1163 try (CollectSupportDataIRListener listener = 1164 new CollectSupportDataIRListener(this, outputPathArg.getValue())) 1165 { 1166 // Construct the extended request to send to the server. 1167 final Control[] controls; 1168 if (dryRunArg.isPresent()) 1169 { 1170 controls = new Control[] 1171 { 1172 new NoOpRequestControl() 1173 }; 1174 } 1175 else 1176 { 1177 controls = StaticUtils.NO_CONTROLS; 1178 } 1179 1180 final CollectSupportDataExtendedRequest request = 1181 new CollectSupportDataExtendedRequest(properties, listener, 1182 controls); 1183 request.setResponseTimeoutMillis(0L); 1184 1185 1186 // Send the request and read the result. 1187 final CollectSupportDataExtendedResult result; 1188 try 1189 { 1190 result = (CollectSupportDataExtendedResult) 1191 pool.processExtendedOperation(request); 1192 } 1193 catch (final LDAPException e) 1194 { 1195 Debug.debugException(e); 1196 final String message = ERR_CSD_ERROR_SENDING_REQUEST.get( 1197 StaticUtils.getExceptionMessage(e)); 1198 wrapErr(0, WRAP_COLUMN, message); 1199 toolCompletionMessage.set(message); 1200 return e.getResultCode(); 1201 } 1202 1203 1204 resultCode = result.getResultCode(); 1205 final String diagnosticMessage = result.getDiagnosticMessage(); 1206 if (diagnosticMessage != null) 1207 { 1208 if ((resultCode == ResultCode.SUCCESS) || 1209 (resultCode == ResultCode.NO_OPERATION)) 1210 { 1211 wrapOut(0, WRAP_COLUMN, diagnosticMessage); 1212 } 1213 else 1214 { 1215 wrapErr(0, WRAP_COLUMN, diagnosticMessage); 1216 } 1217 1218 toolCompletionMessage.set(diagnosticMessage); 1219 } 1220 else 1221 { 1222 toolCompletionMessage.set(INFO_CSD_COMPLETED_WITH_RESULT_CODE.get( 1223 String.valueOf(resultCode))); 1224 } 1225 } 1226 catch (final IOException e) 1227 { 1228 Debug.debugException(e); 1229 1230 if (resultCode == ResultCode.SUCCESS) 1231 { 1232 resultCode = ResultCode.LOCAL_ERROR; 1233 toolCompletionMessage.set(e.getMessage()); 1234 } 1235 } 1236 1237 return resultCode; 1238 } 1239 finally 1240 { 1241 pool.close(); 1242 } 1243 } 1244 1245 1246 1247 /** 1248 * Retrieves the passphrase to use to generate the key for encrypting the 1249 * support data archive. This method should only be used when the tool 1250 * processing will be performed using an extended operation. 1251 * 1252 * @return The passphrase to use to generate the key for encrypting the 1253 * support data archive. 1254 * 1255 * @throws LDAPException If a problem is encountered while attempting to 1256 * obtain the passphrase. 1257 */ 1258 @Nullable() 1259 private ASN1OctetString getEncryptionPassphraseForExtOpProcessing() 1260 throws LDAPException 1261 { 1262 if (! encryptArg.isPresent()) 1263 { 1264 return null; 1265 } 1266 1267 if (passphraseFileArg.isPresent()) 1268 { 1269 final File passphraseFile = passphraseFileArg.getValue(); 1270 if (generatePassphraseArg.isPresent()) 1271 { 1272 // Generate a passphrase as a base64url-encoded representation of some 1273 // randomly generated data. 1274 final byte[] randomBytes = new byte[64]; 1275 ThreadLocalSecureRandom.get().nextBytes(randomBytes); 1276 final String passphrase = Base64.urlEncode(randomBytes, false); 1277 1278 try (PrintWriter writer = new PrintWriter(passphraseFile)) 1279 { 1280 writer.println(passphrase); 1281 } 1282 catch (final Exception e) 1283 { 1284 Debug.debugException(e); 1285 throw new LDAPException(ResultCode.LOCAL_ERROR, 1286 ERR_CSD_CANNOT_WRITE_GENERATED_PASSPHRASE.get( 1287 passphraseFile.getAbsolutePath(), 1288 StaticUtils.getExceptionMessage(e)), 1289 e); 1290 } 1291 1292 return new ASN1OctetString(passphrase); 1293 } 1294 else 1295 { 1296 try 1297 { 1298 final char[] passphrase = 1299 getPasswordFileReader().readPassword(passphraseFile); 1300 return new ASN1OctetString(new String(passphrase)); 1301 } 1302 catch (final Exception e) 1303 { 1304 Debug.debugException(e); 1305 1306 ResultCode resultCode = ResultCode.LOCAL_ERROR; 1307 if (e instanceof LDAPException) 1308 { 1309 resultCode = ((LDAPException) e).getResultCode(); 1310 } 1311 1312 throw new LDAPException(resultCode, 1313 ERR_CSD_CANNOT_READ_PASSPHRASE.get( 1314 passphraseFile.getAbsolutePath(), 1315 StaticUtils.getExceptionMessage(e)), 1316 e); 1317 } 1318 } 1319 } 1320 1321 1322 // Prompt for the encryption passphrase. 1323 while (true) 1324 { 1325 try 1326 { 1327 getOut().print(INFO_CSD_PASSPHRASE_INITIAL_PROMPT.get()); 1328 final byte[] passphraseBytes = PasswordReader.readPassword(); 1329 1330 getOut().print(INFO_CSD_PASSPHRASE_CONFIRM_PROMPT.get()); 1331 final byte[] confirmBytes = PasswordReader.readPassword(); 1332 if (Arrays.equals(passphraseBytes, confirmBytes)) 1333 { 1334 return new ASN1OctetString(passphraseBytes); 1335 } 1336 else 1337 { 1338 wrapErr(0, WRAP_COLUMN, ERR_CSD_PASSPHRASE_MISMATCH.get()); 1339 err(); 1340 } 1341 } 1342 catch (final Exception e) 1343 { 1344 throw new LDAPException(ResultCode.LOCAL_ERROR, 1345 ERR_CSD_PASSPHRASE_PROMPT_READ_ERROR.get( 1346 StaticUtils.getExceptionMessage(e)), 1347 e); 1348 } 1349 } 1350 } 1351 1352 1353 1354 /** 1355 * Performs the collect-support-data tool using reflection to invoke the 1356 * server-side version of the tool. 1357 * 1358 * @return A result code that indicates the result of the processing. 1359 */ 1360 @NotNull() 1361 private ResultCode doLocalProcessing() 1362 { 1363 // Construct the argument list to use when invoking the server-side code. 1364 // Although this tool supports all of the arguments that the server-side 1365 // tool provides, the server-side tool does not support all of the arguments 1366 // that this version offers, nor does it support all of the variants (e.g., 1367 // alternate names) for the arguments that do overlap. 1368 final List<String> argList = new ArrayList<>(20); 1369 1370 if (outputPathArg.isPresent()) 1371 { 1372 argList.add("--outputPath"); 1373 argList.add(outputPathArg.getValue().getAbsolutePath()); 1374 } 1375 1376 if (noLDAPArg.isPresent()) 1377 { 1378 argList.add("--noLdap"); 1379 } 1380 1381 if (pidArg.isPresent()) 1382 { 1383 for (final Integer pid : pidArg.getValues()) 1384 { 1385 argList.add("--pid"); 1386 argList.add(pid.toString()); 1387 } 1388 } 1389 1390 if (sequentialArg.isPresent()) 1391 { 1392 argList.add("--sequential"); 1393 } 1394 1395 if (reportCountArg.isPresent()) 1396 { 1397 argList.add("--reportCount"); 1398 argList.add(reportCountArg.getValue().toString()); 1399 } 1400 1401 if (reportIntervalSecondsArg.isPresent()) 1402 { 1403 argList.add("--reportInterval"); 1404 argList.add(reportIntervalSecondsArg.getValue().toString()); 1405 } 1406 1407 if (jstackCountArg.isPresent()) 1408 { 1409 argList.add("--maxJstacks"); 1410 argList.add(jstackCountArg.getValue().toString()); 1411 } 1412 1413 if (collectExpensiveDataArg.isPresent()) 1414 { 1415 argList.add("--collectExpensiveData"); 1416 } 1417 1418 if (collectReplicationStateDumpArg.isPresent()) 1419 { 1420 argList.add("--collectReplicationStateDump"); 1421 } 1422 1423 if (commentArg.isPresent()) 1424 { 1425 argList.add("--comment"); 1426 argList.add(commentArg.getValue()); 1427 } 1428 1429 if (includeBinaryFilesArg.isPresent()) 1430 { 1431 argList.add("--includeBinaryFiles"); 1432 } 1433 1434 if (securityLevelArg.isPresent()) 1435 { 1436 argList.add("--securityLevel"); 1437 argList.add(securityLevelArg.getValue()); 1438 } 1439 1440 if (encryptArg.isPresent()) 1441 { 1442 argList.add("--encrypt"); 1443 } 1444 1445 if (passphraseFileArg.isPresent()) 1446 { 1447 argList.add("--passphraseFile"); 1448 argList.add(passphraseFileArg.getValue().getAbsolutePath()); 1449 } 1450 1451 if (generatePassphraseArg.isPresent()) 1452 { 1453 argList.add("--generatePassphrase"); 1454 } 1455 1456 if (decryptArg.isPresent()) 1457 { 1458 argList.add("--decrypt"); 1459 argList.add(decryptArg.getValue().getAbsolutePath()); 1460 } 1461 1462 if (logTimeRangeArg.isPresent()) 1463 { 1464 final ObjectPair<Date,Date> timeRange; 1465 try 1466 { 1467 timeRange = parseTimeRange(logTimeRangeArg.getValue(), false); 1468 } 1469 catch (final LDAPException e) 1470 { 1471 // This should never happen because we should have pre-validated the 1472 // value. But handle it just in case. 1473 Debug.debugException(e); 1474 wrapErr(0, WRAP_COLUMN, e.getMessage()); 1475 return e.getResultCode(); 1476 } 1477 1478 if (timeRange == null) 1479 { 1480 // We'll assume that this means the time range was specified using 1481 // rotated log filenames, which we can't handle in the LDAP SDK code so 1482 // we'll just pass the argument value through to the server code. 1483 argList.add("--timeRange"); 1484 argList.add(logTimeRangeArg.getValue()); 1485 } 1486 else 1487 { 1488 final Date startTime = timeRange.getFirst(); 1489 Date endTime = timeRange.getSecond(); 1490 if (endTime == null) 1491 { 1492 endTime = new Date(Math.max(System.currentTimeMillis(), 1493 startTime.getTime())); 1494 } 1495 1496 final SimpleDateFormat timestampFormatter = 1497 new SimpleDateFormat(SERVER_LOG_TIMESTAMP_FORMAT_WITH_MILLIS); 1498 argList.add("--timeRange"); 1499 argList.add(timestampFormatter.format(startTime) + ',' + 1500 timestampFormatter.format(endTime)); 1501 } 1502 } 1503 1504 if (logDurationArg.isPresent()) 1505 { 1506 argList.add("--duration"); 1507 argList.add(DurationArgument.nanosToDuration( 1508 logDurationArg.getValue(TimeUnit.NANOSECONDS))); 1509 } 1510 1511 if (logFileHeadCollectionSizeKBArg.isPresent()) 1512 { 1513 argList.add("--logFileHeadCollectionSizeKB"); 1514 argList.add(String.valueOf(logFileHeadCollectionSizeKBArg.getValue())); 1515 } 1516 1517 if (logFileTailCollectionSizeKBArg.isPresent()) 1518 { 1519 argList.add("--logFileTailCollectionSizeKB"); 1520 argList.add(String.valueOf(logFileTailCollectionSizeKBArg.getValue())); 1521 } 1522 1523 if (archiveExtensionSourceArg.isPresent()) 1524 { 1525 argList.add("--archiveExtensionSource"); 1526 } 1527 1528 if (noPromptArg.isPresent()) 1529 { 1530 argList.add("--no-prompt"); 1531 } 1532 1533 if (scriptFriendlyArg.isPresent()) 1534 { 1535 argList.add("--script-friendly"); 1536 } 1537 1538 1539 // We also need to include values for arguments provided by the LDAP 1540 // command-line tool framework. 1541 for (final String argumentIdentifier : 1542 Arrays.asList("hostname", "port", "bindDN", "bindPassword", 1543 "bindPasswordFile", "useSSL", "useStartTLS", "trustAll", 1544 "keyStorePath", "keyStorePassword", "keyStorePasswordFile", 1545 "keyStoreFormat", "trustStorePath", "trustStorePassword", 1546 "trustStorePasswordFile", "trustStoreFormat", "certNickname", 1547 "saslOption", "propertiesFilePath", "noPropertiesFile")) 1548 { 1549 final Argument arg = parser.getNamedArgument(argumentIdentifier); 1550 if (arg.getNumOccurrences() > 0) 1551 { 1552 for (final String value : arg.getValueStringRepresentations(false)) 1553 { 1554 argList.add("--" + argumentIdentifier); 1555 if (arg.takesValue()) 1556 { 1557 argList.add(value); 1558 } 1559 } 1560 } 1561 } 1562 1563 1564 // If the --dryRun argument was provided, then return without actually 1565 // invoking the tool. 1566 if (dryRunArg.isPresent()) 1567 { 1568 final String message = INFO_CSD_LOCAL_MODE_DRY_RUN.get(); 1569 wrapOut(0, WRAP_COLUMN, message); 1570 toolCompletionMessage.set(message); 1571 return ResultCode.NO_OPERATION; 1572 } 1573 1574 1575 // Make sure that we have access to the method in the server codebase that 1576 // we need to invoke local collect-support-data processing. 1577 final Method doMainMethod; 1578 try 1579 { 1580 final Class<?> csdToolClass = Class.forName(SERVER_CSD_TOOL_CLASS); 1581 doMainMethod = csdToolClass.getMethod("doMain", Boolean.TYPE, 1582 OutputStream.class, OutputStream.class, String[].class); 1583 } 1584 catch (final Throwable t) 1585 { 1586 Debug.debugException(t); 1587 final String message = ERR_CSD_SERVER_CODE_NOT_AVAILABLE.get(); 1588 wrapErr(0, WRAP_COLUMN, message); 1589 toolCompletionMessage.set(message); 1590 return ResultCode.NOT_SUPPORTED; 1591 } 1592 1593 1594 // Invoke the doMain method via reflection 1595 final String[] argArray = new String[argList.size()]; 1596 argList.toArray(argArray); 1597 1598 try 1599 { 1600 final Object doMainMethodReturnValue = doMainMethod.invoke(null, 1601 true, getOut(), getErr(), argArray); 1602 final int exitCode = ((Integer) doMainMethodReturnValue).intValue(); 1603 return ResultCode.valueOf(exitCode); 1604 } 1605 catch (final Throwable t) 1606 { 1607 Debug.debugException(t); 1608 final String message = 1609 ERR_CSD_INVOKE_ERROR.get(StaticUtils.getExceptionMessage(t)); 1610 wrapErr(0, WRAP_COLUMN, message); 1611 toolCompletionMessage.set(message); 1612 return ResultCode.LOCAL_ERROR; 1613 } 1614 } 1615 1616 1617 1618 /** 1619 * {@inheritDoc} 1620 */ 1621 @Override() 1622 @NotNull() 1623 public LinkedHashMap<String[],String> getExampleUsages() 1624 { 1625 final LinkedHashMap<String[],String> examples = 1626 new LinkedHashMap<>(StaticUtils.computeMapCapacity(3)); 1627 1628 examples.put( 1629 new String[] 1630 { 1631 "--bindDN", "uid=admin,dc=example,dc=com", 1632 "--bindPasswordFile", "admin-pw.txt" 1633 }, 1634 INFO_CSD_EXAMPLE_1.get()); 1635 1636 examples.put( 1637 new String[] 1638 { 1639 "--useRemoteServer", 1640 "--hostname", "ds.example.com", 1641 "--port", "636", 1642 "--useSSL", 1643 "--trustStorePath", "config/truststore", 1644 "--bindDN", "uid=admin,dc=example,dc=com", 1645 "--bindPasswordFile", "admin-pw.txt", 1646 "--collectExpensiveData", 1647 "--collectReplicationStateDump", 1648 "--securityLevel", "maximum", 1649 "--logDuration", "10 minutes", 1650 "--encrypt", 1651 "--passphraseFile", "encryption-passphrase.txt", 1652 "--generatePassphrase", 1653 "--outputPath", "csd.zip" 1654 }, 1655 INFO_CSD_EXAMPLE_2.get()); 1656 1657 examples.put( 1658 new String[] 1659 { 1660 "--decrypt", "support-data-ds-inst1-" + 1661 StaticUtils.encodeGeneralizedTime(new Date()) + 1662 "-zip-encrypted", 1663 "--passphraseFile", "encryption-passphrase.txt" 1664 }, 1665 INFO_CSD_EXAMPLE_3.get()); 1666 1667 return examples; 1668 } 1669}