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