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