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